diagrams-lib-1.2: src/Diagrams/TwoD/Arrowheads.hs
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE NoMonomorphismRestriction #-}
{-# LANGUAGE TypeFamilies #-}
-----------------------------------------------------------------------------
-- |
-- Module : Diagrams.TwoD.Arrowheads
-- Copyright : (c) 2013 diagrams-lib team (see LICENSE)
-- License : BSD-style (see LICENSE)
-- Maintainer : diagrams-discuss@googlegroups.com
--
-- Standard arrowheads and tails. Each arrowhead or tail is designed
-- to be drawn filled, with a line width of 0, and is normalized to
-- fit inside a circle of diameter 1.
--
-----------------------------------------------------------------------------
module Diagrams.TwoD.Arrowheads
(
-- * Arrowheads
-- ** Standard arrowheads
tri
, dart
, spike
, thorn
, lineHead
, noHead
-- ** Configurable arrowheads
-- | Creates arrowheads of the same shape as the standard heads but
-- where the angle parameter is used to specify the angle to the top
-- left point of the arrowhead.
, arrowheadTriangle
, arrowheadDart
, arrowheadSpike
, arrowheadThorn
-- * Arrow tails
-- ** Standard arrow tails
, tri'
, dart'
, spike'
, thorn'
, lineTail
, noTail
, quill
, block
-- ** Configurable arrow tails
, arrowtailQuill
, arrowtailBlock
-- * Internals
, ArrowHT
) where
import Control.Lens ((&), (.~), (^.))
import Data.AffineSpace
import Data.Default.Class
import Data.Monoid (mempty, (<>))
import Data.VectorSpace
import Diagrams.Angle
import Diagrams.Core
import Diagrams.Coordinates ((^&))
import Diagrams.Path
import Diagrams.Segment
import Diagrams.Trail
import Diagrams.TrailLike (fromOffsets)
import Diagrams.TwoD.Align
import Diagrams.TwoD.Arc (arc')
import Diagrams.TwoD.Path ()
import Diagrams.TwoD.Polygons
import Diagrams.TwoD.Shapes
import Diagrams.TwoD.Transform
import Diagrams.TwoD.Types
import Diagrams.TwoD.Vector (fromDirection, direction, unit_X)
import Diagrams.Util (( # ))
-----------------------------------------------------------------------------
type ArrowHT = Double -> Double -> (Path R2, Path R2)
closedPath :: (Floating (Scalar v), Ord (Scalar v), InnerSpace v) => Trail v -> Path v
closedPath = pathFromTrail . closeTrail
-- Heads ------------------------------------------------------------------
-- > drawHead h = arrowAt' (with & arrowHead .~ h & shaftStyle %~ lw none)
-- > origin (r2 (0.001, 0))
-- > <> square 0.5 # alignL # lw none
-- | Isoceles triangle style. The above example specifies an angle of `2/5 Turn`.
-- | <<diagrams/src_Diagrams_TwoD_Arrowheads_tri25Ex.svg#diagram=tri25Ex&width=120>>
-- > tri25Ex = arrowAt' (with & arrowHead .~ arrowheadTriangle (2/5 @@ turn) & shaftStyle %~ lw none)
-- > origin (r2 (0.001, 0))
-- > <> square 0.6 # alignL # lw none
arrowheadTriangle :: Angle -> ArrowHT
arrowheadTriangle theta = aHead
where
aHead len _ = (p, mempty)
where
psi = pi - (theta ^. rad)
r = len / (1 + cos psi)
p = polygon (def & polyType .~ PolyPolar [theta, (negateV 2 *^ theta)]
(repeat r) & polyOrient .~ NoOrient) # alignL
-- | Isoceles triangle with linear concave base. Inkscape type 1 - dart like.
arrowheadDart :: Angle -> ArrowHT
arrowheadDart theta len shaftWidth = (hd # scale size, jt)
where
hd = snugL . pathFromTrail . glueTrail $ fromOffsets [t1, t2, b2, b1]
jt = pathFromTrail . glueTrail $ j <> reflectY j
j = closeTrail $ fromOffsets [(-jLength ^& 0), (0 ^& shaftWidth / 2)]
v = fromDirection theta
(t1, t2) = (unit_X ^+^ v, (-0.5 ^& 0) ^-^ v)
[b1, b2] = map (reflectY . negateV) [t1, t2]
psi = pi - (direction . negateV $ t2) ^. rad
jLength = shaftWidth / (2 * tan psi)
-- If the shaft if too wide, set the size to a default value of 1.
size = max 1 ((len - jLength) / (1.5))
-- | Isoceles triangle with curved concave base. Inkscape type 2.
arrowheadSpike :: Angle -> ArrowHT
arrowheadSpike theta len shaftWidth = (hd # scale r, jt # scale r)
where
hd = snugL . closedPath $ l1 <> c <> l2
jt = alignR . centerY . pathFromTrail
. closeTrail $ arc' 1 (negateV phi) phi
l1 = trailFromSegments [straight $ unit_X ^+^ v]
l2 = trailFromSegments [reverseSegment . straight $ (unit_X ^+^ (reflectY v))]
c = reflectX $ arc' 1 theta (negateV theta)
v = fromDirection theta
-- The length of the head without its joint is, -2r cos theta and
-- the length of the joint is r - sqrt (r^2 - y^2). So the total
-- length of the arrow head is given by r(1 - 2 cos theta)-sqrt (r^2-y^2).
-- Solving the quadratic gives two roots, we want the larger one.
-- 1/4 turn < theta < 2/3 turn.
a = 1 - 2 * cos (theta ^. rad)
y = shaftWidth / 2
-- If the shaft is too wide for the head, we default the radius r to
-- 2/3 * len by setting d=1 and phi=pi/2.
d = max 1 (len**2 + (1 - a**2) * y**2)
r = (a * len + sqrt d) / (a**2 -1)
phi = asin (min 1 (y/r)) @@ rad
-- | Curved sides, linear concave base. Illustrator CS5 #3
arrowheadThorn :: Angle -> ArrowHT
arrowheadThorn theta len shaftWidth = (hd # scale size, jt)
where
hd = snugL . pathFromTrail . glueTrail $ hTop <> reflectY hTop
hTop = closeTrail . trailFromSegments $ [c, l]
jt = pathFromTrail . glueTrail $ j <> reflectY j
j = closeTrail $ fromOffsets [(-jLength ^& 0), (0 ^& shaftWidth / 2)]
c = curvedSide theta
v = fromDirection theta
l = reverseSegment . straight $ t
t = v ^-^ (-0.5 ^& 0)
psi = pi - (direction . negateV $ t) ^. rad
jLength = shaftWidth / (2 * tan psi)
-- If the shaft if too wide, set the size to a default value of 1.
size = max 1 ((len - jLength) / (1.5))
-- | Make a side for the thorn head.
curvedSide :: Angle -> Segment Closed R2
curvedSide theta = bezier3 ctrl1 ctrl2 end
where
v0 = unit_X
v1 = fromDirection theta
ctrl1 = v0
ctrl2 = v0 ^+^ v1
end = v0 ^+^ v1
-- Standard heads ---------------------------------------------------------
-- | A line the same width as the shaft.
lineHead :: ArrowHT
lineHead s w = (square 1 # scaleX s # scaleY w # alignL, mempty)
noHead :: ArrowHT
noHead _ _ = (mempty, mempty)
-- | <<diagrams/src_Diagrams_TwoD_Arrowheads_triEx.svg#diagram=triEx&width=100>>
-- > triEx = drawHead tri
tri :: ArrowHT
tri = arrowheadTriangle (1/3 @@ turn)
-- | <<diagrams/src_Diagrams_TwoD_Arrowheads_spikeEx.svg#diagram=spikeEx&width=100>>
-- > spikeEx = drawHead spike
spike :: ArrowHT
spike = arrowheadSpike (3/8 @@ turn)
-- | <<diagrams/src_Diagrams_TwoD_Arrowheads_thornEx.svg#diagram=thornEx&width=100>>
-- > thornEx = drawHead thorn
thorn :: ArrowHT
thorn = arrowheadThorn (3/8 @@ turn)
-- | <<diagrams/src_Diagrams_TwoD_Arrowheads_dartEx.svg#diagram=dartEx&width=100>>
-- > dartEx = drawHead dart
dart :: ArrowHT
dart = arrowheadDart (2/5 @@ turn)
-- Tails ------------------------------------------------------------------
-- > drawTail t = arrowAt' (with & arrowTail .~ t & shaftStyle %~ lw none & arrowHead .~ noHead)
-- > origin (r2 (0.001, 0))
-- > <> square 0.5 # alignL # lw none
-- | Utility function to convert any arrowhead to an arrowtail, i.e.
-- attached at the start of the trail.
headToTail :: ArrowHT -> ArrowHT
headToTail hd = tl
where
tl size shaftWidth = (t, j)
where
(t', j') = hd size shaftWidth
t = reflectX t'
j = reflectX j'
arrowtailBlock :: Angle -> ArrowHT
arrowtailBlock theta = aTail
where
aTail len _ = (t, mempty)
where
t = rect len (len * x) # alignR
a' = fromDirection theta
a = a' ^-^ (reflectY a')
x = magnitude a
-- | The angle is where the top left corner intersects the circle.
arrowtailQuill :: Angle -> ArrowHT
arrowtailQuill theta = aTail
where
aTail len shaftWidth = (t, j)
where
t = ( closedPath $ trailFromVertices [v0, v1, v2, v3, v4, v5, v0] )
# scale size # alignR
size = len / 0.6
v0 = p2 (0.5, 0)
v2 = p2 (unr2 $ fromDirection theta # scale 0.5)
v1 = v2 # translateX (5/8)
v3 = p2 (-0.1, 0)
v4 = v2 # reflectY
v5 = v4 # translateX (5/8)
s = 1 - shaftWidth / magnitude (v1 .-. v5)
n1 = v0 # translateY (0.5 * shaftWidth)
n2 = v1 .-^ ((v1 .-. v0) # scale s)
n3 = v5 .-^ ((v5 .-. v0) # scale s)
n4 = n1 # reflectY
j = ( closedPath $ trailFromVertices
[ v0, n1, n2, v0, n3, n4, v0 ])
-- Standard tails ---------------------------------------------------------
-- | A line the same width as the shaft.
lineTail :: ArrowHT
lineTail s w = (square 1 # scaleY w # scaleX s # alignR, mempty)
noTail :: ArrowHT
noTail _ _ = (mempty, mempty)
-- | <<diagrams/src_Diagrams_TwoD_Arrowheads_tri'Ex.svg#diagram=tri'Ex&width=100>>
-- > tri'Ex = drawTail tri'
tri' :: ArrowHT
tri' = headToTail tri
-- | <<diagrams/src_Diagrams_TwoD_Arrowheads_spike'Ex.svg#diagram=spike'Ex&width=100>>
-- > spike'Ex = drawTail spike'
spike' :: ArrowHT
spike' = headToTail spike
-- | <<diagrams/src_Diagrams_TwoD_Arrowheads_thorn'Ex.svg#diagram=thorn'Ex&width=100>>
-- > thorn'Ex = drawTail thorn'
thorn' :: ArrowHT
thorn' = headToTail thorn
-- | <<diagrams/src_Diagrams_TwoD_Arrowheads_dart'Ex.svg#diagram=dart'Ex&width=100>>
-- > dart'Ex = drawTail dart'
dart' :: ArrowHT
dart' = headToTail dart
-- | <<diagrams/src_Diagrams_TwoD_Arrowheads_quillEx.svg#diagram=quillEx&width=100>>
-- > quillEx = drawTail quill
quill :: ArrowHT
quill = arrowtailQuill (2/5 @@ turn)
-- | <<diagrams/src_Diagrams_TwoD_Arrowheads_blockEx.svg#diagram=blockEx&width=100>>
-- > blockEx = drawTail block
block :: ArrowHT
block = arrowtailBlock (7/16 @@ turn)