diagrams-lib-0.3: src/Diagrams/TwoD/Shapes.hs
{-# LANGUAGE TypeFamilies, FlexibleContexts #-}
-----------------------------------------------------------------------------
-- |
-- Module : Diagrams.TwoD.Shapes
-- Copyright : (c) 2011 diagrams-lib team (see LICENSE)
-- License : BSD-style (see LICENSE)
-- Maintainer : diagrams-discuss@googlegroups.com
--
-- Various two-dimensional shapes.
--
-----------------------------------------------------------------------------
module Diagrams.TwoD.Shapes
(
-- * Miscellaneous
hrule, vrule
-- * General polygons
, polygon, polygonVertices
, PolygonOpts(..), PolygonOrientation(..)
-- * Special polygons
, unitSquare
, square
, rect
, starPolygon
, eqTriangle
-- * Other shapes
, roundedRect
) where
import Graphics.Rendering.Diagrams
import Diagrams.Segment
import Diagrams.Path
import Diagrams.TwoD.Arc
import Diagrams.TwoD.Types
import Diagrams.TwoD.Transform
import Diagrams.Util
import Data.Monoid
import Data.VectorSpace
import Data.Default
-- | Create a centered horizontal (L-R) line of the given length.
hrule :: (PathLike p, V p ~ R2) => Double -> p
hrule d = pathLike (P (-d/2,0)) False [Linear (d,0)]
-- | Create a centered vertical (T-B) line of the given length.
vrule :: (PathLike p, V p ~ R2) => Double -> p
vrule d = pathLike (P (0,d/2)) False [Linear (0,-d)]
-- | Determine how a polygon should be oriented.
data PolygonOrientation = NoOrient -- ^ No special orientation; one
-- vertex will be at (1,0).
-- This is the default.
| OrientToX -- ^ Orient so the botommost edge
-- is parallel to the x-axis.
| OrientToY -- ^ Orient so the leftmost edge
-- is parallel to the y-axis.
deriving (Eq, Ord, Show, Read)
data PolygonOpts = PolygonOpts {
sides :: Int -- ^ Number of sides; the default is 5.
, edgeSkip :: Int -- ^ Create star polygons by setting the
-- edge skip to some number other than 1
-- (the default). With an edge skip of n,
-- edges will connect every nth vertex.
, orientation :: PolygonOrientation
-- ^ Determine how the polygon should be
-- oriented.
}
deriving (Eq, Ord, Show, Read)
instance Default PolygonOpts where
def = PolygonOpts { sides = 5, edgeSkip = 1, orientation = NoOrient }
-- | Create a closed regular polygon from the given options.
polygon :: (PathLike p, V p ~ R2) => PolygonOpts -> p
polygon opts = pathLike v True (segmentsFromVertices vvs)
where vvs@(v:vs) = polygonVertices opts
-- | Generate the vertices of a regular polygon from the given
-- options.
polygonVertices :: PolygonOpts -> [P2]
polygonVertices opts = orient . take n . iterate (rotateBy turn) $ start
where start = translateX 1 origin
turn = fromIntegral (edgeSkip opts) / fromIntegral n
n = sides opts
orient | orientation opts == OrientToX = orientX
| orientation opts == OrientToY = orientY
| otherwise = id
orientX | odd n = rotateBy (1/4)
| n `mod` 4 == 0 = rotateBy (turn/2)
| otherwise = id
orientY | even n = rotateBy (turn/2)
| otherwise = id
-- | A sqaure with its center at the origin and sides of length 1,
-- oriented parallel to the axes.
unitSquare :: (Transformable p, PathLike p, V p ~ R2) => p
unitSquare = scale (1/sqrt 2) $ polygon with { sides = 4, orientation = OrientToX }
-- | A sqaure with its center at the origin and sides of the given
-- length, oriented parallel to the axes.
square :: (PathLike p, Transformable p, V p ~ R2) => Double -> p
square d = unitSquare # scale d
-- | @rect w h@ is an axis-aligned rectangle of width @w@ and height
-- @h@, centered at the origin.
rect :: (PathLike p, Transformable p, V p ~ R2) => Double -> Double -> p
rect w h = unitSquare # scaleX w # scaleY h
-- | @starPolygon p q@ creates a star polygon, where @p@ indicates the
-- number of vertices, and an edge connects every @q@th vertex.
starPolygon :: (PathLike p, Transformable p, V p ~ R2) => Int -> Int -> p
starPolygon p q = polygon def { sides = p, edgeSkip = q }
-- | An equilateral triangle, with radius 1 and base parallel to the
-- x-axis.
eqTriangle :: (PathLike p, Transformable p, V p ~ R2) => p
eqTriangle = polygon with {sides = 3, orientation = OrientToX}
{-
pentagon :: (Backend b R2, Renderable (Path R2) b) => Diagram b R2
pentagon = writeMe "pentagon"
hexagon :: (Backend b R2, Renderable (Path R2) b) => Diagram b R2
hexagon = writeMe "hexagon"
septagon :: (Backend b R2, Renderable (Path R2) b) => Diagram b R2
septagon = writeMe "septagon"
octagon :: (Backend b R2, Renderable (Path R2) b) => Diagram b R2
octagon = writeMe "octagon"
nonagon :: (Backend b R2, Renderable (Path R2) b) => Diagram b R2
nonagon = writeMe "nonagon"
decagon :: (Backend b R2, Renderable (Path R2) b) => Diagram b R2
decagon = writeMe "decagon"
-- | Construct a triangle from three side lengths, if possible. The
-- longest side will be parallel to the x-axis.
triangleFromSides :: (Backend b R2, Renderable (Path R2) b)
=> Double -> Double -> Double -> Maybe (Diagram b R2)
triangleFromSides = writeMe "triangleFromSides"
-}
------------------------------------------------------------
-- Other shapes ------------------------------------------
------------------------------------------------------------
-- | @roundedRect v r@ generates a closed trail, or closed path
-- centered at the origin, of an axis-aligned rectangle with diagonal
-- @v@ and circular rounded corners of radius @r@. @r@ must be
-- between @0@ and half the smaller dimension of @v@, inclusive; smaller or
-- larger values of @r@ will be treated as @0@ or half the smaller
-- dimension of @v@, respectively. The trail or path begins with the
-- right edge and proceeds counterclockwise.
roundedRect :: (PathLike p, V p ~ R2) => R2 -> Double -> p
roundedRect v r = pathLike (P (xOff/2 + r', -yOff/2)) True
. trailSegments
$ seg (0,yOff)
<> mkCorner 0
<> seg (-xOff,0)
<> mkCorner 1
<> seg (0, -yOff)
<> mkCorner 2
<> seg (xOff,0)
<> mkCorner 3
where seg = fromOffsets . (:[])
r' = clamp r 0 maxR
maxR = uncurry min v / 2
(xOff,yOff) = v ^-^ (2*r', 2*r')
mkCorner k | r' == 0 = mempty
| otherwise = arc (k/4) ((k+1)/4::CircleFrac) # scale r'
-- | @clamp x lo hi@ clamps @x@ to lie between @lo@ and @hi@
-- inclusive. That is, if @lo <= x <= hi@ it returns @x@; if @x < lo@
-- it returns @lo@, and if @hi < x@ it returns @hi@.
clamp :: Ord a => a -> a -> a -> a
clamp x lo hi | x < lo = lo
| x > hi = hi
| otherwise = x