File: Geometry.hs

package info (click to toggle)
haskell-chart 1.9.5-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 384 kB
  • sloc: haskell: 4,680; makefile: 3
file content (396 lines) | stat: -rw-r--r-- 12,576 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
-----------------------------------------------------------------------------
-- |
-- Module      :  Graphics.Rendering.Chart.Geometry
-- Copyright   :  (c) Tim Docker 2006, 2014
-- License     :  BSD-style (see chart/COPYRIGHT)
--
module Graphics.Rendering.Chart.Geometry
  ( -- * Points and Vectors
    Rect(..)
  , Point(..)
  , Vector(..)

  , RectSize
  , Range

  , pointToVec

  , mkrect
  , rectPath
  , pvadd
  , pvsub
  , psub
  , vangle
  , vlen
  , vscale
  , within
  , intersectRect

  , RectEdge(..)
  , Limit(..)
  , PointMapFn

  -- * Paths
  , Path(..)
  , lineTo, moveTo
  , lineTo', moveTo'
  , arc, arc'
  , arcNeg, arcNeg'
  , close

  , foldPath
  , makeLinesExplicit

  -- * Matrices
  , transformP, scaleP, rotateP, translateP
  , Matrix(..)
  , identity
  , rotate, scale, translate
  , scalarMultiply
  , adjoint
  , invert
  ) where

import qualified Prelude
import Prelude hiding ((^))

-- The homomorphic version to avoid casts inside the code.
(^) :: Num a => a -> Integer -> a
(^) = (Prelude.^)

-- | A point in two dimensions.
data Point = Point {
    p_x :: Double,
    p_y :: Double
} deriving Show

-- | A vector in two dimensions.
data Vector = Vector {
    v_x :: Double,
    v_y :: Double
} deriving Show

-- | Convert a 'Point' to a 'Vector'.
pointToVec :: Point -> Vector
pointToVec (Point x y) = Vector x y

-- | Angle of a vector (counterclockwise from positive x-axis)
vangle :: Vector -> Double
vangle (Vector x y)
    | x > 0 = atan (y/x)
    | x < 0 = atan (y/x) + pi
    | otherwise = if y > 0 then pi/2 else -pi/2

-- | Length/magnitude of a vector
vlen :: Vector -> Double
vlen (Vector x y) = sqrt $ x^2 + y^2

-- | Scale a vector by a constant.
vscale :: Double -> Vector -> Vector
vscale c (Vector x y) = Vector (x*c) (y*c)

-- | Add a point and a vector.
pvadd :: Point -> Vector -> Point
pvadd (Point x1 y1) (Vector x2 y2) = Point (x1+x2) (y1+y2)

-- | Subtract a vector from a point.
pvsub :: Point -> Vector -> Point
pvsub (Point x1 y1) (Vector x2 y2) = Point (x1-x2) (y1-y2)

-- | Subtract two points.
psub :: Point -> Point -> Vector
psub (Point x1 y1) (Point x2 y2) = Vector (x1-x2) (y1-y2)

data Limit a = LMin | LValue a | LMax
   deriving Show

-- | A function mapping between points.
type PointMapFn x y = (Limit x, Limit y) -> Point

-- | A rectangle is defined by two points.
data Rect = Rect Point Point
   deriving Show

-- | Edge of a rectangle.
data RectEdge = E_Top | E_Bottom | E_Left | E_Right

-- | Create a rectangle based upon the coordinates of 4 points.
mkrect :: Point -> Point -> Point -> Point -> Rect
mkrect (Point x1 _) (Point _ y2) (Point x3 _) (Point _ y4) =
    Rect (Point x1 y2) (Point x3 y4)

-- | Test if a point is within a rectangle.
within :: Point -> Rect -> Bool
within (Point x y) (Rect (Point x1 y1) (Point x2 y2)) =
    x >= x1 && x <= x2 && y >= y1 && y <= y2

-- | Intersects the rectangles. If they intersect the
--   intersection rectangle is returned.
--   'LMin' is the empty rectangle / intersection and
--   'LMax' is the infinite plane.
intersectRect :: Limit Rect -> Limit Rect -> Limit Rect
intersectRect LMax r = r
intersectRect r LMax = r
intersectRect LMin _ = LMin
intersectRect _ LMin = LMin
intersectRect (LValue (Rect (Point x11 y11) (Point x12 y12)))
              (LValue (Rect (Point x21 y21) (Point x22 y22))) =
  let p1@(Point x1 y1) = Point (max x11 x21) (max y11 y21)
      p2@(Point x2 y2) = Point (min x12 x22) (min y12 y22)
  in if x2 < x1 || y2 < y1
        then LMin
        else LValue $ Rect p1 p2

type Range    = (Double,Double)
type RectSize = (Double,Double)

{-
-- | Make a path from a rectangle.
rectPointPath :: Rect -> [Point]
rectPointPath (Rect p1@(Point x1 y1) p3@(Point x2 y2)) = [p1,p2,p3,p4,p1]
  where
    p2 = (Point x1 y2)
    p4 = (Point x2 y1)
-}

-- | Make a path from a rectangle.
rectPath :: Rect -> Path
rectPath (Rect p1@(Point x1 y1) p3@(Point x2 y2)) =
  let p2 = Point x1 y2
      p4 = Point x2 y1
  in moveTo p1 <> lineTo p2 <> lineTo p3 <> lineTo p4 <> close

-- -----------------------------------------------------------------------
-- Path Types
-- -----------------------------------------------------------------------

-- | The path type used by Charts.
--
--   A path can consist of several subpaths. Each
--   is started by a 'MoveTo' operation. All subpaths
--   are open, except the last one, which may be closed
--   using the 'Close' operation. When filling a path
--   all subpaths are closed implicitly.
--
--   Closing a subpath means that a line is drawn from
--   the end point to the start point of the subpath.
--
--   If a 'Arc' (or 'ArcNeg') is drawn a implicit line
--   from the last end point of the subpath is drawn
--   to the beginning of the arc. Another implicit line
--   is drawn from the end of an arc to the beginning of
--   the next path segment.
--
--   The beginning of a subpath is either (0,0) or set
--   by a 'MoveTo' instruction. If the first subpath is started
--   with an arc the beginning of that subpath is the beginning
--   of the arc.
data Path = MoveTo Point Path
          | LineTo Point Path
          | Arc Point Double Double Double Path
          | ArcNeg Point Double Double Double Path
          | End
          | Close

-- | Paths are monoids. After a path is closed you can not append
--   anything to it anymore. The empty path is open.
--   Use 'close' to close a path.
instance Semigroup Path where
  p1 <> p2 = case p1 of
    MoveTo p path -> MoveTo p $ path <> p2
    LineTo p path -> LineTo p $ path <> p2
    Arc    p r a1 a2 path -> Arc p r a1 a2 $ path <> p2
    ArcNeg p r a1 a2 path -> ArcNeg p r a1 a2 $ path <> p2
    End   -> p2
    Close -> Close

instance Monoid Path where
  mappend = (<>)
  mempty = End

-- | Move the paths pointer to the given location.
moveTo :: Point -> Path
moveTo p = MoveTo p mempty

-- | Short-cut for 'moveTo', if you don't want to create a 'Point'.
moveTo' :: Double -> Double -> Path
moveTo' x y = moveTo $ Point x y

-- | Move the paths pointer to the given location and draw a straight
--   line while doing so.
lineTo :: Point -> Path
lineTo p = LineTo p mempty

-- | Short-cut for 'lineTo', if you don't want to create a 'Point'.
lineTo' :: Double -> Double -> Path
lineTo' x y = lineTo $ Point x y

-- | Draw the arc of a circle. A straight line connects
--   the end of the previous path with the beginning of the arc.
--   The zero angle points in direction of the positive x-axis.
--   Angles increase in clock-wise direction. If the stop angle
--   is smaller then the start angle it is increased by multiples of
--   @2 * pi@ until is is greater or equal.
arc :: Point  -- ^ Center point of the circle arc.
    -> Double -- ^ Radius of the circle.
    -> Double -- ^ Angle to start drawing at, in radians.
    -> Double -- ^ Angle to stop drawing at, in radians.
    -> Path
arc p r a1 a2 = Arc p r a1 a2 mempty

-- | Short-cut for 'arc', if you don't want to create a 'Point'.
arc' :: Double -> Double -> Double -> Double -> Double -> Path
arc' x y r a1 a2 = Arc (Point x y) r a1 a2 mempty

-- | Like 'arc', but draws from the stop angle to the start angle
--   instead of between them.
arcNeg :: Point -> Double -> Double -> Double -> Path
arcNeg p r a1 a2 = ArcNeg p r a1 a2 mempty

-- | Short-cut for 'arcNeg', if you don't want to create a 'Point'.
arcNeg' :: Double -> Double -> Double -> Double -> Double -> Path
arcNeg' x y r a1 a2 = ArcNeg (Point x y) r a1 a2 mempty

-- | A closed empty path. Closes a path when appended.
close :: Path
close = Close

-- | Fold the given path to a monoid structure.
foldPath :: (Monoid m)
         => (Point -> m) -- ^ MoveTo
         -> (Point -> m) -- ^ LineTo
         -> (Point -> Double -> Double -> Double -> m) -- ^ Arc
         -> (Point -> Double -> Double -> Double -> m) -- ^ ArcNeg
         -> m    -- ^ Close
         -> Path -- ^ Path to fold
         -> m
foldPath moveTo_ lineTo_ arc_ arcNeg_ close_ path =
  let restF = foldPath moveTo_ lineTo_ arc_ arcNeg_ close_
  in case path of
    MoveTo p rest -> moveTo_ p `mappend` restF rest
    LineTo p rest -> lineTo_ p `mappend` restF rest
    Arc    p r a1 a2 rest -> arc_    p r a1 a2 `mappend` restF rest
    ArcNeg p r a1 a2 rest -> arcNeg_ p r a1 a2 `mappend` restF rest
    End   -> mempty
    Close -> close_

-- | Enriches the path with explicit instructions to draw lines,
--   that otherwise would be implicit. See 'Path' for details
--   about what lines in paths are implicit.
makeLinesExplicit :: Path -> Path
makeLinesExplicit (Arc c r s e rest) =
  Arc c r s e $ makeLinesExplicit' rest
makeLinesExplicit (ArcNeg c r s e rest) =
  ArcNeg c r s e $ makeLinesExplicit' rest
makeLinesExplicit path = makeLinesExplicit' path

-- | Utility for 'makeLinesExplicit'.
makeLinesExplicit' :: Path -> Path
makeLinesExplicit' End   = End
makeLinesExplicit' Close = Close
makeLinesExplicit' (Arc c r s e rest) =
  let p = translateP (pointToVec c) $ rotateP s $ Point r 0
  in lineTo p <> arc c r s e <> makeLinesExplicit' rest
makeLinesExplicit' (ArcNeg c r s e rest) =
  let p = translateP (pointToVec c) $ rotateP s $ Point r 0
  in lineTo p <> arcNeg c r s e <> makeLinesExplicit' rest
makeLinesExplicit' (MoveTo p0 rest) =
  MoveTo p0 $ makeLinesExplicit' rest
makeLinesExplicit' (LineTo p0 rest) =
  LineTo p0 $ makeLinesExplicit' rest

-- -----------------------------------------------------------------------
-- Matrix Type
-- -----------------------------------------------------------------------

-- | Transform a point using the given matrix.
transformP :: Matrix -> Point -> Point
transformP t (Point x y) = Point
  (xx t * x + xy t * y + x0 t)
  (yx t * x + yy t * y + y0 t)

-- | Rotate a point around the origin.
--   The angle is given in radians.
rotateP :: Double -> Point -> Point
rotateP a = transformP (rotate a 1)

-- | Scale a point.
scaleP :: Vector -> Point -> Point
scaleP s = transformP (scale s 1)

-- | Translate a point.
translateP :: Vector -> Point -> Point
translateP = flip pvadd

-- | Copied from Graphics.Rendering.Cairo.Matrix
data Matrix = Matrix { xx :: !Double, yx :: !Double,
                       xy :: !Double, yy :: !Double,
                       x0 :: !Double, y0 :: !Double }
                     deriving Show

-- | Copied from Graphics.Rendering.Cairo.Matrix
instance Num Matrix where
  -- use underscore to avoid ghc complaints about shadowing the Matrix
  -- field names
  (*) (Matrix xx_ yx_ xy_ yy_ x0_ y0_)
      (Matrix xx'_ yx'_ xy'_ yy'_ x0'_ y0'_) =
    Matrix (xx_ * xx'_ + yx_ * xy'_)
           (xx_ * yx'_ + yx_ * yy'_)
           (xy_ * xx'_ + yy_ * xy'_)
           (xy_ * yx'_ + yy_ * yy'_)
           (x0_ * xx'_ + y0_ * xy'_ + x0'_)
           (x0_ * yx'_ + y0_ * yy'_ + y0'_)

  (+) = pointwise2 (+)
  (-) = pointwise2 (-)

  negate = pointwise negate
  abs    = pointwise abs
  signum = pointwise signum

  fromInteger n = Matrix (fromInteger n) 0 0 (fromInteger n) 0 0

-- | Copied from Graphics.Rendering.Cairo.Matrix
{-# INLINE pointwise #-}
pointwise :: (Double -> Double) -> Matrix -> Matrix
pointwise f (Matrix xx_ yx_ xy_ yy_ x0_ y0_) =
  Matrix (f xx_) (f yx_) (f xy_) (f yy_) (f x0_) (f y0_)

-- | Copied from Graphics.Rendering.Cairo.Matrix
{-# INLINE pointwise2 #-}
pointwise2 :: (Double -> Double -> Double) -> Matrix -> Matrix -> Matrix
pointwise2 f (Matrix xx_ yx_ xy_ yy_ x0_ y0_) (Matrix xx'_ yx'_ xy'_ yy'_ x0'_ y0'_) =
  Matrix (f xx_ xx'_) (f yx_ yx'_) (f xy_ xy'_) (f yy_ yy'_) (f x0_ x0'_) (f y0_ y0'_)

-- | Copied from Graphics.Rendering.Cairo.Matrix
identity :: Matrix
identity = Matrix 1 0 0 1 0 0

-- | Copied and adopted from Graphics.Rendering.Cairo.Matrix
translate :: Vector -> Matrix -> Matrix
translate tv m = m * Matrix 1 0 0 1 (v_x tv) (v_y tv)

-- | Copied and adopted from Graphics.Rendering.Cairo.Matrix
scale :: Vector -> Matrix -> Matrix
scale sv m = m * Matrix (v_x sv) 0 0 (v_y sv) 0 0

-- | Copied from Graphics.Rendering.Cairo.Matrix
--   Rotations angle is given in radians.
rotate :: Double -> Matrix -> Matrix
rotate r m = m * Matrix c s (-s) c 0 0
  where s = sin r
        c = cos r

-- | Copied from Graphics.Rendering.Cairo.Matrix
scalarMultiply :: Double -> Matrix -> Matrix
scalarMultiply scalar = pointwise (* scalar)

-- | Copied from Graphics.Rendering.Cairo.Matrix
adjoint :: Matrix -> Matrix
adjoint (Matrix a b c d tx ty) =
  Matrix d (-b) (-c) a (c*ty - d*tx) (b*tx - a*ty)

-- | Copied from Graphics.Rendering.Cairo.Matrix
invert :: Matrix -> Matrix
invert m@(Matrix xx_ yx_ xy_ yy_ _ _) = scalarMultiply (recip det) $ adjoint m
  where det = xx_*yy_ - yx_*xy_