This tutorial will walk you through the basics of using the diagrams DSL to create graphics in a powerful, modular, and declarative way. There's enough here to get you started quickly; for more in-depth information, see the user manual.

This is not a Haskell tutorial (although a Haskell-tutorial-via-diagrams is a fun idea and may happen in the future). For now, we recommend Learn You a Haskell for a nice introduction to Haskell; Chapters 1-6 should give you pretty much all you need for working with diagrams.

Some resources that may be helpful to you as you learn about diagrams:

The user manual

The

`#diagrams`

IRC channel on Libera.Chat

Before getting on with generating beautiful diagrams, you'll need a few things:

You'll need a recent version of the Glasgow Haskell Compiler (8.4 or later), as well as the cabal-install tool. If you do not already have these, we recommend installing them via ghcup.

You can also easily work with diagrams using stack, though that is not covered in these instructions.

No special installation is needed — the necessary `diagrams`

packages will be automatically downloaded and installed for you by
`cabal`

or `stack`

once you create a project. However, it's worth
mentioning which packages you will likely need:

`diagrams-core`

contains the core data structures and definitions that form the abstract heart of the library.`diagrams-lib`

is a standard library of drawing primitives, attributes, and combinators built on top of the core library.`diagrams-contrib`

is a library of user-contributed extensions.`diagrams-svg`

is a backend which renders diagrams as SVG files.

There are other backends as well; see the diagrams package documentation and the diagrams wiki for more information.

Before diving in to create some diagrams, it's worth taking a minute to explain some of the philosophy that drove many of the design decisions. (If you're particularly impatient, feel free to skip this section for now—but you might want to come back and read it later!)

Positioning and scaling are always

*relative*. There is never any global coordinate system to think about; everything is done relative to diagrams'*local*vector spaces. This is not only easier to think about, it also increases modularity and compositionality, since diagrams can always be designed without thought for the context in which they will eventually be used. Doing things this way is more work for the*library*and less work for the*user*, which is the way it should be.Almost everything is based around the concept of

*monoids*(more on this later).The core library is as simple and elegant as possible—almost everything is built up from a very small set of primitive types and operations. One consequence is that diagrams is optimized for simplicity and flexibility rather than for speed; if you are looking to do

*real-time*graphics generation you will probably be best served by looking elsewhere! (With that said, however, we certainly are interested in making diagrams as fast as possible without sacrificing other features, and there have been several cases of people successfully using diagrams for simple real-time graphics generation.)

Create a file called `DiagramsTutorial.lhs`

with the following contents:

```
> {-# LANGUAGE NoMonomorphismRestriction #-}
> {-# LANGUAGE FlexibleContexts #-}
> {-# LANGUAGE TypeFamilies #-}
>
> import Diagrams.Prelude
> import Diagrams.Backend.SVG.CmdLine
>
> myCircle :: Diagram B
> myCircle = circle 1
>
> main = mainWith myCircle
```

Turning off the Dreaded Monomorphism Restriction is quite important: if you don't, you will almost certainly run into it (and be very confused by the resulting error messages). The other two extensions are not needed for this simple example in particular, but are often required by diagrams in general, so it doesn't hurt to include them as a matter of course.

This tutorial assumes the latest version of `diagrams`

(namely,
1.4). If you get an error message saying ```
Expecting one more
argument to 'Diagram B'
```

, it means you have an older (1.2 or older)
version of `diagrams`

installed. We recommend upgrading to the
latest version.

The first `import`

statement brings into scope the entire diagrams DSL
and standard library, as well as a few things from other libraries
re-exported for convenience. The second `import`

is so that we can
use the SVG backend for rendering diagrams. Among other things, it
provides the function `mainWith`

, which takes a diagram as input (in
this case, a circle of radius 1) and creates a command-line-driven
application for rendering it.

To be able to compile and run this code, we'll create a simple
`.cabal`

file which specifies its dependencies. Create a file
called `diagrams-tutorial.cabal`

with the following contents:

```
cabal-version: 2.4
name: diagrams-tutorial
version: 0.1.0.0
executable diagrams-tutorial
main-is: DiagramsTutorial.lhs
build-depends: base, diagrams-lib, diagrams-svg
default-language: Haskell2010
```

You should now be able to build and run the example using the following commands:

```
$ cabal build
... lots of output while it downloads and builds all the dependencies ...
$ cabal exec diagrams-tutorial -- -o circle.svg -w 400
```

If you now view `circle.svg`

in your favorite web browser, you should
see an unfilled black circle on a white background (actually, it's on
a transparent background, but most browsers use white):

Be careful not to omit the `-w 400`

argument! This specifies that the
width of the output file should be 400 units, and the height should
be determined automatically. You can also specify just a height
(using `-h`

), or both a width and a height if you know the exact
dimensions of the output image you want (note that the diagram will
not be stretched; extra padding will be added if the aspect ratios do
not match). If you do not specify a width or a height, the absolute
scale of the diagram itself will be used, which in this case would be
rather tiny—only 2x2.

There are several more options besides `-o`

, `-w`

, and `-h`

; you can
see what they are by typing `cabal exec diagrams-tutorial -- --help`

. One
particularly useful option is `-l`

, which puts the program into "looped
mode": it will watch the source file for changes, and then
automatically recompile and rerun itself, like this (note that you may
need to specify the source file explicitly using `-s`

, as shown here):

```
$ cabal exec diagrams-tutorial -- -o circle.svg -w 400 -l -s DiagramsTutorial.lhs
Looping turned on
Watching source file DiagramsTutorial.lhs
Compiling target: DiagramsTutorial
Program args: -o circle.svg -w 400 -s DiagramsTutorial.lhs
Modified 02:41:42 ... compiling ... running ... done.
Modified 02:41:50 ... compiling ... running ... done.
```

With looped mode, you only need to edit and save the source code, then reload the image in your browser or image viewer.

The `mainWith`

function is also quite a bit more general than
accepting just a diagram: it can accept animations, lists of diagrams,
association lists of names and diagrams, or functions producing any of
the above. For more information, see the diagrams command-line
creation tutorial.

A few miscellaneous notes:

Diagrams does not require the use of literate Haskell (

`.lhs`

) files; normal`.hs`

files work perfectly well. However, we suggest using`.lhs`

while following diagrams tutorials, since you will be able to easily copy and paste sections of text and code from the tutorial page into your editor without reformatting it.The type signature on

`myCircle :: Diagram B`

is needed to inform the diagrams framework which backend you intend to use for rendering (every backend exports`B`

as a synonym for itself). Without the type signature, you are likely to get type errors about ambiguous type variables. You can often get away with putting just one type signature on the final diagram to be rendered, and letting GHC infer the rest, though including more type signatures can also be helpful.

Suppose we want our circle to be blue, with a thick dashed purple
outline (there's no accounting for taste!). We can apply attributes to
the `circle`

diagram with the `(#)`

operator:

You may need to include a type signature to build the examples that
follow. We omit `example :: Diagram B`

in the examples below.

```
> example = circle 1 # fc blue
> # lw veryThick
> # lc purple
> # dashingG [0.2,0.05] 0
```

There's actually nothing special about the `(#)`

operator: it's just
reverse function application, that is,

`> x # f = f x`

Just to illustrate,

```
> example = dashingG [0.2,0.05] 0 . lc purple . lw veryThick . fc blue
> $ circle 1
```

produces exactly the same diagram as before. So why bother with
`(#)`

? First, it's often more natural to write (and easier to read)
what a diagram *is* first, and what it is *like* second. Also,
`(#)`

has a high precedence (namely, 8), making it more convenient to
combine diagrams with specified attributes. For example,

`> example = circle 1 # fc red # lw none ||| circle 1 # fc green # lw none`

places a red circle with no border next to a green circle with no
border (we'll see more about the `(|||)`

operator shortly). Without
`(#)`

we would have to write something with more parentheses, like

`> (fc red . lw none $ circle 1) ||| (fc green . lw none $ circle 1)`

For information on other standard attributes, see the
`Diagrams.Attributes`

and `Diagrams.TwoD.Attributes`

modules.

OK, so we can draw a single circle: boring! Much of the power of the
diagrams framework, of course, comes from the ability to build up
complex diagrams by *combining* simpler ones.

Let's start with the most basic way of combining two diagrams:
superimposing one diagram on top of another. We can accomplish this
with `atop`

:

`> example = square 1 # fc aqua `atop` circle 1`

(Incidentally, these colors are coming from the
`Data.Colour.Names`

module.)

"Putting one thing on top of another" sounds rather vague: how do we
know exactly where the circle and square will end up relative to one
another? To answer this question, we must introduce the fundamental
notion of a *local origin*.

Every diagram has a distinguished point called its *local origin*.
Many operations on diagrams—such as `atop`

—work somehow with
respect to the local origin. `atop`

in particular works by
superimposing two diagrams so that their local origins coincide (and
this point becomes the local origin of the new, combined diagram).

The `showOrigin`

function is provided for conveniently visualizing the
local origin of a diagram.

`> example = circle 1 # showOrigin`

Not surprisingly, the local origin of `circle`

is at its center. So
is the local origin of `square`

. This is why `square 1 `atop` circle 1`

produces a square centered on a circle.

Another fundamental way to combine two diagrams is by placing them
*next to* each other. The `(|||)`

and `(===)`

operators let us
conveniently put two diagrams next to each other in the horizontal or
vertical directions, respectively. For example, horizontal:

`> example = circle 1 ||| square 2`

and vertical:

`> example = circle 1 === square 2`

The two diagrams are arranged next to each other so that their local
origins are on the same horizontal or vertical line. As you can
ascertain for yourself with `showOrigin`

, the local origin of the new,
combined diagram coincides with the local origin of the first diagram.

The `hcat`

and `vcat`

functions are provided for laying out an entire
*list* of diagrams horizontally or vertically:

```
> circles = hcat (map circle [1..6])
> example = vcat (replicate 3 circles)
```

See also `hsep`

and `vsep`

for including space in between subsequent
diagrams.

`(|||)`

and `(===)`

are actually just convenient specializations of
the more general `beside`

combinator. `beside`

takes as arguments a
*vector* and two diagrams, and places them next to each other "along
the vector"—that is, in such a way that the vector points from the
local origin of the first diagram to the local origin of the second.

```
> circleSqV1 = beside (r2 (1,1)) (circle 1) (square 2)
>
> circleSqV2 = beside (r2 (1,-2)) (circle 1) (square 2)
>
> example = hcat [circleSqV1, strutX 1, circleSqV2]
```

Notice how we use the `r2`

function to create a 2D vector from a pair
of coordinates; see the vectors and points tutorial for more.

How does the diagrams library figure out how to place two diagrams "next to" each other? And what exactly does "next to" mean? There are many possible definitions of "next to" that one could imagine choosing, with varying degrees of flexibility, simplicity, and tractability. The definition of "next to" adopted by diagrams is as follows:

To place two diagrams next to each other in the direction
of a vector *v*, place them as close as possible so that there is a
*separating line* perpendicular to *v*; that is, a line perpendicular
to *v* such that the first diagram lies completely on one side of the
line and the other diagram lies completely on the other side.

There are certainly some tradeoffs in this choice. The biggest
downside is that adjacent diagrams sometimes end up with undesired
space in between them. For example, the two rotated ellipses in the
diagram below have some space between them. (Try adding a vertical
line between them with `vrule`

and you will see why.)

```
> example = ell ||| ell
> where ell = circle 1 # scaleX 0.5 # rotateBy (1/6)
```

However:

This rule is very

*simple*, in that it is easy to predict what will happen when placing two diagrams next to each other.It is also

*tractable*. Every diagram carries along with it an "envelope"—a function which takes as input a vector*v*, and returns the minimum distance to a separating line from the local origin in the direction of*v*. When composing two diagrams with`atop`

we take the pointwise maximum of their envelopes; to place two diagrams next to each other we use their envelopes to decide how to reposition their local origins before composing them with`atop`

.

Happily, in this particular case, it *is* possible to place the
ellipses tangent to one another (though this solution is not quite as
general as one might hope):

```
> example = ell # snugR <> ell # snugL
> where ell = circle 1 # scaleX 0.5 # rotateBy (1/6)
```

The `snug`

class of functions use diagrams' *trace* (something like an
embedded raytracer) rather than their envelope. (For more information,
see `Diagrams.TwoD.Align`

and the user manual section on
traces.)

As you would expect, there is a range of standard functions available for transforming diagrams, such as:

`scale`

(scale uniformly)`scaleX`

and`scaleY`

(scale in the X or Y axis only)`rotate`

(rotate by an Angle)`rotateBy`

(rotate by a fraction of a circle)`reflectX`

and`reflectY`

for reflecting along the X and Y axes

For example:

```
> circleRect = circle 1 # scale 0.5 ||| square 1 # scaleX 0.3
>
> circleRect2 = circle 1 # scale 0.5 ||| square 1 # scaleX 0.3
> # rotateBy (1/6)
> # scaleX 0.5
>
> example = hcat [circleRect, strutX 1, circleRect2]
```

(Of course, `circle 1 # scale 0.5`

would be better written as just `circle 0.5`

.)

Of course, there are also translation transformations like
`translate`

, `translateX`

, and `translateY`

. These operations
translate a diagram within its *local vector space*—that is,
relative to its local origin.

`> example = circle 1 # translate (r2 (0.5, 0.3)) # showOrigin`

As the above example shows, translating a diagram by `(0.5, 0.3)`

is
the same as moving its local origin by `(-0.5, -0.3)`

.

Since diagrams are always composed with respect to their local origins, translation can affect the way diagrams are composed.

```
> circleSqT = square 1 `atop` circle 1 # translate (r2 (0.5, 0.3))
> circleSqHT = square 1 ||| circle 1 # translate (r2 (0.5, 0.3))
> circleSqHT2 = square 1 ||| circle 1 # translate (r2 (19.5, 0.3))
>
> example = hcat [circleSqT, strutX 1, circleSqHT, strutX 1, circleSqHT2]
```

As `circleSqHT`

and `circleSqHT2`

demonstrate, when we place a
translated circle next to a square, it doesn't matter how much the
circle was translated in the *horizontal* direction—the square and
circle will always simply be placed next to each other. The vertical
direction matters, though, since the local origins of the square and
circle are placed on the same horizontal line.

It's quite common to want to *align* some diagrams in a certain way
when placing them next to one another—for example, we might want a
horizontal row of diagrams aligned along their top edges. The
*alignment* of a diagram simply refers to its position relative to its
local origin, and convenient alignment functions are provided for
aligning a diagram with respect to its envelope. For example,
`alignT`

translates a diagram in a vertical direction so that its
local origin ends up exactly on the edge of its envelope.

```
> example = hrule (2 * sum sizes) === circles # centerX
> where circles = hcat . map alignT . zipWith scale sizes
> $ repeat (circle 1)
> sizes = [2,5,4,7,1,3]
```

See `Diagrams.TwoD.Align`

for other alignment combinators.

As you may have already suspected if you are familiar with monoids,
diagrams form a monoid under `atop`

. This means that you can use
`(<>)`

instead of `atop`

to superimpose two diagrams. It also means
that `mempty`

is available to construct the "empty diagram", which
takes up no space and produces no output.

Quite a few other things in the diagrams standard library are also monoids (transformations, trails, paths, styles, colors, envelopes, traces...).

As a way of exhibiting a complete example and introducing some additional features of diagrams, consider trying to draw the following picture:

This features a hexagonal arrangement of numbered nodes, with an arrow from node \(i\) to node \(j\) whenever \(i < j\). While we're at it, we might as well make our program generic in the number of nodes, so it generates a whole family of similar diagrams.

The first thing to do is place the nodes. We can use the `regPoly`

function to produce a regular polygon with sides of a given length. (In
this case we want to hold the side length constant, rather than the
radius, so that we can simply make the nodes a fixed size. To create
polygons with a fixed radius as well as many other types of polygons,
use the `polygon`

function.)

`> example = regPoly 6 1`

However, `regPoly`

(and most other functions for describing shapes)
can be used to produce not just a diagram, but also a *trail* or
*path*. Loosely speaking, trails are purely geometric,
one-dimensional tracks through space, and paths are collections of
trails; see the tutorial on trails and paths for a more detailed
account. Trails and paths can be explicitly manipulated and computed
with, and used, for example, to describe and position other
diagrams. In this case, we can use the `trailVertices`

and `atPoints`

functions to
place nodes at the vertices of the trail produced by `regPoly`

:

```
> node = circle 0.2 # fc green
> example = atPoints (trailVertices $ regPoly 6 1) (repeat node)
```

As a next step, we can add text labels to the nodes. For quick and
dirty text, we can use the `text`

function provided by
`diagrams-lib`

. (For more sophisticated text support, see the
`SVGFonts`

package.) While we are at it, we also abstract over
the number of nodes:

```
> node :: Int -> Diagram B
> node n = text (show n) # fontSizeL 0.2 # fc white <> circle 0.2 # fc green
>
> tournament :: Int -> Diagram B
> tournament n = atPoints (trailVertices $ regPoly n 1) (map node [1..n])
>
> example = tournament 5
```

Note the use of the type `B`

, which is exported by every backend as a
synonym for its particular backend type tag. This makes it easier to
switch between backends while still giving explicit type signatures for
your code: in contrast to a type like `Diagram SVG`

which is
explicitly tied to a particular backend and would have to be changed
when switching to a different backend, the `B`

in `Diagram B`

will
get instantiated to whichever backend happens to be in scope.

Our final task is to connect the nodes with arrows. First, in order
to specify the parts of the diagram between which arrows should be
drawn, we need to give *names* to the nodes, using the `named`

function:

```
> node :: Int -> Diagram B
> node n = text (show n) # fontSizeL 0.2 # fc white
> <> circle 0.2 # fc green # named n
>
> tournament :: Int -> Diagram B
> tournament n = atPoints (trailVertices $ regPoly n 1) (map node [1..n])
```

Note the addition of `... # named n`

to the circles making up the nodes.
This doesn't yet change the picture in any way, but it sets us up to
describe arrows between the nodes. We can use values of arbitrary
type (subject to a few restrictions) as names; in this case the
obvious choice is the `Int`

values corresponding to the nodes
themselves. (See the user manual section on named subdiagrams for
more.)

The `Diagrams.TwoD.Arrow`

module provides a number of tools for
drawing arrows (see also the user manual section on arrows and the
arrow tutorial). In this case, we can use the `connectOutside`

function to draw an arrow between the outer edges of two named
objects. Here we connect nodes 1 and 2:

```
> node :: Int -> Diagram B
> node n = text (show n) # fontSizeL 0.2 # fc white
> <> circle 0.2 # fc green # named n
>
> tournament :: Int -> Diagram B
> tournament n = atPoints (trailVertices $ regPoly n 1) (map node [1..n])
>
> example = tournament 6 # connectOutside (1 :: Int) (2 :: Int)
```

(The type annotations on `1`

and `2`

are necessary since numeric
literals are polymorphic and we can use names of any type.)

This won't do, however; we want to leave some space between the nodes and the
ends of the arrows, and to use a slightly larger arrowhead. Fortunately, the
arrow-drawing code is highly configurable. Instead of
`connectOutside`

we can use its sibling function `connectOutside'`

(note the prime) which takes an extra record of options controlling the way
arrows are drawn. We want to override the default arrowhead size as
well as specify gaps before and after the arrow, which we do as
follows:

```
> node :: Int -> Diagram B
> node n = text (show n) # fontSizeL 0.2 # fc white
> <> circle 0.2 # fc green # named n
>
> tournament :: Int -> Diagram B
> tournament n = atPoints (trailVertices $ regPoly n 1) (map node [1..n])
>
> example = tournament 6
> # connectOutside' (with & gaps .~ small
> & headLength .~ local 0.15
> )
> (1 :: Int) (2 :: Int)
```

`with`

is a convenient name for the default arguments record, and we
update it using the `lens`

library. (This pattern is common
throughout diagrams; See the user manual section on optional named
arguments.)

Now we simply need to call `connectOutside'`

for each pair of nodes.
`applyAll`

, which applies a list of functions, is useful in this sort
of situation.

```
> node :: Int -> Diagram B
> node n = text (show n) # fontSizeL 0.2 # fc white
> <> circle 0.2 # fc green # named n
>
> arrowOpts = with & gaps .~ small
> & headLength .~ local 0.15
>
> tournament :: Int -> Diagram B
> tournament n = atPoints (trailVertices $ regPoly n 1) (map node [1..n])
> # applyAll [connectOutside' arrowOpts j k | j <- [1 .. n-1], k <- [j+1 .. n]]
>
> example = tournament 6
```

Voilá!

This tutorial has really only scratched the surface of what is possible! Here are pointers to some resources for learning more:

There are other tutorials on more specific topics available. For example, there is a tutorial on working with vectors and points, one on trails and paths, one on drawing arrows between things, one on construting command-line driven interfaces, and others.

The diagrams user manual goes into much more depth on all the topics covered in this tutorial, plus many others, and includes lots of illustrative examples. If there is anything in the manual that you find unclear, confusing, or omitted, please report it as a bug!

The diagrams-lib API is generally well-documented; start with the documentation for

`Diagrams.Prelude`

, and then drill down from there to learn about whatever you are interested in. If there is anything in the API documentation that you find unclear or confusing, please report it as a bug as well!If you run into difficulty or have any questions, join the

`#diagrams`

IRC channel on Libera.Chat, or the diagrams-discuss mailing list.