User manual
Diagrams User Manual
Contents
Preliminaries
Introduction
diagrams is a flexible, powerful embedded domain-specific language
(EDSL) for creating vector graphics and animations. The diagrams
framework is:
Declarative: you specify what a diagram is, not how to draw it.
diagramstakes care of the how.Compositional: diagrams can be easily combined in many ways to produce more complex diagrams.
Embedded: the full power of Haskell, including every library on Hackage, is available to help construct and manipulate graphics.
Extensible: extending diagrams with additional or higher-level functionality is as simple as writing a Haskell module.
Flexible: diagrams is designed from the ground up to be as generic and flexible as possible, with support for pluggable rendering backends and multiple vector spaces (2D, 3D, ...).
About this document
This document attempts to explain all major aspects of using the
diagrams core and standard libraries, organized by topic to make
it easy to find what you are looking for. It is not, however, a
complete reference of every single function in the standard library:
for that, see the API documentation listed under Other resources.
Most sections contain links to relevant module(s) you can follow to
read about other functions not covered in the text.
Module names in the text are typeset like this:
Diagrams.Prelude. Click on a module name to visit its
documentation. You can also click on any function or operator name in
code examples to take you to its documentation. Try it:
> example = circle 2 ||| unitCircleMathematical equations are typeset using MathJax. Right-click on any equation to access MathJax options, like displaying the LaTeX source, switching between MathML and HTML/CSS for display, zoom settings, and so on.
This user manual is still under construction. Content that has yet to be written is noted by a light blue box with a "document" icon on the right hand side, like this:
Explain zygohistomorphic prepromorphisms
Essay on postmodernist critiques of
diagramsvis-a-vis Kant
If you see a box like this in the place of something you would really
like to know about, please bug the developers (using the #diagrams IRC
channel on Freenode, or the diagrams mailing list) so they can
prioritize it!
Warnings, "gotchas", and other important asides are in a yellow box with a "warning" icon, like this:
Diagrams is extremely addictive and may be hazardous to your health!
You would do well to pay special attention to the contents of such boxes.
Other resources
Here are some other resources that may be helpful to you as you learn
about diagrams:
The API reference documentation for all the
diagramspackages is intended to be high-quality and up-to-date, and is available from the diagrams website. If you find an omission, error, or something confusing, please report it as a bug!The
diagramswebsite has a gallery of examples and links to tutorials, blog posts, and other documentation.The diagrams wiki is a good place to find tips and tricks, examples, answers to frequently asked questions, and more.
The
#diagramsIRC channel on Freenode is a friendly place where you can get help from otherdiagramsusers and developers.Consider joining the diagrams mailing list for discussions and announcements about
diagrams.See the issue trackers in the diagrams organization on github for a list of open tickets. If you find a bug or would like to request a feature, please file a ticket!
Installation
Before installing diagrams, you will need the following:
The Glasgow Haskell Compiler (GHC), version 6.12 or later (except 7.0.1, which has a type inference bug making
diagramshard to use).It is recommended (but not required) to have the latest release of the Haskell Platform (currently 2012.4.0.0). At the very least you will want the cabal-install tool.
If you are on OS X or Windows, GHC itself comes with the Haskell Platform; if you are on Linux, you will have to install GHC first.
Once you have successfully installed the Haskell platform, installing
diagrams should be as easy as issuing the command:
cabal install diagramsThe diagrams package is a convenience wrapper that simply pulls
in (by default) four other packages:
diagrams-core(core datatype definitions and utilities),
diagrams-lib(standard primitives and combinators),diagrams-contrib(user-contributed extensions), anddiagrams-svg(Haskell-native backend generating SVG files).
There is also a backend based on the cairo graphics library; it has support for more features than the SVG backend and additional output formats (PNG, PS, PDF), but can be much more difficult to install on some platforms (notably OS X). If you want the cairo backend, you can issue the command
cabal install gtk2hs-buildtools -fcairo diagrams(You can omit gtk2hs-buildtools if you have already installed it
previously. If you don't want the SVG backend at all, you can add
the -f-svg flag.)
There is also a Haskell-native postscript backend which will soon become officially supported but is currently unreleased; you are welcome to build it from source and try it out.
See the wiki for the most up-to-date information regarding installation. If you have trouble installing diagrams, feel free to send email to the diagrams mailing list; we would like to collect reports of problems and solutions on various platforms.
Getting started
Create a file called TestDiagram.hs (or whatever you like) with
the following contents:
{-# LANGUAGE NoMonomorphismRestriction #-}
import Diagrams.Prelude
import Diagrams.Backend.SVG.CmdLine
-- or:
-- import Diagrams.Backend.Cairo.CmdLine
-- if using the Cairo backend
main = defaultMain (circle 1)The first line turns off the dreaded monomorphism restriction, which is
quite important when using diagrams: otherwise you will probably
run into lots of crazy error messages.
Diagrams.Prelude re-exports most everything from the standard
library; Diagrams.Backend.SVG.CmdLine provides a command-line
interface to the SVG rendering backend.
To compile your program, type
$ ghc --make TestDiagram(Note that the $ indicates a command prompt and should not
actually be typed.) Then execute TestDiagram with some
appropriate options:
$ ./TestDiagram -w 100 -h 100 -o TestDiagram.svgThe above will generate a 100x100 SVG that should look like this:

(If you are using the cairo backend you can also request a .png,
.ps, or .pdf file; the output type is automatically
determined by the extension.)
Try typing
$ ./TestDiagram --helpto see the other options that are supported.
To get started quickly, you may wish to continue by reading the quick start tutorial; or you can continue reading the rest of this user manual.
Contributing
diagrams is an open-source project, and contributions are
encouraged! All diagrams-related repositories are in the diagrams
organization on github. The Contributing page on the
diagrams wiki explains how to get the repositories and make
contributions.
Essential concepts
Before we jump into the main content of the manual, this chapter explains a number of general ideas and central concepts that will recur throughought. If you're eager to skip right to the good stuff, feel free to skip this section at first, and come back to it when necessary; there are many links to this chapter from elsewhere in the manual.
Monoids
A monoid consists of
A set of elements \(S\)
An associative binary operation on the set, that is, some operation
\[\oplus \colon S \to S \to S\]for which
\[(x \oplus y) \oplus z = x \oplus (y \oplus z).\]An identity element \(i \in S\) which is the identity for \(\oplus\), that is,
\[x \oplus i = i \oplus x = x.\]
In Haskell, monoids are expressed using the Monoid type class,
defined in Data.Monoid:
> class Monoid m where
> mempty :: m
> mappend :: m -> m -> mThe mappend function represents the associative binary operation,
and mempty is the identity element. A function
> mconcat :: Monoid m => [m] -> mis also provided as a shorthand for the common operation of combining
a whole list of elements with mappend.
Since mappend is tediously long to write, diagrams provides the
operator (<>) as a synonym when built with versions of base
prior to 4.5. base-4.5 and newer provide (<>) in
Data.Monoid, which diagrams re-exports.
Monoids are used extensively in diagrams: diagrams,
transformations, envelopes, traces, trails, paths, styles, colors,
and queries are all instances of Monoid.
Faking optional named arguments
Many diagram-related operations can be customized in a wide variety of ways. For example, when creating a regular polygon, one can customize the number of sides, the radius, the orientation, and so on. However, to have a single function that takes all of these options as separate arguments is a real pain: it's hard to remember what the arugments are and what order they should go in, and often one wants to use default values for many of the options and only override a few. Some languages (such as Python) support optional, named function arguments, which are ideal for this sort of situation. Sadly, Haskell does not. However, we can fake it!
Any function which should take some optional, named arguments instead
takes a single argument which is a record of options. The record type
is declared to be an instance of the Default type class:
> class Default d where
> def :: dThat is, types which have a Default instance have some default value
called def. For option records, def is declared to be the record
containing all the default arguments. The idea is that you can pass
def as an argument to a function which takes a record of options,
and use record update syntax to override only the fields you want,
like this:
foo (def { arg1 = someValue, arg6 = blah })There are a couple more things to note. First, record update actually
binds more tightly than function application, so the parentheses
above are actually not necessary (this is a strange corner of Haskell
syntax but it works nicely for our purposes here). Second,
diagrams also defines with as a synonym for def, which makes
the syntax a bit more natural. So, instead of the above, you could
write
foo with { arg1 = someValue, arg6 = blah }Vectors and points
Although much of this user manual focuses on constructing
two-dimensional diagrams, the definitions in the core library in fact
work for any vector space. Vector spaces are defined in the
Data.VectorSpace module from the vector-space package.
Many objects (diagrams, paths, backends...) inherently live in some
particular vector space. The vector space associated to any type can
be computed by the type function V. So, for example, the type
Foo d => V d -> d -> dis the type of a two-argument function whose first argument is a
vector in whatever vector space corresponds to the type d (which
must be an instance of Foo).
Each vector space has a type of vectors v and an associated type
of scalars, Scalar v. A vector represents a direction and
magnitude, whereas a scalar represents only a magnitude. Useful
operations on vectors and scalars include:
Adding and subtracting vectors with
(^+^)and(^-^)Multiplying a vector by a scalar with
(*^)Linearly interpolating between two vectors with
lerpFinding the
magnitude(length) of a vectorProjecting one vector onto another with
project.
See Data.VectorSpace for other useful functions and operators.
One might think we could also identify points in a space with vectors having one end at the origin. However, this turns out to be a poor idea. There is a very important difference between vectors and points: namely, vectors are translationally invariant whereas points are not. A vector represents a direction and magnitude, not a location. Translating a vector has no effect. Points, on the other hand, represent a specific location. Translating a point results in a different point.
Although it is a bad idea to conflate vectors and points, we can
certainly represent points using vectors. The
vector-space-points package defines newtype wrapper around
vectors called Point. The most important connection between points
and vectors is given by (.-.), defined in
Data.AffineSpace. If p1 and p2 are points, p2 .-. p1 is
the vector giving the direction and distance from p1 to p2.
Offsetting a point by a vector (resulting in a new point) is
accomplished with (.+^).
Envelopes and local vector spaces
In order to be able to position diagrams relative to one another, each diagram must keep track of some bounds information. Rather than use a bounding box (which is neither general nor compositional) or even a more general bounding path (which is rather complicated to deal with), each diagram has an associated bounding function, called the envelope. Given some direction (represented by a vector) as input, the envelope answers the question: "how far in this direction must one go before reaching a perpendicular (hyper)plane that completely encloses the diagram on one side of it?"
That's a bit of a mouthful, so hopefully the below illustration will help clarify things if you found the above description confusing. (For completeness, the code used to generate the illustration is included, although you certainly aren't expected to understand it yet if you are just reading this manual for the first time!)

> illustrateEnvelope v d
> = mconcat
> [ origin ~~ (origin .+^ v)
> # lc black # lw 0.03
> , polygon with { polyType = PolyRegular 3 0.1
> , polyOrient = OrientTo (negateV v)
> }
> # fc black
> # translate v
> , origin ~~ b
> # lc green # lw 0.05
> , p1 ~~ p2
> # lc red # lw 0.02
> ]
> where
> b = envelopeP v d
> v' = normalized v
> p1 = b .+^ (rotateBy (1/4) v')
> p2 = b .+^ (rotateBy (-1/4) v')
>
> d1 :: Path R2
> d1 = circle 1
>
> d2 :: Path R2
> d2 = (pentagon 1 === roundedRect 1.5 0.7 0.3)
>
> example = (stroke d1 # showOrigin <> illustrateEnvelope (r2 (-0.5,0.3)) d1)
> ||| (stroke d2 # showOrigin <> illustrateEnvelope (r2 (0.5, 0.2)) d2)The black arrows represent inputs to the envelopes for the two diagrams; the envelopes' outputs are the distances represented by the thick green lines. The red lines illustrate the enclosing (hyper)planes (which are really to be thought of as extending infinitely to either side): notice how they are as close as possible to the diagrams without intersecting them at all.
Of course, the base point from which the envelope is
measuring matters quite a lot! If there were no base point, questions
of the form "how far do you have to go..." would be
meaningless—how far from where? This base point (indicated by the
red dots in the diagram above) is called the local origin of a
diagram. Every diagram has its own intrinsic local vector space;
operations on diagrams are always with respect to their local origin,
and you can affect the way diagrams are combined with one another by
moving their local origins. The showOrigin function is provided as
a quick way of visualizing the local origin of a diagram (also
illustrated above).
Postfix transformation
You will often see idiomatic diagrams code that looks like this:
foobar # attr1
# attr2
# attr3
# transform1There is nothing magical about (#), and it is not required in order
to apply attributes or transformations. In fact, it is nothing more
than reverse function application with a high precedence (namely, 8):
x # f = f x(#) is provided simply because it often reads better to first write
down what a diagram is, and then afterwards write down attributes
and modifications. Additionally, (#) has a high precedence so it
can be used to make "local" modifications without requiring lots of
parentheses:
> example = square 2 # fc red # rotateBy (1/3)
> ||| circle 1 # lc blue # fc greenNote how the modifiers fc red and rotateBy (1/3) apply only to the
square, and lc blue and fc green only to the circle ((|||) has a
precedence of 6, lower than that of (#)).
Types and type classes
Diagrams has been designed with the goals of flexibility and
power. The tradeoff is that it is far from simple, and the types
can be intimidating at first. For example, hcat is a function which
takes a list of diagrams and lays them out in a horizontal row. So
one might expect its type to be something like [Diagram] ->
Diagram. In actuality, its type is
> hcat :: (Juxtaposable a, HasOrigin a, Monoid' a, V a ~ R2) => [a] -> awhich may indeed be intimidating at first glance, and at any rate
takes a bit of time and practice to understand! The essential idea is
to realize that hcat is actually quite a bit more general than
previously described: it can lay out not just diagrams, but any
two-dimensional things (V a ~ R2) which can be positioned "next
to" one another (Juxtaposable), can be translated (HasOrigin), and
are an instance of Monoid (Monoid' is actually a synonym for the
combination of Monoid and Semigroup). This certainly includes
diagrams, but it also includes other things like paths, envelopes,
animations, and even tuples, lists, sets, or maps containing any of
these things.
At first, you may want to just try working through some examples intuitively, without worrying too much about the types involved. However, at some point you will of course want to dig deeper into understanding the types, either to understand an error message (though for help interpreting some common error messages, see Deciphering error messages) or to wield diagrams like a true type ninja. When that point comes, you should refer to Understanding diagrams types and the Type class reference.
Creating 2D diagrams
The main purpose of diagrams is to construct two-dimensional
vector graphics (although it can be used for more general purposes as
well). This section explains the building blocks provided by
diagrams-core and diagrams-lib for constructing
two-dimensional diagrams.
All 2D-specific things can be found in Diagrams.TwoD, which
re-exports most of the contents of Diagrams.TwoD.* modules. This
section also covers many things which are not specific to two
dimensions; later sections will make clear which are which.
Basic 2D types
Diagrams.TwoD.Types defines types for working with
two-dimensional Euclidean space and with angles.
Euclidean 2-space
There are three main type synonyms defined for referring to two-dimensional space:
R2is the type of the two-dimensional Euclidean vector space. The positive \(x\)-axis extends to the right, and the positive \(y\)-axis extends upwards. This is consistent with standard mathematical practice, but upside-down with respect to many common graphics systems. This is intentional: the goal is to provide an elegant interface which is abstracted as much as possible from implementation details.unitXandunitYare unit vectors in the positive \(x\)- and \(y\)-directions, respectively. Their negated counterparts areunit_Xandunit_Y.Vectors of type
R2can be created by passing a pair of type(Double, Double)to the functionr2; vectors can likewise be converted back into pairs usingunr2.Vectors can also be constructed and pattern-matched using the utilities defined in
Diagrams.Coordinates, which provides a uniform interface for constructing points and vectors of any dimension. Vectors can be created using the syntax(x & y)and pattern-matched by callingcoordsand then matching on the pattern(x :& y).P2is the type of points in two-dimensional space. It is a synonym forPoint R2. The distinction between points and vectors is important; see Vectors and points.Points can be created from pairs of coordinates using
p2and converted back usingunp2. They can also be constructed and destructed using the same syntax as for vectors, as defined inDiagrams.Coordinates.T2is the type of two-dimensional affine transformations. It is a synonym forTransformation R2.
Angles
The Angle type class classifies types which measure two-dimensional
angles. Three instances are provided by default (you can, of course,
also make your own):
CircleFracrepresents fractions of a circle. A value of1represents a full turn.Radrepresents angles measured in radians. A value oftau(that is, \(\tau = 2 \pi\)) represents a full turn. (If you haven't heard of \(\tau\), see The Tau Manifesto.)Degrepresents angles measured in degrees. A value of360represents a full turn.
The intention is that to pass an argument to a function that expects a
value of some Angle type, you can write something like (3 :: Deg)
or (3 :: Rad). The convertAngle function is also provided for
converting between different angle representations.
The direction function computes the angle of a vector, measured
clockwise from the positive \(x\)-axis.
Primitive shapes
diagrams-lib provides many standard two-dimensional shapes for
use in constructing diagrams.
Circles and ellipses
Circles can be created with the unitCircle and circle
functions, defined in Diagrams.TwoD.Ellipse.
For example,

> example = circle 0.5 <> unitCircleunitCircle creates a circle of radius 1 centered at the
origin; circle takes the desired radius as an argument.
Every ellipse is the image of the unit circle under some affine transformation, so ellipses can be created by appropriately scaling and rotating circles.

> example = unitCircle # scaleX 0.5 # rotateBy (1/6)For convenience the standard library also provides ellipse, for
creating an ellipse with a given eccentricity, and ellipseXY, for
creating an axis-aligned ellipse with specified radii in the x and y
directions.
Arcs
Diagrams.TwoD.Arc provides a function arc, which constructs a
radius-one circular arc starting at a first angle and extending
counterclockwise to the second.

> example = arc (tau/4 :: Rad) (4 * tau / 7 :: Rad)Pre-defined shapes
Diagrams.TwoD.Shapes provides a number of pre-defined
polygons and other path-based shapes. For example:
eqTriangleconstructs an equilateral triangle with sides of a given length.squareconstructs a square with a given side length;unitSquareconstructs a square with sides of length1.pentagon,hexagon, ...,dodecagonconstruct other regular polygons with sides of a given length.In general,
regPolyconstructs a regular polygon with any number of sides.rectconstructs a rectangle of a given width and height.roundedRectconstructs a rectangle with circular rounded corners.roundedRect'works likeroundedRectbut allowing a different radius to be set for each corner, usingRoundedRectOpts.

> example = square 1 ||| rect 0.3 0.5
> ||| eqTriangle 1
> ||| roundedRect 0.5 0.4 0.1
> ||| roundedRect 0.5 0.4 (-0.1)
> ||| roundedRect' 0.7 0.4 with { radiusTL = 0.2
> , radiusTR = -0.2
> , radiusBR = 0.1 }More special polygons may be added in future versions of the library.
Completing the hodgepodge in Diagrams.TwoD.Shapes for now, the
functions hrule and vrule create horizontal and vertical lines,
respectively.

> example = circle 1 ||| hrule 2 ||| circle 1General polygons
The polygon function from Diagrams.TwoD.Polygons can be used
to construct a wide variety of polygons. Its argument is a record of
optional arguments that control the generated polygon:
polyTypespecifies one of several methods for determining the vertices of the polygon:PolyRegularindicates a regular polygon with a certain number of sides and a given radius.PolySidesspecifies the vertices using a list of angles between edges, and a list of edge lengths.PolyPolarspecifies the vertices using polar coordinates: a list of central angles between vertices, and a list of vertex radii.
polyOrientspecifies thePolyOrientation: the polygon can be oriented with an edge parallel to the \(x\)-axis. with an edge parallel to the \(y\)-axis, or with an edge perpendicular to any given vector. You may also specify that no special orientation should be applied, in which case the first vertex of the polygon will be located along the positive \(x\)-axis.Additionally, a center other than the origin can be specified using
polyCenter.

> poly1 = polygon with { polyType = PolyRegular 13 5
> , polyOrient = OrientV }
> poly2 = polygon with { polyType = PolyPolar (repeat (1/40 :: CircleFrac))
> (take 40 $ cycle [2,7,4,6]) }
> example = (poly1 ||| strutX 1 ||| poly2) # lw 0.05Notice the idiom of using with to construct a record of default
options and selectively overriding particular options by name. with
is a synonym for def from the type class Default, which specifies
a default value for types which are instances. You can read more
about this idiom in the section Faking optional named arguments.
Star polygons
A "star polygon" is a polygon where the edges do not connect consecutive vertices; for example:

> example = star (StarSkip 3) (regPoly 13 1) # strokeDiagrams.TwoD.Polygons provides the star function for
creating star polygons of this sort, although it is actually quite a
bit more general.
As its second argument, star expects a list of points. One way to
generate a list of points is with polygon-generating functions such as
polygon or regPoly, or indeed, any function which can output any
PathLike type (see the section about PathLike), since a list of
points is an instance of the PathLike class. But of course, you are
free to construct the list of points using whatever method you like.
As its first argument, star takes a value of type StarOpts, for
which there are two possibilities:
StarSkipspecifies that every \(n\) th vertex should be connected by an edge.
> example = stroke (star (StarSkip 2) (regPoly 8 1)) > ||| strutX 1 > ||| stroke (star (StarSkip 3) (regPoly 8 1))As you can see,
starmay result in a path with multiple components, if the argument toStarSkipevenly divides the number of vertices.StarFuntakes as an argument a function of type(Int -> Int), which specifies which vertices should be connected to which other vertices. Given the function \(f\), vertex \(i\) is connected to vertex \(j\) if and only if \(f(i) \equiv j \pmod n\), where \(n\) is the number of vertices. This can be used as a compact, precise way of specifying how to connect a set of points (or as a fun way to visualize functions in \(Z_n\)!).
> funs = map (flip (^)) [2..6] > visualize f = stroke' with { vertexNames = [[0 .. 6 :: Int]] } > (regPoly 7 1) > # lw 0 > # showLabels > # fontSize 0.6 > <> star (StarFun f) (regPoly 7 1) > # stroke # lw 0.05 # lc red > example = centerXY . hcat' with {sep = 0.5} $ map visualize funs
You may notice that all the above examples need to call stroke (or
stroke'), which converts a path into a diagram. Many functions
similar to star are polymorphic in their return type over any
PathLike, but star is not. As we have seen, star may need to
construct a path with multiple components, which is not supported by
the PathLike class.
Composing diagrams
The diagrams framework is fundamentally compositional: complex
diagrams are created by combining simpler diagrams in various ways.
Many of the combination methods discussed in this section are defined
in Diagrams.Combinators.
Superimposing diagrams with atop
The most fundamental way to combine two diagrams is to place one on
top of the other with atop. The diagram d1 `atop` d2 is formed
by placing d1's local origin on top of d2's local origin; that is,
by identifying their local vector spaces.

> example = circle 1 `atop` square (sqrt 2)As noted before, diagrams form a monoid
with composition given by identification of vector spaces. atop is
simply a synonym for mappend (or (<>)), specialized to two
dimensions.
This also means that a list of diagrams can be stacked with mconcat;
that is, mconcat [d1, d2, d3, ...] is the diagram with d1 on top
of d2 on top of d3 on top of...

> example = mconcat [ circle 0.1 # fc green
> , eqTriangle 1 # scale 0.4 # fc yellow
> , square 1 # fc blue
> , circle 1 # fc red
> ]Juxtaposing diagrams
Fundamentally, atop is actually the only way to compose diagrams;
however, there are a number of other combining methods (all ultimately
implemented in terms of atop) provided for convenience.
Two diagrams can be placed next to each other using beside. The
first argument to beside is a vector specifying a direction. The
second and third arguments are diagrams, which are placed next to each
other so that the vector points from the first diagram to the second.

> example = beside (r2 (20,30))
> (circle 1 # fc orange)
> (circle 1.5 # fc purple)
> # showOriginAs can be seen from the above example, the length of the vector
makes no difference, only its direction is taken into account. (To
place diagrams at a certain fixed distance from each other, see
cat'.) As can also be seen, the local origin of the new, combined
diagram is the same as the local origin of the first diagram. (This
makes beside v associative, so diagrams under beside v form a
semigroup—but not a monoid, since there is no identity
element. mempty is a right but not a left identity for beside v.)
In older versions of diagrams, the local origin of the combined
diagram was at the point of tangency between the two diagrams. To
recover the old behavior, simply perform an alignment on the first in
the same direction before combining (see Alignment):

> example = beside (r2 (20,30))
> (circle 1 # fc orange # align (r2 (20,30)))
> (circle 1.5 # fc purple)
> # showOriginIf you want to place two diagrams next to each other using the local
origin of the second diagram, you can use something like beside' =
flip . beside . negateV, that is, use a vector in the opposite
direction and give the diagrams in the other order.
Since placing diagrams next to one another horizontally and vertically
is quite common, special combinators are provided for convenience.
(|||) and (===) are specializations of beside which juxtapose
diagrams in the \(x\)- and \(y\)-directions, respectively.

> d1 = circle 1 # fc red
> d2 = square 1 # fc blue
> example = (d1 ||| d2) ||| strutX 3 ||| ( d1
> ===
> d2 )Juxtaposing without composing
Sometimes, one may wish to position a diagram next to another
diagram without actually composing them. This can be accomplished
with the juxtapose function. In particular, juxtapose v d1 d2
returns a modified version of d2 which has been translated to be
next to d1 in the direction of v. (In fact, beside itself is
implemented as a call to juxtapose followed by a call to (<>).)

> d1 = juxtapose unitX (square 1) (circle 1 # fc red)
> d2 = juxtapose (unitX ^+^ unitY) (square 1) (circle 1 # fc green)
> d3 = juxtapose unitY (square 1) (circle 1 # fc blue)
> example = mconcat [d1, d2, d3]See envelopes and local vector spaces for more information on what "next to" means, Working with envelopes for information on functions available for manipulating envelopes, or Envelopes for precise details.
Concatenating diagrams
We have already seen one way to combine a list of diagrams, using
mconcat to stack them. Several other methods for combining lists of
diagrams are also provided in Diagrams.Combinators.
The simplest method of combining multiple diagrams is position,
which takes a list of diagrams paired with points, and places the
local origin of each diagram at the indicated point.

> example = position (zip (map mkPoint [-3, -2.8 .. 3]) (repeat dot))
> where dot = circle 0.2 # fc black
> mkPoint x = p2 (x,x^2)cat is an iterated version of beside, which takes a direction
vector and a list of diagrams, laying out the diagrams beside one
another in a row. The local origins of the subdiagrams will be placed
along a straight line in the direction of the given vector, and the
local origin of the first diagram in the list will be used as the
local origin of the final result.

> example = cat (r2 (2,-1)) (map p [3..8]) # showOrigin
> where p n = regPoly n 1 # lw 0.03Semantically speaking, cat v === foldr (beside v) mempty, although
the actual implementation of cat uses a more efficient balanced fold.
For more control over the way in which the diagrams are laid out, use
cat', a variant of cat which also takes a CatOpts record. See
the documentation for cat' and CatOpts to learn about the various
possibilities.

> example = cat' (r2 (2,-1)) with { catMethod = Distrib, sep = 2 } (map p [3..8])
> where p n = regPoly n 1 # lw 0.03
> # scale (1 + fromIntegral n/4)
> # showOriginFor convenience, Diagrams.TwoD.Combinators also provides hcat, hcat',
vcat, and vcat', variants of cat and cat' which concatenate
diagrams horizontally and vertically.
Finally, appends is like an iterated variant of beside, with the
important difference that multiple diagrams are placed next to a
single central diagram without reference to one another; simply
iterating beside causes each of the previously appended diagrams to
be taken into account when deciding where to place the next one. Of
course, appends is implemented in terms of juxtapose (see
Juxtaposing without composing).

> c = circle 1 # lw 0.03
> dirs = iterate (rotateBy (1/7)) unitX
> cdirs = zip dirs (replicate 7 c)
> example1 = appends c cdirs
> example2 = foldl (\a (v,b) -> beside v a b) c cdirs
> example = example1 ||| strutX 3 ||| example2Diagrams.Combinators also provides decoratePath and
decorateTrail, which are described in Decorating trails and
paths.
Modifying diagrams
Attributes and styles
Every diagram has a style which is an arbitrary collection of
attributes. This section will describe some of the default
attributes which are provided by the diagrams library and
recognized by most backends. However, you can easily create your own
attributes as well; for details, see Style and attribute internals.
In many examples, you will see attributes applied to diagrams using
the (#) operator. Keep in mind that there is nothing special about
this operator as far as attributes are concerned. It is merely
backwards function application, which is used for attributes since it
often reads better to have the main diagram come first, followed by
modifications to its attributes. See Postfix transformation.
In general, inner attributes (that is, attributes applied earlier) override outer ones. Note, however, that this is not a requirement. Each attribute may define its own specific method for combining multiple values. See Style and attribute internals for more details.
Most of the attributes discussed in this section are defined in
Diagrams.Attributes.
Color
Two-dimensional diagrams have two main colors, the color used to
stroke the paths in the diagram and the color used to fill them.
These can be set, respectively, with the lc (line color) and fc
(fill color) functions.

> example = circle 0.2 # lc purple # fc yellowBy default, diagrams use a black line color and a completely transparent fill color.
Colors themselves are handled by the colour package, which
provides a large set of predefined color names as well as many more
sophisticated color operations; see its documentation for more
information. The colour package uses a different type for
colors with an alpha channel (i.e. transparency). To make use of
transparent colors you can use lcA and fcA.

> import Data.Colour (withOpacity)
>
> colors = map (blue `withOpacity`) [0.1, 0.2 .. 1.0]
> example = hcat' with { catMethod = Distrib, sep = 1 }
> (zipWith fcA colors (repeat (circle 1)))Transparency can also be tweaked with the Opacity attribute, which
sets the opacity/transparency of a diagram as a whole. Applying
opacity p to a diagram, where p is a value between 0 and 1,
results in a diagram p times as opaque.

> s c = square 1 # fc c
> reds = (s darkred ||| s red) === (s pink ||| s indianred)
> example = hcat' with { sep = 1 } . take 4 . iterate (opacity 0.7) $ redsTo "set the background color" of a diagram, use the bg
function—which does not actually set any attributes, but simply
superimposes the diagram on top of a bounding rectangle of the given
color.

> t = regPoly 3 1
>
> example = t ||| t # bg orangeLine width
To alter the width of the lines used to stroke paths, use lw. The
default line width is (arbitrarily) 0.01. You can also set the line
width to zero if you do not want a path stroked at all.
Line width actually more subtle than you might think. Suppose you
create a diagram consisting of a square, and another square twice as
large next to it (using scale 2). How should they be drawn? Should
the lines be the same width, or should the larger square use a line
twice as thick?
In fact, in many situations the lines should actually be the same
thickness, so a collection of shapes will be drawn in a uniform way.
This is the default in diagrams. Specifically, the argument to
lw is measured with respect to the final vector space of a
complete, rendered diagram, not with respect to the local vector
space at the time the lw function is applied. Put another way,
subsequent transformations do not affect the line width. This is
perhaps a bit confusing, but trying to get line widths to look
reasonable would be a nightmare otherwise.

> example = (square 1
> ||| square 1 # scale 2
> ||| circle 1 # scaleX 3) # lw 0.03However, occasionally you do want subsequent transformations to
affect line width. The freeze function is supplied for this
purpose. Once freeze has been applied to a diagram, any subsequent
transformations will affect the line width.

> example = (square 1
> ||| square 1 # freeze # scale 2
> ||| circle 1 # freeze # scaleX 3) # lw 0.03Note that line width does not affect the envelope of diagrams at all. Future versions of the standard library may provide a function to convert a stroked path into an actual region, which would allow line width to be taken into account.
Other line parameters
Many rendering backends provide some control over the particular way
in which lines are drawn. Currently, diagrams provides support
for three aspects of line drawing:
lineCapsets theLineCapstyle.lineJoinsets theLineJoinstyle.dashingallows for drawing dashed lines with arbitrary dashing patterns.

> path = fromVertices (map p2 [(0,0), (1,0.3), (2,0), (2.2,0.3)]) # lw 0.1
> example = centerXY . vcat' with { sep = 0.1 }
> $ map (path #)
> [ lineCap LineCapButt . lineJoin LineJoinMiter
> , lineCap LineCapRound . lineJoin LineJoinRound
> , lineCap LineCapSquare . lineJoin LineJoinBevel
> , dashing [0.1,0.2,0.3,0.1] 0
> ]The HasStyle class
Functions such as fc, lc, lw, and lineCap do not take only
diagrams as arguments. They take any type which is an instance of the
HasStyle type class. Of course, diagrams themselves are an
instance.
However, the Style type is also an instance. This is useful in
writing functions which offer the caller flexible control over the
style of generated diagrams. The general pattern is to take a Style
(or several) as an argument, then apply it to a diagram along with
some default attributes:
> myFun style = d # applyStyle style # lc red # ...
> where d = ...This way, any attributes provided by the user in the style argument
will override the default attributes specified afterwards.
To call myFun, a user can construct a Style by starting with an
empty style (mempty, since Style is an instance of Monoid) and
applying the desired attributes:
> foo = myFun (mempty # fontSize 10 # lw 0 # fc green)If the type T is an instance of HasStyle, then [T] is also.
This means that you can apply styles uniformly to entire lists of
diagrams at once, which occasionally comes in handy. Likewise, there
are HasStyle instances for pairs, Maps, Sets, and functions.
2D Transformations
Any diagram can be transformed by applying arbitrary affine
transformations to it. Affine transformations include linear
transformations (rotation, scaling, reflection, shears—anything
which leaves the origin fixed and sends lines to lines) as well as
translations. Diagrams.TwoD.Transform defines a number of
common affine transformations in two-dimensional space. (To construct
transformations more directly, see
Diagrams.Core.Transform.)
Every transformation comes in two variants, a noun form and a verb
form. For example, there are two functions for scaling along the
\(x\)-axis, scalingX and scaleX. The noun form constructs a
transformation object, which can then be stored in a data structure,
passed as an argument, combined with other transformations, etc.,
and ultimately applied to a diagram with the transform function.
The verb form directly applies the transformation to a diagram. The
verb form is much more common (and the documentation below will only
discuss verb forms), but getting one's hands on a transformation can
occasionally be useful.
Transformations in general
Before looking at specific two-dimensional transformations, it's worth
saying a bit about transformations in general (a fuller treatment can
be found under Transformations). The Transformation type is
defined in Diagrams.Core.Transform, from the
diagrams-core package. Transformation is parameterized by
the vector space over which it acts; recall that T2 is provided as a
synonym for Transformation R2.
Transformation v is a Monoid for any vector space v:
memptyis the identity transformation;mappendis composition of transformations:t1 `mappend` t2(also writtent1 <> t2) performs firstt2, thent1.
To invert a transformation, use inv. For any transformation t,
t <> inv t === inv t <> t === mempty.
To apply a transformation to a diagram, use transform.
Rotation
Use rotate to rotate a diagram couterclockwise by a given angle
about the origin. Since rotate takes an angle, you must specify an
angle type, such as rotate (80 :: Deg). In the common case that you
wish to rotate by an angle specified as a certain fraction of a
circle, like rotate (1/8 :: CircleFrac), you can use rotateBy
instead. rotateBy is specialized to only accept fractions of a
circle, so in this example you would only have to write
rotateBy (1/8).
You can also use rotateAbout in the case that you want to rotate
about some point other than the origin.

> eff = text "F" <> square 1 # lw 0
> rs = map rotateBy [1/7, 2/7 .. 6/7]
> example = hcat . map (eff #) $ rsScaling and reflection
Scaling by a given factor is accomplished with scale (which scales
uniformly in all directions), scaleX (which scales along the \(x\)-axis
only), or scaleY (which scales along the \(y\)-axis only). All of these
can be used both for enlarging (with a factor greater than one) and
shrinking (with a factor less than one). Using a negative factor
results in a reflection (in the case of scaleX and scaleY) or a
180-degree rotation (in the case of scale).

> eff = text "F" <> square 1 # lw 0
> ts = [ scale (1/2), id, scale 2, scaleX 2, scaleY 2
> , scale (-1), scaleX (-1), scaleY (-1)
> ]
>
> example = hcat . map (eff #) $ tsScaling by zero is forbidden. Let us never speak of it again.
For convenience, reflectX and reflectY perform reflection along
the \(x\)- and \(y\)-axes, respectively; but I think you can guess how they
are implemented. Their names can be confusing (does reflectX
reflect along the \(x\)-axis or across the \(x\)-axis?) but you can just
remember that reflectX = scaleX (-1).
To reflect in some line other than an axis, use reflectAbout.

> eff = text "F" <> square 1 # lw 0
> example = eff
> <> reflectAbout (p2 (0.2,0.2)) (rotateBy (-1/10) unitX) effTranslation
Translation is achieved with translate, translateX, and
translateY, which should be self-explanatory.
Conjugation
Diagrams.Transform exports useful transformation utilities
which are not specific to two dimensions. At the moment there are
only two: conjugate and under. The first simply performs
conjugation: conjugate t1 t2 == inv t1 <> t2 <> t1, that is,
performs t1, then t2, then undoes t1.
under performs a transformation using conjugation. It takes as
arguments a function to perform some transformation as well as a
transformation to conjugate by. For example, scaling by a factor of 2
along the diagonal line \(y = x\) can be accomplished thus:

> eff = text "F" <> square 1 # lw 0
> example = (scaleX 2 `under` rotation (-1/8 :: CircleFrac)) effThe letter F is first rotated so that the desired scaling axis lies
along the \(x\)-axis; then scaleX is performed; then it is rotated back
to its original position.
Note that reflectAbout and rotateAbout are implemented using
under.
The Transformable class
Transformations can be applied not just to diagrams, but values of any
type which is an instance of the Transformable type class.
Instances of Transformable include vectors, points, trails, paths,
envelopes, and Transformations themselves. In addition,
tuples, lists, maps, or sets of Transformable things are also
Transformable in the obvious way.
Alignment
Since diagrams are always combined with respect to their local origins, moving a diagram's local origin affects the way it combines with others. The position of a diagram's local origin is referred to as its alignment.
The functions moveOriginBy and moveOriginTo are provided for
explicitly moving a diagram's origin, by an absolute amount and to an
absolute location, respectively. moveOriginBy and translate are
actually dual, in the sense that
moveOriginBy v === translate (negateV v).This duality comes about since translate moves a diagram with
respect to its origin, whereas moveOriginBy moves the origin with
respect to the diagram. Both are provided so that you can use
whichever one corresponds to the most natural point of view in a given
situation, without having to worry about inserting calls to negateV.
Often, however, one wishes to move a diagram's origin with respect to
its envelope. To this end, some general tools are provided
in Diagrams.Align, and specialized 2D-specific ones by
Diagrams.TwoD.Align.
Functions like alignT (align Top) and alignBR (align Bottom Right)
move the local origin to the edge of the envelope:

> s = square 1 # fc yellow
> x |-| y = x ||| strutX 0.5 ||| y
> example = (s # showOrigin)
> |-| (s # alignT # showOrigin)
> |-| (s # alignBR # showOrigin)There are two things to note about the above example. First, notice
how alignT and alignBR move the local origin of the square in the
way you would expect. Second, notice that when placed "next to" each
other using the (|||) operator, the squares are placed so that their
local origins fall on a horizontal line.
Functions like alignY allow finer control over the alignment. In
the below example, the origin is moved to a series of locations
interpolating between the bottom and top of the square:

> s = square 1 # fc yellow
> example = hcat . map showOrigin
> $ zipWith alignY [-1, -0.8 .. 1] (repeat s)Working with paths
Paths are one of the most fundamental tools in diagrams. They can
be used not only directly to draw things, but also as guides to help
create and position other diagrams.
Segments
The most basic path component is a Segment, which is some sort of
primitive path from one point to another. Segments are
translationally invariant; that is, they have no inherent location,
and applying a translation to a segment has no effect (however, other
sorts of transformations, such as rotations and scales, have the
effect you would expect). In other words, a segment is not
a way to get from point A to point B; it is a way to get from
wherever you are to somewhere else.
Currently, diagrams supports
two types of segment, defined in Diagrams.Segment:
A linear segment is simply a straight line, defined by an offset from its beginning point to its end point; you can construct one using
straight.A Bézier segment is a cubic curve defined by an offset from its beginning to its end, along with two control points; you can construct one using
bezier3. An example is shown below, with the endpoints shown in red and the control points in blue. Bézier curves always start off from the beginning point heading towards the first control point, and end up at the final point heading away from the last control point. That is, in any drawing of a Bézier curve like the one below, the curve will be tangent to the two dotted lines.

> illustrateBezier c1 c2 x2
> = endpt
> <> endpt # translate x2
> <> ctrlpt # translate c1
> <> ctrlpt # translate c2
> <> l1
> <> l2
> <> fromSegments [bezier3 c1 c2 x2]
> where
> dashed = dashing [0.1,0.1] 0
> endpt = circle 0.05 # fc red # lw 0
> ctrlpt = circle 0.05 # fc blue # lw 0
> l1 = fromOffsets [c1] # dashed
> l2 = fromOffsets [x2 ^-^ c2] # translate c2 # dashed
>
> x2 = r2 (3,-1) :: R2 -- endpoint
> [c1,c2] = map r2 [(1,2), (3,0)] -- control points
>
> example = illustrateBezier c1 c2 x2Diagrams.Segment also provides a few tools for working with
segments:
atParamfor computing points along a segment;segOffsetfor computing the offset from the start of a segment to its endpoint;splitAtParamfor splitting a segment into two smaller segments;arcLengthfor approximating the arc length of a segment;arcLengthToParamfor approximating the parameter corresponding to a given arc length along the segment; andadjustSegmentfor extending or shrinking a segment.
Trails
Trails, defined in Diagrams.Path, are essentially lists of
segments laid end-to-end. Since segments are translationally
invariant, so are trails; that is, trails have no inherent starting
location, and translating them has no effect.
Trails can also be open or closed: a closed trail is one with an implicit (linear) segment connecting the endpoint of the trail to the starting point.
To construct a Trail, you can use one of the following:
fromOffsetstakes a list of vectors, and turns each one into a linear segment.fromVerticestakes a list of vertices, generating linear segments between them.(~~)creates a simple linear trail between two points.cubicSplinecreates a smooth curve passing through a given list of points; it is described in more detail in the section on Splines.fromSegmentstakes an explicit list ofSegments.
If you look at the types of these functions, you will note that they
do not, in fact, return just Trails: they actually return any type
which is an instance of PathLike, which includes Trails, Paths
(to be covered in the next section), Diagrams, and lists of points.
See the PathLike section for more on the PathLike class.
Trails form a Monoid with concatenation as the binary operation,
and the empty (no-segment) trail as the identity element. The example
below creates a two-segment trail called spike and then constructs
a starburst path by concatenating a number of rotated copies.
strokeT turns a trail into a diagram, with the start of the trail at
the local origin.

> spike :: Trail R2
> spike = fromOffsets . map r2 $ [(1,3), (1,-3)]
>
> burst = mconcat . take 13 . iterate (rotateBy (-1/13)) $ spike
>
> example = strokeT burst # fc yellow # lw 0.1 # lc orangeFor details on the functions provided for manipulating trails, see the
documentation for Diagrams.Path. One other function worth
mentioning is explodeTrail, which turns each segment in a trail into
its own individual Path. This is useful when you want to construct
a trail but then do different things with its individual segments.
For example, we could construct the same starburst as above but color
the edges individually:

> spike :: Trail R2
> spike = fromOffsets . map r2 $ [(1,3), (1,-3)]
>
> burst = mconcat . take 13 . iterate (rotateBy (-1/13)) $ spike
>
> colors = cycle [aqua, orange, deeppink, blueviolet, crimson, darkgreen]
>
> example = lw 0.1
> . mconcat
> . zipWith lc colors
> . map stroke . explodeTrail origin
> $ burst(If we wanted to fill the starburst with yellow as before, we would have to separately draw another copy of the trail with a line width of zero and fill that; this is left as an exercise for the reader.)
Paths
A Path, also defined in Diagrams.Path, is a (possibly empty)
collection of trails, along with an absolute starting location for
each trail. Paths of a single trail can be constructed using the same
functions described in the previous section: fromSegments,
fromOffsets, fromVertices, (~~), and cubicSpline.
Paths also form a Monoid, but the binary operation is
superposition (just like that of diagrams). Paths with
multiple components can be used, for example, to create shapes with
holes:

> ring :: Path R2
> ring = circle 3 <> circle 2
>
> example = stroke ring # fc purple # fillRule EvenOdd(See Fill rules for an explanation of the call to fillRule
EvenOdd.)
stroke turns a path into a diagram, just as strokeT turns a trail
into a diagram. (In fact, strokeT really works by first turning the
trail into a path and then calling stroke on the result.)
explodePath, similar to explodeTrail, turns the segments of a path
into individual paths. Since a path is a collection of trails, each
of which is a sequence of segments, explodePath actually returns a
list of lists of paths.
For information on other path manipulation functions such as
pathFromTrail, pathFromTrailAt, pathVertices, and pathOffsets,
see the documentation in Diagrams.Path.
Stroking trails and paths
The strokeT and stroke functions, which turn trails and paths into
diagrams respectively, have already been mentioned; they are defined
in Diagrams.TwoD.Path. Both also have primed variants,
strokeT' and stroke', which take a record of StrokeOpts.
Currently, StrokeOpts has two fields:
vertexNamestakes a list of lists of names, and zips each list with a component of the path, creating point subdiagrams (usingpointDiagram) associated with the names. This means that the names can be used to later refer to the locations of the path vertices (see Named subdiagrams). In the case ofstrokeT', only the first list is used.By default,
vertexNamesis an empty list.queryFillRulespecifies the fill rule (see Fill rules) used to determine which points are inside the diagram, for the purposes of its query (see Using queries). Note that it does not affect how the diagram is actually drawn; for that, use thefillRulefunction. (This is not exactly a feature, but for various technical reasons it is not at all obvious how to have this field actually affect both the query and the rendering of the diagram.)By default,
queryFillRuleis set toWinding.
Decorating trails and paths
Paths (and trails) can be used not just to draw certain shapes, but
also as tools for positioning other objects. To this end,
diagrams provides decoratePath and decorateTrail, which
position a list of objects at the vertices of a given path or trail,
respectively.
For example, suppose we want to create an equilateral triangular
arrangement of dots. One possibility is to create horizontal rows of
dots, center them, and stack them vertically. However, this is
annoying, because we must manually compute the proper vertical
stacking distance between rows. Whether you think this sounds easy or
not, it is certainly going to involve the sqrt function, or perhaps
some trig, or both, and we'd rather avoid all that.
Fortunately, there's an easier way: after creating the horizontal rows, we create the path corresponding to the left-hand side of the triangle (which can be done using a simple rotation), and then decorate it with the rows.

> dot = circle 1 # fc black
> mkRow n = hcat' with {sep = 0.5} (replicate n dot)
> mkTri n = decoratePath
> (fromOffsets (replicate (n-1) (2.5 *^ unitX))
> # rotateBy (1/6))
> (map mkRow [n, n-1 .. 1])
> example = mkTri 5The PathLike class
As you may have noticed by now, a large class of functions in the
standard library—such as square, polygon, fromVertices, and so
on—generate not just diagrams, but any type which is an instance
of the PathLike type class.
The PathLike type class has only a single method, pathLike:
> pathLike :: Point (V p)
> -> Bool
> -> [Segment (V p)]
> -> pThe first argument is a starting point for the path-like thing; path-like things which are translationally invariant (such as
Trails) simply ignore this argument.The second argument indicates whether the path-like thing should be closed.
The third argument specifies the segments of the path-like thing.
Currently, there are four instances of PathLike:
Trail: as noted before, the implementation ofpathLikeforTrails ignores the first argument, sinceTrails have no inherent starting location.Path: of course,pathLikecan only construct paths of a single component.Diagram b R2: as long as the backendbknows how to render 2D paths,pathLikecan construct a diagram by stroking the generated single-component path.[Point v]: this instance generates the vertices of the path.
It is quite convenient to be able to use, say, square 2 as a
diagram, path, trail, or list of vertices, whichever suits one's
needs. Otherwise, either four different functions would be needed for
each primitive (like square, squarePath, squareTrail, and
squareVertices, ugh), or else explicit conversion functions would
have to be inserted when you wanted something other than what the
square function gave you by default.
As an (admittedly contrived) example, the following diagram defines
s as an alias for square 2 and then uses it at all four instances of
PathLike:

> s = square 2 -- a squarish thing.
>
> blueSquares = decoratePath s {- 1 -}
> (replicate 4 (s {- 2 -} # scale 0.5) # fc blue)
> paths = lc purple . stroke $ star (StarSkip 2) s {- 3 -}
> aster = centerXY . lc green . strokeT
> . mconcat . take 5 . iterate (rotateBy (1/5))
> $ s {- 4 -}
> example = (blueSquares <> aster <> paths) # lw 0.05Exercise: figure out which occurrence of s has which type. (Answers
below.)
At its best, this type-directed behavior results in a "it just
works/do what I mean" experience. However, it can occasionally be
confusing, and care is needed. The biggest gotcha occurs when
combining a number of shapes using (<>) or mconcat: diagrams,
paths, trails, and lists of vertices all have Monoid instances, but
they are all different, so the combination of shapes has different
semantics depending on which type is inferred.

> ts = mconcat . take 3 . iterate (rotateBy (1/9)) $ eqTriangle 1
> example = (ts ||| stroke ts ||| strokeT ts ||| fromVertices ts) # fc redThe above example defines ts by generating three equilateral
triangles offset by 1/9 rotations, then combining them with mconcat.
The sneaky thing about this is that ts can have the type of any
PathLike instance, and it has completely different meanings
depending on which type is chosen. The example uses ts at each of
the four PathLike types:
Since
exampleis a diagram, the firstts, used by itself, is also a diagram; hence it is interpreted as three equilateral triangle diagrams superimposed on one another withatop.stroketurnsPaths into diagrams, so the secondtshas typePath R2. Hence it is interpreted as three triangular paths superimposed into one three-component path, which is then stroked.strokeTturnsTrails into diagrams, so the third occurrence oftshas typeTrail R2. It is thus interpreted as three triangular trails (without the implicit closing segments) sequenced end-to-end into one long trail.The last occurrence of
tsis a list of points, namely, the concatenation of the vertices of the three triangles. Turning this into a diagram withfromVerticesgenerates a single-component, open path that visits each of the points in turn. The generated diagram looks passingly similar to the one from the second occurrence ofts, but a careful look reveals that they are quite different.
Of course, one way to avoid all this would be to give ts a specific
type signature, if you know which type you would like it to be. Then
using it at a different type will result in a type error, rather than
confusing semantics.
Answers to the square 2 type inference challenge:
Path R2Diagram b R2[P2]Trail R2
The Closeable class
Creating closed paths can be accomplished with the close method of
the Closeable type class. There is also an open method, which
does what you would think. Currently, there are only two instances of
Closeable: Trail and Path.
Splines
Constructing Bézier segments by hand is tedious. The
Diagrams.CubicSpline module provides the cubicSpline
function, which, given a list of points, constructs a smooth curved
path passing through each point in turn. The first argument to
cubicSpline is a boolean value indicating whether the path should be
closed.

> pts = map p2 [(0,0), (2,3), (5,-2), (-4,1), (0,3)]
> dot = circle 0.2 # fc blue # lw 0
> mkPath closed = position (zip pts (repeat dot))
> <> cubicSpline closed pts # lw 0.05
> example = mkPath False ||| strutX 2 ||| mkPath TrueFor more control over the generation of curved paths, see
diagrams-spiro, which provides an FFI binding to the spiro
library (the cubic spline library used by Inkscape).
Fill rules
There are two main algorithms or "rules" used when determining which
areas to fill with color when filling the interior of a path: the
winding rule and the even-odd rule. The rule used to draw a
path-based diagram can be set with fillRule, defined in
Diagrams.TwoD.Path. For simple, non-self-intersecting paths,
determining which points are inside is quite simple, and the two
algorithms give the same results. However, for self-intersecting
paths, they usually result in different regions being filled.

> loopyStar = fc red
> . mconcat . map (cubicSpline True)
> . pathVertices
> . star (StarSkip 3)
> $ regPoly 7 1
> example = loopyStar # fillRule EvenOdd
> ||| strutX 1
> ||| loopyStar # fillRule WindingThe even-odd rule specifies that a point is inside the path if a straight line extended from the point off to infinity (in one direction only) crosses the path an odd number of times. Points with an even number of crossings are outside the path. This rule is simple to implement and works perfectly well for non-self-intersecting paths. For self-intersecting paths, however, it results in a funny pattern of alternatingly filled and unfilled regions, as seen in the above example. Sometimes this pattern is desirable for its own sake.
The winding rule specifies that a point is inside the path if its winding number is nonzero. The winding number measures how many times the path "winds" around the point, and can be intuitively computed as follows: imagine yourself standing at the given point, facing some point on the path. You hold one end of an (infinitely stretchy) rope; the other end of the rope is attached to a train sitting at the point on the path at which you are looking. Now the train begins traveling around the path. As it goes, you keep hold of your end of the rope while standing fixed in place, not turning at all. After the train has completed one circuit around the path, look at the rope: if it is wrapped around you some number of times, you are inside the path; if it is not wrapped around you, you are outside the path. More generally, we say that the number of times the rope is wrapped around you (positive for one direction and negative for the other) is the point's winding number.
Draw a picture of you and the train
For example, if you stand outside a circle looking at a train traveling around it, the rope will move from side to side as the train goes around the circle, but ultimately will return to exactly the state in which it started. If you are standing inside the circle, however, the rope will end up wrapped around you once.
For paths with multiple components, the winding number is simply the sum of the winding numbers for the individual components. This means, for example, that "holes" can be created in shapes using a path component traveling in the opposite direction from the outer path.
This rule does a much better job with self-intersecting paths, and it turns out to be (with some clever optimizations) not much more difficult to implement or inefficient than the even-odd rule.
Clipping
With backends that support clipping, paths can be used to clip other diagrams. Only the portion of a clipped diagram falling inside the clipping path will be drawn. Note that the diagram's envelope is unaffected.

> example = square 3
> # fc green
> # lw 0.05
> # clipBy (square 3.2 # rotateBy (1/10))Altering a diagram's envelope can be accomplished using withEnvelope
(see Envelope-related functions). The view function is also
provided for the special case of setting a diagram's envelope to some
rectangle, often used for the purpose of selecting only a part of a
diagram to be "viewed" in the final output. It takes a point—the
lower-left corner of the viewing rectangle—and the vector from the
lower-left to upper-right corner.

> circles = (c ||| c) === (c ||| c) where c = circle 1 # fc fuchsia
> example = circles # centerXY # view (p2 (-1,-1)) (r2 (1.3, 0.7))Note in the above example how the actual portion of the diagram that
ends up being visible is larger than the specification given to
view—this is because the aspect ratio of the requested output
image does not match the aspect ratio of the rectangle given to
view (and also because of the use of pad by the framework which
renders the user manual examples). If the aspect ratios matched the
viewed portion would be exactly that specified in the call to view.
Text
Text objects, defined in Diagrams.TwoD.Text, can be created
most simply with the text function, which turns a String into a
diagram with (centered) text:

> example = text "Hello world!" <> rect 8 1Text with different alignments can be created using topLeftText,
baselineText, or, more generally, alignedText:

> pt = circle 0.1 # fc red
>
> t1 = pt <> topLeftText "top left" <> rect 8 1
> t2 = pt <> baselineText "baseline" <> rect 8 1
> t3 = pt <> alignedText 0.7 0.5 "(0.7, 0.5)" <> rect 8 1
>
> d1 =/= d2 = d1 === strutY 2 === d2
> example = t1 =/= t2 =/= t3The most important thing to keep in mind when working with text objects is that they take up no space; that is, the envelope for a text object is constantly zero. If we omitted the rectangle from the above example, there would be no output.
Text objects take up no space!
There are two reasons for this. First, computing the size of some
text in a given font is rather complicated, and diagrams cannot
(yet) do it natively. The cairo backend can do it (see below) but we
don't want to tie diagrams to a particular backend.
The second reason is that font size is handled similarly to line
width, so the size of a text object cannot be known at the time of its
creation anyway! (Future versions of diagrams may include some
sort of constraint-solving engine to be able to handle this sort of
situation, but don't hold your breath.) Font size is treated
similarly to line width for a similar reason: we often want disparate
text elements to be the same size, but those text elements may be part
of subdiagrams that have been transformed in various ways.
Note, however, that the cairo backend includes a module
Diagrams.Backend.Cairo.Text with functions for querying font
and text extents, and creating text diagrams that take up an
appropriate amount of space. So it is possible to have
automatically-sized text objects, at the cost of being tied to the
cairo backend and bringing IO into the picture (or being at peace
with some probably-justified uses of unsafePerformIO).
To set the font size, use the fontSize function; the default font
size is (arbitrarily) 1. Remember, however, that the font size is
measured in the final vector space of the diagram, rather than in
the local vector space in effect at the time of the text's creation.
Other attributes of text can be set using font, bold (or, more
generally, fontWeight), italic, and oblique (or, more generally,
fontSlant). Text is colored with the current fill color (see
Color).

> text' s t = text t # fontSize s <> strutY (s * 1.3)
> example = centerXY $
> text' 10 "Hello" # italic
> === text' 5 "there" # bold # font "freeserif"
> === text' 3 "world" # fc greenNative font support
The SVGFonts package
implements native SVG font support for diagrams. Among other things,
it provides its own textSVG function which can be used to convert
text into a path tracing the outline of the text. This may
eventually be merged into the main diagrams codebase; for now, you can
just import it if you need fancier font support.
Images
The Diagrams.TwoD.Image module provides basic support for
including external images in diagrams. Simply use the image
function and specify a file name and size for the image:

> no = (circle 1 <> hrule 2 # rotateBy (1/8))
> # lw 0.2 # lc red
> example = no <> image "manual/static/phone.png" 1.5 1.5Unfortunately, you must specify both a width and a height for each
image. You might hope to be able to specify just a width or just a
height, and have the other dimension computed so as to preserve the
image's aspect ratio. However, there is no way for diagrams to
query an image's aspect ratio until rendering time, but (until such
time as a constraint solver is added) it needs to know the size of the
image when composing it with other subdiagrams. Hence, both
dimensions must be specified, and for the purposes of positioning
relative to other diagrams, the image will be assumed to occupy a
rectangle of the given dimensions.
However, note that the image's aspect ratio will be preserved: if you
specify dimensions that do not match the actual aspect ratio of the
image, blank space will be left in one of the two dimensions to
compensate. If you wish to alter an image's aspect ratio, you can do
so by scaling nonuniformly with scaleX, scaleY, or something
similar.
Currently, the cairo backend can only include images in .png
format, but hopefully this will be expanded in the future. Other
backends may be able to handle other types of external images.
Advanced tools for diagram creation
This section covers some of the more advanced tools provided by the core and standard libraries for constructing diagrams. Most of the content in this section is applicable to diagrams in any vector space, although 2D diagrams are used as illustrations.
Working with envelopes
The Envelope type, defined in
Diagrams.Core.Envelope, encapsulates envelopes
(see envelopes and local vector spaces). Things which have an
associated envelope—including diagrams, segments, trails, and
paths—are instances of the Enveloped type class.
Envelopes are used implicitly when placing diagrams next to each other (see Juxtaposing diagrams) or when aligning diagrams (see Alignment).
The Enveloped class
All objects with an associated envelope are instances of the
Enveloped type class. This includes diagrams, segments, trails, and
paths. Enveloped provides a single method,
> getEnvelope :: Enveloped b => b -> Envelope (V b)which returns the envelope of an object.
In addition, the list type [b] is an instance of Enveloped
whenever b is. The envelope for a list is simply the
combination of all the individual envelopes of the list's
elements—that is, an envelope that contains all of the list
elements. In conjunction with the Transformable instance for lists
(see The Transformable class), this can be used to do things such
as apply an alignment to a list of diagrams considered as a group.
For some examples and an explanation of why this might be useful, see
Delayed composition.
Traces
Envelopes are useful for placing diagrams relative to one another, but they are not particularly useful for finding actual points on the boundary of a diagram. Finding points on the boundary of a diagram can be useful for things like drawing lines or arrows between two shapes, or deciding how to position another diagram relative to a given one.
Every diagram (and, more generally, anything which is an instance of
the Traced type class) has a trace, a function which is like an
"embedded ray tracer" for finding points on the diagram boundary. In
particular, the trace function takes a ray as input (represented by
a base point and a vector indicating the direction) and returns the
positive distance (given as a multiple of the input vector magnitude)
to the nearest point where the ray intersects the diagram.
Normally, a trace is accessed using one of the four functions
traceV, traceP, maxTraceV, and maxTraceP.
traceVtakes as inputs a base pointp, a vectorv, and any instance ofTraced. It looks for intersection points with the given object along the entire line determined bypandv(that is, the line with parametric formp .+^ (t *^ v)), and returns a scalar multiple ofvwhich extends frompto the point of intersection with the smallest parameter, orNothingif there is no such intersection.Intuitively, the result of
traceVextends from the given point to the "closest" intersection, but it must be kept in mind that if any intersection points lie in the direction oppositev, it is really the furthest such point which will be used (that is, the point with the smallest, i.e. most negative, parameter).This behavior is illustrated below. Note that it can be somewhat unintuitive when the base point lies inside the diagram.

> import Data.Maybe (fromMaybe) > > drawV v = (arrowHead <> shaft) # fc black > where > shaft = origin ~~ (origin .+^ v) > arrowHead = eqTriangle 0.1 > # rotateBy (direction v - 1/4) > # translate v > drawTraceV v d > = lc green $ > fromMaybe mempty > ((origin ~~) <$> traceP origin v d) > illustrateTraceV v d = (d <> drawV v <> drawTraceV v d) # showOrigin > > example = hcat' with {sep = 1} > . lw 0.03 > . map (illustrateTraceV (0.5 *^ (1 & 1))) > $ [ circle 1 # translate ((-1.5) & (-1.5)) > , circle 1 > , circle 1 # translate (1.5 & 1.5) > ]tracePworks similarly, except that it returns the point of intersection itself, which lies on the boundary of the object, orNothingif there is no such point.traceVandtracePare related:traceP p v x == Just p'if and only iftraceV p v x == Just (p' .-. p).maxTraceVandmaxTracePare similar totraceVandtraceP, respectively, except they look for the point of intersection with the greatest parameter.
For slightly more low-level access, the Traced class provides the
getTrace method, which can be used to directly access the trace
function for an object. Currently, given inputs p and v, it
returns a scalar s such that s *^ v extends from p to the
intersection point with the smallest parameter. In the future, it may
be extended to return a list of all intersection points instead of
just the smallest; please contact the developers if you would be
interested in this feature.
The below diagram illustrates the use of the traceP function to
identify points on the boundaries of several diagrams.

> import Data.Maybe (mapMaybe)
>
> illustrateTrace d = d <> traceLines
> where
> traceLines = mconcat
> . mapMaybe traceLine
> . iterateN 30 (rotateBy (1/60))
> $ unitX
> traceLine v = (basePt ~~) <$> traceP basePt v d
> basePt = 0 & (-2)
>
> example
> = hcat' with {sep = 1}
> . map illustrateTrace
> $ [ square 1
> , circle 1
> , eqTriangle 1 # rotateBy (-1/4) ||| eqTriangle 1 # rotateBy (1/4)
> ]Of course, diagrams are not the only instance of Traced. Paths are
also Traced, as are trails, segments, and points. Lists and tuples
are Traced as long as all of their components are—the trace for a
list or tuple is the combination of all the element traces.
Named subdiagrams
Although the simple combinatorial approach to composing diagrams can
get you a long way, for many tasks it becomes necessary (or, at least,
much simpler) to have a way to refer to previously placed subdiagrams.
That is, we want a way to give a name to a particular diagram, combine
it with some others, and then later be able to refer back to the the
subdiagram by name. Any diagram can be given a name with the named
function.
The name mechanism described in this section should be considered experimental; it is quite likely to change (in both small and large ways) in future versions of diagrams. Your feedback on the current design is greatly appreciated!
User-defined names
Anything can be used as a name, as long as its type is an instance of
the IsName type class; to be an instance of the IsName class, it
suffices for a type to be an instance of Typeable, Eq, Ord, and
Show. Making a user-defined type an instance of IsName is as
simple as:
> {-# LANGUAGE DeriveDataTypeable #-}
>
> data Foo = Baz | Bar | Wibble
> deriving (Typeable, Eq, Ord, Show)
>
> instance IsName FooThat's it! No method definitions are even needed for the IsName
instance, since toName (the sole method of IsName) has a default
implementation which works just fine.
Listing names
Sometimes you may not be sure what names exist within a diagram—for
example, if you have obtained the diagram from some external module,
or are debugging your own code. The names function extracts a list
of all the names recorded within a diagram and the locations of any
associated subdiagrams.
When using names you will often need to add a type annotation such
as D R2 to its argument, as shown below—for an explanation and
more information, see No instances for Backend b0 R2 ....
ghci> names (circle 1 # named "joe" ||| circle 2 # named "bob" :: D R2)
[("bob",[P (2.9999999999999996 & 0.0)]),("joe",[P (0.0 & 0.0)])]Of course, there is in fact an entire subdiagram (or subdiagrams)
associated with each name, not just a point; but subdiagrams do not
have a Show instance.
Accessing names
Create better tools for extracting subdiagrams directly and write about them here. Should also write about the low-level interface here, I suppose.
Once we have given names to one or more diagrams, what can we do with
them? The primary tool for working with names is withName, which
has the (admittedly scary-looking!) type
> withName :: ( IsName n, AdditiveGroup (Scalar v), Floating (Scalar v)
> , InnerSpace v, HasLinearMap v)
> => n -> (Subdiagram v -> QDiagram b v m -> QDiagram b v m)
> -> (QDiagram b v m -> QDiagram b v m)Let's pick this apart a bit. First, we see that the type n must be
a name type. So far so good. Then there are a bunch of constraints
involving v, but we can ignore those; they just ensure that v is a
vector space with the right properties. So the first argument of
withName is a name—that makes sense. The second argument is a
function of type
> Subdiagram v -> QDiagram b v m -> QDiagram b v mWe can see this function as a transformation on diagrams, except that
it also gets to use some extra information—namely, a
"Subdiagram v", which records the local origin and envelope
associated with the name we pass as the first argument to withName.
Finally, the return type of withName is itself a transformation of
diagrams.
So here's how withName works. Suppose we call it with the arguments
withName n f d. If some subdiagram of d has the name n, then
f is called with the located envelope associated with n
as its first argument, and d itself as its second argument. So we
get to transform d based on information about where the subdiagram
named n is located within it. And what if there is no subdiagram
named n in d? In that case f is ignored, and d is returned
unmodified.
Here's a simple example making use of names to draw a line connecting the centers of two subdiagrams.

> data Foo = Baz | Bar | Wibble
> deriving (Typeable, Eq, Ord, Show)
>
> instance IsName Foo
>
> connect n1 n2
> = withName n1 $ \b1 ->
> withName n2 $ \b2 ->
> atop ((location b1 ~~ location b2) # lc red # lw 0.03)
>
> example = (square 3 # named Baz ||| circle 2.3 # named Bar)
> # connect Baz BarThe connect function takes two names and returns a function from
diagrams to diagrams, which adds a red line connecting the locations
denoted by the two names. Note how the two calls to withName are
chained, and how we have written the second arguments to withName
using lambda expressions (this is a common style). Finally, we draw a
line between the two points (using the location function to access
the base points of the located envelopes), give it a style, and
specify that it should be layered on top of the diagram given as the
third argument to connect.
We then draw a square and a circle, give them names, and use connect
to draw a line between their centers. Of course, in this example, it
would not be too hard to manually compute the endpoints of the line
(this is left as an exercise for the reader); but in more complex
examples such manual calculation can be quite out of the question.
withName also has two other useful variants:
withNameAlltakes a single name and makes available a list of all located envelopes associated with that name. (withName, by contrast, returns only the most recent.) This is useful when you want to work with a collection of named subdiagrams all at once.withNamestakes a list of names, and makes available a list of the most recent located envelopes associated with each. Instead of the two calls towithNamein the example above, we could have written> connect n1 n2 > = withNames [n1,n2] $ \[b1,b2] -> > ...
There is also a function place, which is simply a flipped version of
moveTo, provided for convenience since it can be useful in
conjunction with withName. For example, to draw a square at the
location of a given name, one can write something like
> withName n $ atop . place (square 1) . locationThis computes the location of the name n, positions a square at that
location, and then superimposes the positioned square atop the diagram
containing n.
Subdiagrams
So far, the examples we have seen have only made use of the local
origin associated with each subdiagram, accessed using the location
function. However, subdiagrams are full-fledged diagrams, so there is
much more information to be taken advantage of. For example, the
below code draws a tree of circles, using subdiagram traces (see
Traces) to connect the bottom edge of the parent circle to the
top edge of each child circle, instead of connecting their centers.

> import Data.Maybe (fromMaybe)
>
> root = circle 1 # named "root"
> leaves = centerXY
> . hcat' with {sep = 0.5}
> $ map (\c -> circle 1 # named c) "abcde"
>
> parentToChild child
> = withName "root" $ \rb ->
> withName child $ \cb ->
> atop ( fromMaybe origin (traceP (location rb) unitY rb)
> ~~ fromMaybe origin (traceP (location cb) unit_Y cb))
>
> nodes = root === strutY 2 === leaves
>
> example = nodes # applyAll (map parentToChild "abcde")Explain this example. Also, make some nicer tools so the code isn't so terrible. Reinstate 'boundaryFrom' function?
Qualifying names
To avoid name clashes, sometimes it is useful to be able to qualify
existing names with one or more prefixes. Names actually consist of a
sequence of atomic names, much like Haskell module names consist of
a sequence of identifiers like Diagrams.TwoD.Shapes.
To qualify an existing name, use the (|>) operator, which can be
applied not only to individual names but also to an entire diagram
(resulting in all names in the diagram being qualified). To construct
a qualified name explicitly, separate the components with (.>).

> data Corner = NW | NE | SW | SE
> deriving (Typeable, Eq, Ord, Show)
> instance IsName Corner
>
> connect n1 n2
> = withName n1 $ \b1 ->
> withName n2 $ \b2 ->
> atop ((location b1 ~~ location b2) # lc red # lw 0.03)
>
> squares = (s # named NW ||| s # named NE)
> === (s # named SW ||| s # named SE)
> where s = square 1
>
> d = hcat' with {sep = 0.5} (zipWith (|>) [0::Int ..] (replicate 5 squares))
>
> pairs :: [(Name, Name)]
> pairs = [ ((0::Int) .> NE, (2::Int) .> SW)
> , ((1::Int) .> SE, (4::Int) .> NE)
> , ((3::Int) .> NW, (3::Int) .> SE)
> , ((0::Int) .> SE, (1::Int) .> NW)
> ]
>
> example = d # applyAll (map (uncurry connect) pairs)We create a four-paned square with a name for each of its panes; we then make five copies of it. At this point, each of the copies has the same names, so there would be no way to refer to any of them individually. The solution is to qualify each of the copies differently; here we have used a numeric prefix.
(As an aside, note how we had to use a type annotation on the integers
that we used as names; numeric literals are polymorphic and (|>)
needs to know what type of atomic name we are using. Without the type
annotations, we would get an error about an "ambiguous type variable".
It's a bit annoying to insert all these annotations, of course;
another option would be to use monomorphic constants like Strings
or Chars instead, or to create our own data type with a short
constructor name that wraps an Int.)
Note how we also made use of applyAll, which takes a list of
functions as an argument and composes them into one; that is,
applyAll [f, g, h] === f . g . h.
Using queries
Every diagram has an associated query, which assigns a value to every point in the diagram. These values must be taken from some monoid (see Monoids). Combining two diagrams results in their queries being combined pointwise.
The default query
The default query assigns a value of type Any to each point in a
diagram. In fact, Diagram b v is really a synonym for
QDiagram b v Any. Any represents the monoid on the booleans
with logical or as the binary operation (and hence False as the
identity). The default query simply indicates which points are
"inside" the diagram and which are "outside".
The default Any query and the envelope are quite
different, and may give unrelated results. The envelope
is an approximation used to be able to place diagrams next to one
another; the Any query is a more accurate record of which points
are enclosed by the diagram. (Using the query in order to position
diagrams next to each other more accurately/snugly would be,
generally speaking, computationally infeasible.)
The following example queries an ellipse (using the sample function
to sample it at a set of particular points), coloring points inside
the ellipse red and points outside it blue.

> c :: Diagram Cairo R2
> c = circle 5 # scaleX 2 # rotateBy (1/14) # lw 0.03
>
> -- Generated by fair dice roll, guaranteed to be random
> points = map p2 $
> [ (0.8936218079179525,6.501173563301563)
> , (0.33932828810065985,9.06458375044167)
> , (2.12546952534467,4.603130561299622)
> , (-8.036711369641125,6.741718165576458)
> , (-9.636495308950543,-8.960315063595772)
> , (-5.125008672475815,-4.196763141080737)
> , (-8.740284494124353,-1.748269759118557)
> , (-2.7303729625418782,-9.902752498164773)
> , (-1.6317121405154467,-6.026127282530069)
> , (-3.363167801871896,7.5571909081190825)
> , (5.109759075567126,-5.433154460042715)
> , (8.492015791125596,-9.813023637980223)
> , (7.762080919928849,8.340037921443582)
> , (-6.8589746952056885,3.9604472182691097)
> , (-0.6083773449063301,-3.7738202372565866)
> , (1.3444943726062775,1.1363744735717773)
> , (0.13720748480409384,8.718934659846127)
> , (-5.091010760515928,-8.887260649353266)
> , (-5.828490639105439,-9.392594425007701)
> , (0.7190148020163178,1.4832069771364331)
> ]
>
> mkPoint p = (p, circle 0.3
> # lw 0
> # fc (case sample c p of
> Any True -> red
> Any False -> blue
> )
> )
>
> example = c <> position (map mkPoint points)Using other monoids
You can use monoids besides Any to record other information about a
diagram. For example, the diagram below uses the Sum monoid to draw
dots whose size is determined by the number of overlapping shapes at a
given point. Note the use of the value function to switch from the
default Any to a different monoid: value v replaces Any True with v
and Any False with mempty.

> withCount = (# value (Sum 1))
>
> c :: QDiagram Cairo R2 (Sum Int)
> c = ( circle 5 # scaleX 2 # rotateBy (1/14) # withCount
> <> circle 2 # scaleX 5 # rotateBy (-4/14) # withCount
> )
> # lw 0.03
>
> -- Generated by fair dice roll, guaranteed to be random
> points = map p2 $
> [ (0.8936218079179525,6.501173563301563)
> , (0.33932828810065985,9.06458375044167)
> , (2.12546952534467,4.603130561299622)
> , (-8.036711369641125,6.741718165576458)
> , (-9.636495308950543,-8.960315063595772)
> , (-5.125008672475815,-4.196763141080737)
> , (-8.740284494124353,-1.748269759118557)
> , (-2.7303729625418782,-9.902752498164773)
> , (-1.6317121405154467,-6.026127282530069)
> , (-3.363167801871896,7.5571909081190825)
> , (5.109759075567126,-5.433154460042715)
> , (8.492015791125596,-9.813023637980223)
> , (7.762080919928849,8.340037921443582)
> , (-6.8589746952056885,3.9604472182691097)
> , (-0.6083773449063301,-3.7738202372565866)
> , (1.3444943726062775,1.1363744735717773)
> , (0.13720748480409384,8.718934659846127)
> , (-5.091010760515928,-8.887260649353266)
> , (-5.828490639105439,-9.392594425007701)
> , (0.7190148020163178,1.4832069771364331)
> ]
>
> mkPoint p = (p, circle (case sample c p of
> Sum n -> 2 * fromIntegral n / 5 + 1/5)
> # fc black
> )
>
> example = c # clearValue <> position (map mkPoint points)Notice also the use of clearValue to get rid of the custom query;
the program that builds this documentation requires example to have
the type QDiagram Cairo R2 Any.
As another interesting example, consider using a set monoid to keep track of names or identifiers for the diagrams at a given point. This could be used, say, to identify which element(s) of a diagram have been selected by the user after receiving the coordinates of a mouse click.
Bounding boxes
Expand on this section, based on Michael Sloan's recent refactoring.
Envelopes (see Working with envelopes) are more flexible and compositional than bounding boxes for the purposes of combining diagrams. However, occasionally it is useful for certain applications to be able to work with bounding boxes, which support fast tests for inclusion as well as union and intersection operations (envelopes support union but not inclusion testing or intersection).
To this end, a generic implementation of arbitrary-dimensional
bounding boxes is provided in Diagrams.BoundingBox. Bounding
boxes can be created from sets of points or from any Enveloped
object, used for inclusion or exclusion testing, and combined via
union or intersection.
To obtain a rectangle corresponding to a diagram's bounding box, use
boundingRect.
Scale-invariance
The ScaleInv wrapper can be used to create "scale-invariant"
objects. (Note that ScaleInv is not exported from
Diagrams.Prelude; to use it, import
Diagrams.TwoD.Transform.) In the diagram below, the same
transformation is applied to each pair of arrows; the arrowheads on
the right are wrapped in ScaleInv but the ones on the left are not.

> {-# LANGUAGE TypeSynonymInstances, FlexibleInstances #-}
>
> import Diagrams.TwoD.Transform
>
> class Drawable d where
> draw :: d -> Diagram Cairo R2
>
> instance Drawable (Diagram Cairo R2) where
> draw = id
>
> instance Drawable a => Drawable (ScaleInv a) where
> draw = draw . unScaleInv
>
> instance (Drawable a, Drawable b) => Drawable (a,b) where
> draw (x,y) = draw x <> draw y
>
> arrowhead, shaft :: Diagram Cairo R2
> arrowhead = eqTriangle 0.5 # fc black # rotateBy (-1/4)
> shaft = origin ~~ (3 & 0)
>
> arrow1 = (shaft, arrowhead # translateX 3)
> arrow2 = (shaft, scaleInv arrowhead unitX # translateX 3)
>
> showT tr = draw (arrow1 # transform tr)
> ||| strutX 1
> ||| draw (arrow2 # transform tr)
>
> example = vcat' with {sep = 0.5}
> (map (centerX . showT)
> [ scalingX (1/2)
> , scalingY 2
> , scalingX (1/2) <> rotation (-1/12 :: CircleFrac)
> ])Type reference
This section serves as a reference in understanding the types used in the diagrams framework.
Understanding diagrams types
Let's look again at the type of hcat, mentioned in Types and type
classes:
> hcat :: (Juxtaposable a, HasOrigin a, Monoid' a, V a ~ R2) => [a] -> aThis is fairly typical of the types you will encounter when using diagrams. They can be intimidating at first, but with a little practice they are not hard to read. Let's look at the components of this particular type from right to left:
[a] -> a. This part is simple enough: it denotes a function from a list ofa's to a singlea. Typically, the type to the right of=>will be some simple polymorphic type.V a ~ R2. This is a type equality constraint, which says that the typesV aandR2must be equal. In this caseR2is the type of two-dimensional vectors, andVis a type family which tells us the vector space that corresponds to a particular type. SoV a ~ R2means "the vector space corresponding toamust beR2", or more informally, "amust be a type representing two-dimensional things".Juxtaposable a, ...These are type class constraints ona, specifying what primitive operationsamust support in order to be meaningfully used withhcat. For a complete reference on all the type classes used by diagrams, see the next section, Type class reference.
Type class reference
This section serves as a reference for all the type classes defined or used by diagrams; there are quite a lot. (Some might even say too many!) Most, if not all, of these are also covered elsewhere, but it is useful to have them collected all in one place. The declaration of each type class is shown along with a short explanation, a list of instances, and links to further reading.
Classes for transforming and combining
HasOrigin
HasOrigin is defined in Diagrams.Core.HasOrigin.
> class VectorSpace (V t) => HasOrigin t where
> moveOriginTo :: Point (V t) -> t -> tHasOrigin classifies types with a notion of a fixed "location"
relative to some "local origin", and provides a means of moving the
local origin. This is provided as a separate class from
Transformable since some things with a local origin do not support
other sorts of transformations; and contrariwise some things that
support transformations are translation-invariant (like trails and
vectors) and hence do not have a HasOrigin instance.
The moveOriginTo method moves the local origin to the given
point.
Instances:
The instances for
Point,SubMap,Subdiagram, andQDiagramall have the meaning you would expect.The instances for
Trace,Envelope, andQueryall obey the invariant that, e.g.,getEnvelope . moveOriginTo p t == moveOriginTo p t . getEnvelope. That is, ifeis the envelope/trace/query for diagramd, moving the origin ofetopyields the envelope/trace/query fordwith its origin moved top.Container types can be translated by translating each element (
(a,b),[a],Set,Map).Things wrapped in
TransInvare not supposed to be affected by translation, so theTransInvinstance hasmoveOriginTo = const id.The instance for
Transformationconstructs a translation and composes it appropriately.
Further reading: Alignment.
Transformable
Transformable is defined in Diagrams.Core.Transform.
> class HasLinearMap (V t) => Transformable t where
> transform :: Transformation (V t) -> t -> tIt represents types which support arbitrary affine transformations (or linear transformations, in the case of translationally invariant things).
Prim,SubMap,Subdiagram,QDiagram: these have the meaning you would expect.Of course,
Transformationis itself transformable, by composition.Container types can be transformed by transforming each element (
(t,t),(t,t,t),[t],Set,Map).Point vis transformable whenevervis; translations actually affect points (whereas they might not have an effect on the underlying typev).Anything wrapped in
TransInvwill not be affected by translation.Anything wrapped in
ScaleInvwill not be affected by scaling. See Scale-invariance for more information.Applying a transformation to a
Stylesimply applies it to every attribute.The meaning of transforming an
Attributedepends on the particular attribute.The instances for
Trace,Envelope, andQueryall obey the invariant that, e.g.,getEnvelope . transform t == transform t . getEnvelope. That is, ifeis the envelope/trace/query for diagramd, transformingewithtyields the envelope/trace/query fordtransformed byt.The instance for
Deletablesimply lifts transformations on the underlying type.The instance for
NullPrimdoes nothing, since there is nothing to transform.Uniform scales can be applied to
DoubleandRationalvalues; translations can also be applied but have no effect.
Further reading: Euclidean 2-space; 2D Transformations.
Juxtaposable
Juxtaposable is defined in Diagrams.Core.Juxtapose.
> class Juxtaposable a where
> juxtapose :: V a -> a -> a -> aJuxtaposable represents types of things which can be positioned
"next to" one another. Note that this is more general than "having an
envelope" (though certainly any instance of Enveloped can be made an
instance of Juxtaposable, using juxtaposeDefault). For example,
animations are an instance of Juxtaposable (which corresponds to
juxtaposing them at every point in time), but not of Enveloped.
juxtapose v a1 a2 positions a2 next to a1 in the
direction of v. In particular, it places a2 so that v points
from the local origin of a1 towards the old local origin of
a2; a1's local origin becomes a2's new local origin. The
result is just a translated version of a2. (In particular,
juxtapose does not combine a1 and a2 in any way.)
QDiagramandEnvelopeare of course instances.Many container types are also instances, since container types have
Envelopedinstances that work by superimposing all the envelopes of the individual elements:[a],(a,b),Set,Map
Further reading: Juxtaposing diagrams; Juxtaposing without composing.
Enveloped
Enveloped is defined in Diagrams.Core.Envelope. It
classifies types which have an associated Envelope.
> class (InnerSpace (V a), OrderedField (Scalar (V a))) => Enveloped a where
> getEnvelope :: a -> Envelope (V a)The getEnvelope method simply computes or projects out its
argument's associated Envelope. InnerSpace, defined in
Data.VectorSpace, classifies vector spaces with an inner (dot)
product. Computing envelopes almost always involves projection of one
vector onto another, which requires an inner product. The
OrderedField class is simply a synonym for a collection of classes,
requiring that the scalar type have multiplicative inverses and be
linearly ordered. See OrderedField.
The instance for
QDiagramdoes what you would expect.The instance for
Subdiagramyields an envelope positioned relative to the parent diagram.Every
Pointhas a "point envelope" consisting of the constantly zero envelope translated to the given point. Note this is not the same as the empty envelope.Many container types have instances which work by combining all the envelopes of the individual elements:
[a],(a,b),Set,Map.
Further reading: Envelopes and local vector spaces; Working with envelopes; Envelopes.
Traced
Traced is defined in Diagrams.Core.Trace, and plays a similar
role as Enveloped. Traced types have an associated Trace, which
is like an embedded ray tracer that can be used to find points on the
boundary of an object.
> class (Ord (Scalar (V a)), VectorSpace (V a)) => Traced a where
> getTrace :: a -> Trace (V a)The instance for
QDiagramdoes what you would expect.The instance for
Subdiagramyields a trace positioned relative to the parent diagram.The trace of a
Pointis the empty trace.Many container types have instances which work by combining all the envelopes of the individual elements:
[a],(a,b),Set,Map.
Further reading: Traces.
Classes for attributes and styles
AttributeClass
AttributeClass, defined in Diagrams.Core.Style, is simply a
proxy for Typeable and Semigroup; it has no methods. Any type
used as an attribute must be made a member of this class.
> class (Typeable a, Semigroup a) => AttributeClass aInstances: many; see Diagrams.Attributes and
Diagrams.TwoD.Path.
Further reading: Attributes and styles; Text; Style and attribute internals.
HasStyle
HasStyle, also defined in Diagrams.Core.Style, classifies
things to which a Style can be applied.
> class HasStyle a where
> applyStyle :: Style (V a) -> a -> aapplyStyle applies the given Style to an object, combining it on
the left with the existing Style (according to the Monoid instance
of Style).
Styleitself is an instance.Many container types are instances as long as their elements are; applying a style to a container simply applies the style uniformly to every element:
(a,b),Map k a,Set,[a].Functions
(b -> a)are an instance as long asais. (This can also be thought of as a "container type".)Of course,
QDiagram b v mis an instance, given a few restrictions onvandm.
Further reading: Attributes and styles; Text; Style and attribute internals.
Classes for names
IsName
IsName is defined in Diagrams.Core.Names. It simply provides
the toName method for converting to Name, with a default
implementation that wraps up a value as an atomic name. It allows
values of arbitrary types to be used as names for subdiagrams.
> class (Typeable a, Ord a, Show a) => IsName a where
> toName :: a -> Name
> toName = Name . (:[]) . ANameMany primitive types such as
(),Bool,Char,Int,Float,Double,Integer,String,[a],(a,b),(a,b,c)have a defaultIsNameinstance.ANameis an instance; converting an atomic name toNameworks by creating a singleton list.Nameis an instance, withtoNameas the identity function.
Further reading: Stroking trails and paths; Named subdiagrams; User-defined names.
Qualifiable
Qualifiable is also defined in Diagrams.Core.Names. Instances
of Qualifiable are things which can be "qualified" by prefixing them
with a name.
> class Qualifiable q where
> -- | Qualify with the given name.
> (|>) :: IsName a => a -> q -> qName: qualifying one name with another is just concatenation.SubMapandQDiagram: qualifying prefixes a name on all the existing names.
Further reading: Named subdiagrams; Subdiagrams; Qualifying names.
Classes for paths
PathLike
The PathLike class, defined in Diagrams.Path, abstracts over
things that are "path-like", so that functions such as square can be
used to construct a diagram, a path, a trail, etc..
> class (Monoid' p, VectorSpace (V p)) => PathLike p where
>
> pathLike :: Point (V p) -- ^ The starting point of the
> -- path. Some path-like things
> -- (e.g. 'Trail's) may ignore this.
> -> Bool -- ^ Should the path be closed?
> -> [Segment (V p)] -- ^ Segments of the path.
> -> pThe pathLike method provides a generic way to build a "path-like"
thing by specifying the low-level path data. Note that there should
usually not be any need for end users to call pathLike directly
(though there certainly may be some use cases).
Trail: this is the most "direct" instance (in some sense the class probably should have been named "TrailLike" instead of "PathLike"). However, the starting point is ignored.Path: this instance creates aPathwith a singleTrail, beginning at the given starting point.QDiagram b R2 Any: the diagram obtained by stroking the given path.[Point v]: a list of the path's vertices.Active p(for anyPathLike p): creates a constantActivevalue.
Further reading: Working with paths; Trails; Paths; The PathLike class.
Closable
The Closeable class, also defined in Diagrams.Path,
represents path-like things which can be "open" or "closed".
> class PathLike p => Closeable p where
> -- | "Open" a path-like thing.
> open :: p -> p
>
> -- | "Close" a path-like thing, by implicitly connecting the
> -- endpoint(s) back to the starting point(s).
> close :: p -> pInstances: Trail and Path.
Further reading: Working with paths; The Closeable class.
Classes for backends
Backend
The Backend class, defined in Diagrams.Core.Types, defines
the primary interface for any diagrams rendering backend. Unlike many
of the other type classes in diagrams, it is quite large. For a full
discussion, see The Backend class.
MultiBackend
MultiBackend, also defined in Diagrams.Core.Types, is for
backends which support rendering multiple diagrams, for example to a
multi-page pdf or something similar. It simply provides the
renderDias function for rendering multiple diagrams at once; the
meaning of this function depends on the backend.
> class Backend b v => MultiBackend b v where
> renderDias :: (InnerSpace v, OrderedField (Scalar v), Monoid' m)
> => b -> Options b v -> [QDiagram b v m] -> Result b vSo far, the only backend which supports multi-diagram rendering is the postscript backend.
Further reading: Rendering backends; Backends.
Renderable
The Renderable type class (from Diagrams.Core.Types) is a
two-parameter type class connecting backends to primitives which they
know how to render. Backend B declares that it knows how to draw
primitive P by giving a Renderable P B instance, which requires
implementing the render function which takes a primitive and renders
it.
> class Transformable t => Renderable t b where
> render :: b -> t -> Render b (V t)Instances: There are many instances defined by each backend.
Further reading: Rendering backends.
Poor man's type synonyms
There are several cases where a certain set of type class constraints
are used together so often that it is convenient to define a synonym
to stand in for the entire set of constraints. In more recent
versions of GHC that support the ConstraintKinds extension, this
could be accomplished with a simple type synonym. However, since
diagrams still supports older versions of GHC, these are declared as a
new type class with no methods and a single universal instance. For
example,
> class (Class1 a, Class2 a, Class3 a) => Synonym a
> instance (Class1 a, Class2 a, Class3 a) => Synonym aIdeally, at some point in the future diagrams will drop support for
versions of GHC without ConstraintKinds and switch to the more
sensible way of defining constraint synonyms.
Monoid'
Monoid' m is a synonym for
(Semigroup m, Monoid m),
defined in Diagrams.Core. This is something of an unfortunate
hack: although every monoid is a semigroup mathematically speaking,
Semigroup is not actually a superclass of Monoid, so if we want to
use both we have to actually declare both.
HasLinearMap
HasLinearMap v is a synonym for
(HasBasis v, HasTrie (Basis v), VectorSpace v),
all of which are necessary for constructing linear maps over the
vector space v.
OrderedField
OrderedField s, defined in Diagrams.Core.Envelope, is a
synonym for
(Fractional s, Floating s, Ord s, AdditiveGroup s),
i.e. a floating-point type which is totally ordered. When dealing
with Envelopes it's often necessary to have scalars which support
all four arithmetic operations as well as square root, and can be
compared for ordering.
Type family reference
V
The V type family is defined in Diagrams.Core.V. The idea is that
many types have an "associated" vector space, i.e. the vector space
in which they "live". V simply maps from types to their associated
vector space.
Render
Render is an associated data family of the Backend class. It
determines the type of rendering operations for a given backend. For
more information, see The Backend class.
Result
Result is an associated type family of the Backend class. It
determines the type of the final result obtained from the backend
after rendering a complete diagram. For more information, see The
Backend class.
Options
Options is an associated data family of the Backend class. It
determines the type of options which can be passed to the backend when
initiating a rendering operation. For more information, see The
Backend class.
Tips and tricks
Using absolute coordinates
Diagrams tries to make it easy to construct many types of graphics while thinking in only "relative" terms: put this to the right of that; lay these out in a row; draw this wherever that other thing ended up; and so on. Sometimes, however, this is not enough, and one really wants to just think in absolute coordinates: draw this here, draw that there. If you find yourself wanting this, here are some tips:
The
positionfunction takes a list of diagrams associated with positions and combines them while placing them at the indicated absolute positions.
moveTocan be used to position a single diagram absolutely.
More tips?
Delayed composition
Suppose we have four diagrams that we want to lay out relative to one another. For example:

> t = eqTriangle 5 # fc orange
> s = square 3 # fc red
> o = ellipseXY 2 3 # fc blue
> c = circle 2 # fc green
>
> d = centerX (t ||| s ||| o ||| c)
>
> example = d(Instead of (|||) we could equivalently have used hcat.) Now
d is the diagram consisting of these four shapes laid out in a
centered row.
But what if we want to do further processing on the individual shapes?
At this point, we are out of luck. There is no way to break apart a
diagram into subdiagrams once it has been composed together (for good
reasons). We could use juxtapose (which positions one diagram
relative to another without actually doing any composition) but that
would get ugly and unintuitive.
Here is where the nifty trick comes in: simply enclose each shape in a list, like so:
> ds = centerX ([t] ||| [s] ||| [o] ||| [c])Now ds is a list of four diagrams, which are the same as the
original t, s, o, c except that they have been positioned as
they would be in d! We can now go on to do other things with them
individually. For example, we could alter their positions slightly
before composing them (e.g. this makes for an easy way to apply some
random "jitter" to a layout):

> t = eqTriangle 5 # fc orange
> s = square 3 # fc red
> o = ellipseXY 2 3 # fc blue
> c = circle 2 # fc green
>
> ds = centerX ([t] ||| [s] ||| [o] ||| [c])
> d' = mconcat $ zipWith translateY [0.5, -0.6, 0, 0.4] ds
>
> example = d'In other words, enclosing diagrams in a list allows them to be positioned, aligned, etc. as they normally would, except that it delays actually composing them!
This works because lists are instances of Juxtaposable, Alignable,
Enveloped, HasOrigin, HasStyle, Transformable, and, of course,
Monoid. All these instances work in the "obvious" way—for
example, the envelope for a list is the combination of the envelopes
of the elements—so applying an operation to a list of diagrams has
the same effect as applying the operation to the composition of those
diagrams. In other words, operations such as centerX, scale,
juxtapose, etc. all commute with mconcat.
Naming vertices
Most functions that create some sort of shape (e.g. square,
pentagon, polygon...) can in fact create any instance of the
PathLike class (see The PathLike class). You can often
take advantage of this to do some custom processing of shapes by
creating a path instead of a diagram, doing some processing, and
then turning the path into a diagram.
In particular, assigning names to the vertices of a shape can be
accomplished as follows. Instead of writing just (say) pentagon, write
> stroke' with { vertexNames = [[0..]] } pentagonwhich assigns consecutive numbers to the vertices of the pentagon.
Deciphering error messages
Although making diagrams an embedded domain specific language
has many benefits, it also has (at least) one major downside:
difficult-to-understand error messages. Interpreting error messages
often requires understanding particular details about the internals of
the diagrams framework as well as the particular behavior of GHC.
This section attempts to make the situation a bit more palatable by
explaining a few common types of error message you might get while
using diagrams, along with some suggestions as to their likely
causes and solutions.
This section is certainly incomplete; please send examples of other error messages to the diagrams mailing list for help interpreting them and/or so they can be added to this section.
Couldn't match type V P2 with R2
This error is due to what appears to be a bug in recent versions of
GHC. For some reason the definition of the V type family for points
is not exported. To solve this you can add an explicit import of the
form import Diagrams.Core.Points to the top of your
file.
No instances for Backend b0 R2 ...
There will probably come a time when you get an error message such as
<interactive>:1:8:
No instances for (Backend b0 R2,
Renderable Diagrams.TwoD.Ellipse.Ellipse b0)
arising from a use of `circle'The problem really has nothing to do with missing instances, but with
the fact that a concrete backend type has not been filled in for b0
(or whatever type variable shows up in the error message). Such
errors arise when you pass a diagram to a function which is
polymorphic in its input but monomorphic in its output, such as
width, height, phantom, or names. Such functions compute some
property of the diagram, or use it to accomplish some other purpose,
but do not result in the diagram being rendered. If the diagram does
not have a monomorphic type, GHC complains that it cannot determine
the diagram's type.
For example, here is the error we get if we try to compute the width of a radius-1 circle:
ghci> width (circle 1)
<interactive>:1:8:
No instances for (Backend b0 R2,
Renderable Diagrams.TwoD.Ellipse.Ellipse b0)
arising from a use of `circle'
Possible fix:
add instance declarations for
(Backend b0 R2, Renderable Diagrams.TwoD.Ellipse.Ellipse b0)
In the first argument of `width', namely `(circle 1)'
In the expression: width (circle 1)
In an equation for `it': it = width (circle 1)GHC complains that it cannot find an instance for "Backend b0
R2"; what is really going on is that it does not have enough
information to decide which backend to use for the circle (hence
the type variable b0). This is annoying because we know that
the choice of backend cannot possibly affect the width of the
circle; but there is no way for GHC to know that.
The special type D is provided for exactly this situation, defined as
> type D v = Diagram NullBackend vNullBackend is a "backend" which simply does nothing: perfect
for use in cases where GHC insists on knowing what backend to use but
the backend really does not matter.
For exapmle, the solution to the problem with width is to annotate
circle 1 with the type D R2, like so:
ghci> width (circle 1 :: D R2)
2.0More ambiguity
You may also see error messages that directly complain about ambiguity. For example, the code below is taken from the example in the section on Qualifying names:
> hcat' with {sep = 0.5} (zipWith (|>) [0 .. ] (replicate 5 squares))It is an attempt to qualify the names in five copies of squares with
the numbers 0, 1, 2, ... However, it generates the error shown below:
Ambiguous type variable `a0' in the constraints:
(IsName a0) arising from a use of `|>'
at /tmp/Diagram2499.lhs:13:39-42
(Num a0) arising from the literal `0' at /tmp/Diagram2499.lhs:13:45
(Enum a0) arising from the arithmetic sequence `0 .. '
at /tmp/Diagram2499.lhs:13:44-49
Probable fix: add a type signature that fixes these type variable(s)
In the first argument of `zipWith', namely `(|>)'
In the second argument of `hcat'', namely
`(zipWith (|>) [0 .. ] (replicate 5 squares))'
In the expression:
hcat'
(with {sep = 0.5}) (zipWith (|>) [0 .. ] (replicate 5 squares))The problem, again, is that GHC does not know what type to choose for
some polymorphic value. Here, the polymorphic values in question are
the numbers 0, 1, ... Numeric literals are polymorphic in Haskell,
so GHC does not know whether they should be Ints or Integers or
Doubles or... The solution is to annotate the 0 with the desired
type.
Animation
As of version 0.5, diagrams has experimental support for the creation of animations. It is by no means complete, so bug reports and feature requests are especially welcome.
Animations are created with the help of a generic Active
abstraction, defined in the active package.
Active
The active package defines a simple abstraction for working
with time-varying values. A value of type Active a is either a
constant value of type a, or a time-varying value of type a
(i.e. a function from time to a) with specific start and end
times. Since active values have start and end times, they can be
aligned, sequenced, stretched, or reversed. In a sense, this is sort
of like a stripped-down version of functional reactive programming
(FRP), without the reactivity.
There are two basic ways to create an Active value. The first is to
use mkActive to create one directly, by specifying a start and end
time and a function of time. More indirectly, one can use the
Applicative instance for Active together with the "unit interval"
ui, which takes on values from the unit interval from time 0 to time
1, or interval, which is like ui but over an arbitrary interval.
For example, to create a value of type Active Double which represents
one period of a sine wave starting at time 0 and ending at time 1, we
could write
> mkActive 0 1 (\t -> sin (fromTime t * tau))or
> (sin . (*tau)) <$> uipure can also be used to create Active values which are constant
and have no start or end time. For example,
> mod <$> (floor <$> interval 0 100) <*> pure 7cycles repeatedly through the numbers 0-6.
To take a "snapshot" of an active value at a particular point in time,
the runActive function can be used to turn one into a function of
time. For example,
> runActive ((sin . (*tau)) <$> ui) $ 0.2
0.9510565162951535Write more about using the active library. For now, you can read the package documentation for more information.
Transforming active values
Combining active values
Using Active with diagrams
An animation is defined, simply, as something of type
Active (Diagram b v) for an appropriate backend type b and vector
space v. Hence it is possible to make an animation by using the
mkActive function and specifying a function from time to diagrams.
However, most often, animations are constructed using the
Applicative interface. For example, to create a moving circle we
can write
> translateX <$> ui <*> circle 2diagrams-cairo includes a very primitive animation rendering
function, animMain, which takes an animation and spits out a bunch
of image files, one for each frame. You can then assemble the
generated frames into an animation using, e.g., ffmpeg. (More
sophisticated animation rendering will be added in future releases.)
If you use animMain to visualize the above animation, however, you
will find that all the generated frames look the same—the circle is
not moving!
Actually, it is moving, it's just that it gets centered in the output at each instant. It's as if the viewport is panning along at the same rate as the circle, with the result that it appears stationary. The way to fix this is by placing the moving circle on top of something larger and stationary in order to "fix" the viewpoint. Let's use an invisible square:
> (translateX <$> ui <*> circle 2) <> (pure (square 6 # lw 0))Notice that we composed two animations using (<>), which does
exactly what you would think: superimposes them at every instant in time.
Since this is such a common thing to want, the
Diagrams.Animation module provides a function animEnvelope
for expanding the envelope of an animation to the union of all the
envelopes over time (determined by sampling at a number of points). That
is, the animation will now use a constant envelope that encloses the
entirety of the animation at all points in time.
> animEnvelope (translateX <$> ui <*> circle 2)Since Active is generic, it is also easy (and useful) to
create active Points, Paths, colors, or values of any other type.
Examples of animating things other than diagrams
Rendering backends
The SVG backend
The cairo backend
The postscript backend
Other backends
For a list of other backends and their status, see the diagrams wiki.
Write a bit about each of these backends.
HTML5 canvas
TikZ
povray
OpenGL?
Tools for backends
lots more stuff goes in this section
Diagrams.Segment exports a FixedSegment type, representing
segments which do have an inherent starting location. Trails and
paths can be "compiled" into lists of FixedSegments with absolute
locations using fixTrail and fixPath. This is of interest to
authors of rendering backends that do not support relative drawing
commands.
Core library
This chapter explains the low-level inner workings of
diagrams-core. Casual users of diagrams should not need to
read this section (although a quick skim may well turn up something
interesting). It is intended more for developers and power users who
want to learn how diagrams actually works under the hood.
This section may not get written for a while; yell if you'd like to read it. The more people who yell, the faster it will get done. =)
Haskell drawing framework




