by Brent Yorgey on January 3, 2017
Tagged as: release, features, announcement, 1.4.
The diagrams team is very pleased to announce the release of diagrams 1.4. The release actually happened a few months ago, in October—we just hadn't gotten around to writing about it yet. But in any case this was a fairly quiet release, with very few breaking changes; mostly 1.4 just introduced new features. There is a migration guide which lists a few known potentially breaking changes, but most users should have no trouble. The rest of this post highlights some of the new features in 1.4.
The existing Diagrams.TwoD.Layout.Tree
module from
diagrams-contrib
has been extended with a new radialLayout
function, based on an algorithm by Andy Pavlo.
> import Diagrams.TwoD.Layout.Tree
> import Data.Tree
>
> t = Node 'A'
> [ Node 'B' (map lf "CDE")
> , Node 'F' [Node 'G' (map lf "HIJKLM"), Node 'N' (map lf "OPQRS")]
> , Node 'T' (map lf "UVWXYZ")
> ]
> where lf x = Node x []
>
> example =
> renderTree (\n -> (text (show n) # fontSizeG 0.5
> <> circle 0.5 # fc white))
> (~~) (radialLayout t)
> # centerXY # frame 0.5
Sometimes, it is desirable to compose some diagrams according to a
certain alignment, but without affecting their local origins. The
composeAligned
function can be used for this purpose. It takes as
arguments an alignment function (such as alignT
or snugL
), a
composition function of type [Diagram] -> Diagram
, and produces a
new composition function which works by first aligning the diagrams
before composing them.
> example :: Diagram B
> example = (hsep 2 # composeAligned alignT) (map circle [5,1,3,2])
> # showOrigin
The example above shows using hsep 2
to compose a collection of
top-aligned circles. Notice how the origin of the composed diagram is
still at the center of the leftmost circle, instead of at its top edge
(where it would normally be placed by alignT
).
The new Diagrams.TwoD.Layout.Constrained
module from
diagrams-contrib
implements basic linear constraint-based
layout. As a simple example of something that would be tedious to
draw without some kind of constraint solving, consider this diagram
which consists of a vertical stack of circles of different sizes,
along with an accompanying set of squares, such that (1) each square
is constrained to lie on the same horizontal line as a circle, and (2)
the squares all lie on a diagonal line.
> import Diagrams.TwoD.Layout.Constrained
> import Control.Monad (zipWithM_)
>
> example :: Diagram B
> example = frame 1 $ layout $ do
> let rs = [2,2,4,3,5,6]
> cirs <- newDias (map circle rs # fc blue)
> sqs <- newDias (replicate (length rs) (square 2) # fc orange)
> constrainWith vcat cirs
> zipWithM_ sameY cirs sqs
> constrainWith hcat [cirs !! 0, sqs !! 0]
> along (direction (1 ^& (-1))) (map centerOf sqs)
See the package documentation for more examples and documentation.
Another new module in diagrams-contrib
,
Diagrams.Anchors
, provides a convenient interface for aligning
diagrams relative to named anchor points. This can be useful, for
example, when laying out diagrams composed of pieces that should
"attach" to each other at various points.
We don't have an example of its use at the moment—if you play with it and create a nice example, let us know!
The new Diagrams.TwoD.Path.Boolean
module from
diagrams-contrib
contains functions for computing boolean
combinations of paths, such as union, intersection, difference, and
symmetric difference.
> import qualified Diagrams.TwoD.Path.Boolean as B
>
> thing1, thing2 :: Path V2 Double
> thing1 = square 1
> thing2 = circle 0.5 # translate (0.5 ^& (-0.5))
>
> example = hsep 0.5 . fc green . map strokePath $
> [ B.union Winding (thing1 <> thing2)
> , B.intersection Winding thing1 thing2
> , B.difference Winding thing1 thing2
> , B.exclusion Winding thing1 thing2
> ]
Diagrams.CubicSpline
has a new function, bspline
, which
creates a smooth curve (to be precise, a [uniform cubic
B-spline](https://en.wikipedia.org/wiki/B-spline) ) with the given
points as control points. The curve begins and ends at the first and
last points, and is tangent to the lines to the second-to-last control
points. It does not, in general, pass through the intermediate
control points.
> pts = map p2 (zip [0 .. 8] (cycle [0, 1]))
> example = mconcat
> [ bspline pts
> , mconcat $ map (place (circle 0.1 # fc blue # lw none)) pts
> ]
One major difference between cubicSpline
and bspline
is that the
curves generated by cubicSpline
depend on the control points in a
global way—that is, changing one control point could alter the
entire curve—whereas with bspline
, each control point only affects
a local portion of the curve.
diagrams-contrib
has a new module,
Diagrams.TwoD.Path.Follow
, which defines a wrapper type
Following n
. Following
is just like Trail' Line V2
, except that
it has a different Monoid
instance: following values are
concatenated, just like regular lines, except that they are also
rotated so the tangents match at the join point. In addition, they are
normalized so the tangent at the start point is in the direction of
the positive \(x\) axis (essentially we are considering trails
equivalent up to rotation).
> import Control.Lens (ala)
> import Diagrams.TwoD.Path.Follow
>
> wibble :: Trail' Line V2 Double
> wibble = hrule 1 <> hrule 0.5 # rotateBy (1/6) <> hrule 0.5 # rotateBy (-1/6) <> a
> where a = arc (xDir # rotateBy (-1/4)) (1/5 @@ turn)
> # scale 0.7
>
> example =
> [ wibble
> , wibble
> # replicate 5
> # ala follow foldMap
> ]
> # map stroke
> # map centerXY
> # hsep 1
> # frame 0.3
Notice how the above example makes use of the ala
combinator from
Control.Lens
to automatically wrap all the Line
s using follow
before combining and then unwrap the result.
The new module Diagrams.TwoD.Path.LSystem
in
diagrams-contrib
draws L-systems described by recursive string
rewriting rules, and provides a number of examples that can be used as
starting points for exploration.
> import Diagrams.TwoD.Path.LSystem
> import qualified Data.Map as M
>
> tree :: RealFloat n => Int -> TurtleState n
> tree n = lSystem n (1/18 @@ turn) (symbols "F") rules
> where
> rules = M.fromList [rule 'F' "F[+>>>F]F[->>>F][>>>F]"]
>
> example = getTurtleDiagram $ tree 6
This example is already provided by the module as tree2
.
Randall Munroe, of xkcd fame, ran a survey to determine commonly used
names for colors, and published a list of the 954 most common colors
based on the results. Diagrams.Color.XKCD
from
diagrams-contrib
provides all these color names.
> import Diagrams.Color.XKCD
>
> colors = [booger, poisonGreen, cinnamon, petrol, vibrantPurple]
> example = hsep 0.1 (zipWith fcA colors (repeat (circle 1 # lw none)))