Announcing diagrams 0.5

March 9, 2012

I am pleased to announce the release of version 0.5 of diagrams, a full-featured framework and embedded domain-specific language for declarative drawing. Check out the gallery for examples of what it can do!

Naive fibonacci call tree

Highlights of this release include:

  • A new diagrams-contrib package of user-contributed modules, which so far contains code for tree drawing, Apollonian gaskets, planar tilings, "wrapped" layout, and turtle graphics.
  • Experimental support for animation, built on top of the new active library.
  • Numerous small additions and improvements, including more general rounded rectangle shapes and better text support.
  • Much better performance in some common situations, such as laying out a very long list of diagrams using ‘cat’ and related combinators.
  • Added support for GHC 7.4.

See the release notes for complete details, and the diagrams wiki for help migrating code from 0.4 to 0.5 (changes should be minimal).

Try it out

For the truly impatient:

cabal install gtk2hs-buildtools
cabal install diagrams

Diagrams is supported under GHC 6.12, 7.0, 7.2, and 7.4. However, getting cairo to build can be tricky on some platforms; see the diagrams wiki for more information and workarounds regarding specific platforms. (A new native SVG backend is in the works, targeted for the 0.6 release.)

To get started with diagrams, read the quick tutorial, which will introduce you to the fundamentals of the framework.

For those who are even less impatient but want to really dig in and use the power features, read the user manual.

Get involved

Subscribe to the project mailing list, and/or come hang out in the #diagrams IRC channel on freenode.org for help and discussion. Make some diagrams. Fix some bugs. Submit your cool examples for inclusion in the gallery or your cool code for inclusion in the diagrams-contrib package!

Happy diagramming!

Brought to you by the diagrams team:

  • Peter Hall
  • Ian Ross
  • Michael Sloan
  • Ryan Yates
  • Brent Yorgey

with contributions from:

  • Sam Griffin
  • Claude Heiland-Allen
  • John Lato
  • Vilhelm Sjöberg
  • Luite Stegeman
  • Kanchalai Suveepattananont
  • Scott Walck

Modern art with diagrams: the face of progress

November 12, 2011

As an addendum to my previous post, here are a few outputs generated while I was debugging:

For extra credit, deduce what the bugs were. ;)


Generating plane tilings with diagrams

November 12, 2011

I’ve finally set up a diagrams-contrib package to serve as a home for user contributions to the diagrams project—generation of specialized diagrams, fun or instructive examples, half-baked ideas, stuff which is not sufficiently polished or general to go in the diagrams-lib package but is nonetheless worth sharing.

As the first “contribution” I put some code I wrote for fun that generates tilings of the Euclidean plane by regular polygons.

So how does it work? I’m sure there are more clever ways if you understand the mathematics better; but essentially it does a depth-first search along the edge graph, stopping when it reaches some user-defined limit, and drawing polygons and edges along the way. This sounds quite simple on the face of it; but there are two nontrivial problems to be worked out:

  1. How can we tell whether we’ve visited a given vertex before?
  2. How do we represent a tiling in a way that lets us easily traverse its edge graph?

The first question is really a question of representation: how do we represent vertices in such a way that we can decide their equality? Representing them with a pair of floating point coordinates does not work: taking two different paths to a vertex will surely result in slightly different coordinates due to floating point error. Another idea is to represent vertices by the path taken to reach them, but now we have to deal with the thorny problem of deciding when two paths are equivalent.

But it turns out we can do something a bit more clever. The only regular polygons that can appear in plane tilings are triangles, squares, hexagons, octagons, and dodecagons. If you remember your high school trigonometry, these all have “special” angles whose sines and cosines can be represented exactly using square roots. It suffices to work in \mathbb{Q}[\sqrt{2}, \sqrt{3}], that is, the ring of rational numbers adjoined with \sqrt{2} and \sqrt{3}. Put simply, we use quadruples of rational numbers (a,b,c,d) which represent the real number a + b\sqrt{2} + c\sqrt{3} + d\sqrt{6}. Now we can represent vertices exactly, so remembering which we’ve already visited is easy.

The other question is how to represent tilings. I chose to use this “zipper-like” representation:

data Tiling = Tiling [TilingPoly] (Int -> Tiling)

Intuitively, a Tiling tells us what polygons surround the current vertex (ordered counterclockwise from the edge along which we entered the vertex), as well as what configurations we can reach by following edges out of the current vertex. Thanks to laziness and knot-tying, we can easily define infinite tilings, such as


t4 :: Tiling
t4 = Tiling (replicate 4 Square) (const t4)

This is a particularly simple example, but the principle is the same. You can look at the source for more complex examples.

Of course, this doesn’t really show off the capabilities of diagrams much (you can draw regular polygons with any old graphics library), but it sure was fun!


Announcing diagrams-0.4

October 23, 2011

I am pleased to announce the release of version 0.4 of diagrams, a full-featured framework and embedded domain-specific language for declarative drawing.

The last announcement was of the 0.1 release; there have been quite a few changes and improvements since then, including:

  • A new website including a gallery of examples
  • A new comprehensive user manual with lots of illustrative examples
  • New primitive shapes: rounded rectangles, wedges, and a new flexible API for generating polygons
  • Cubic splines
  • Basic text support
  • Support for external image primitives
  • Lots more convenient combinators, bug fixes, and improvements

Cool, how can I try it out?

For the truly impatient:

cabal install gtk2hs-buildtools
cabal install diagrams

For the slightly less impatient, read the quick tutorial, which has detailed information about how to install the necessary packages and will introduce you to the fundamentals of the framework.

For those who are even less impatient but want to really dig in and use the power features, read the user manual.

Cool, how can I contribute?

There are lots of ways you can contribute! First, you may want to subscribe to the project mailing list, and/or come hang out in the #diagrams IRC channel on freenode.org.

  • There are lots of easy bug fixes, improvements, and feature requests just waiting for people wanting to get involved: see the bug tracker for a list of open tickets.

    The source repositories are mirrored using both darcs (on patch-tag.com) and git (on github.com), and patches are accepted in either place, thanks to Owen Stephen’s great work on darcs-bridge.

  • Create a higher-level module built on top of the diagrams framework (e.g. tree or graph layout, generating Turing machine configuration diagrams, Penrose tilings … your imagination is the only limit!) and submit it for inclusion in a special diagrams-contrib package which will be created for such higher-level user-contributed modules.
  • Use diagrams to create some cool graphics and submit them for inclusion in the gallery.
  • Start your own project built on top of diagrams and let us know how it goes!
  • Last but certainly not least, just try it out for your pet graphics generation needs and contribute your bug reports and feature requests.

Happy diagramming!

Brought to you by the diagrams team:

  • Brent Yorgey
  • Ryan Yates

with contributions from:

  • Sam Griffin
  • Claude Heiland-Allen
  • John Lato
  • Vilhelm Sjöberg
  • Luite Stegeman
  • Kanchalai Suveepattananont
  • Scott Walck

diagrams repos now mirrored on github!

September 10, 2011

I was quite excited when I learned that Owen Stephens was working on a two-way darcs-git bridge for his Google Summer of Code project. You see, I’m one of those holdouts who, despite acknowledging that github is pretty cool and a lot of people in the Haskell community are using it these days, still really loves darcs and refuses to give it up. Well, thanks to Owen’s great work, it looks like I can now have my cake and eat it too.

I’ve now mirrored four diagrams repositories on github:

  • diagrams-core – core library
  • diagrams-lib – standard library of primitives and combinators built on top of diagrams-core
  • diagrams-cairo – rendering backend using cairo
  • diagrams-doc – documentation for the project, including source code for the website, tutorial, user manual, IRC logs, etc.

I will continue to use darcs as my primary VCS, and of course I will still accept darcs patches (the darcs repos are hosted on patch-tag.com). But I can now accept pull requests on github, for contributors who prefer using git/github. It will probably take a while to get the kinks worked out and figure out how to manage everything properly. But I’m excited by the opportunity to encourage more collaboration.

Oh yes, and the user manual is coming along nicely! You can see a preview version here. Comments and patches (via either darcs or git!) welcome. There have also been quite a few cool features added since the 0.3 release, and we hope to have an 0.4 release out soon.


Tic-tac-toe maps with diagrams

May 18, 2011

Inspired by Randall Munroe, here are some handy guides to optimal tic-tac-toe play, created with the diagrams EDSL. Click the images to open (zoomable) PDF versions.

I hacked this up in just a few hours. How did I do it? First, some code for solving tic-tac-toe (no graphics involved here, just game trees and minimax search):

> {-# LANGUAGE PatternGuards, ViewPatterns #-}
> 
> module Solve where
> 
> import Data.Array
> import Data.Tree
> import Data.Function (on)
> import Data.List (groupBy, maximumBy)
> import Data.Maybe (isNothing, isJust)
> import Data.Monoid
> import Control.Applicative (liftA2)
> import Control.Arrow ((&&&))
> import Control.Monad (guard)
> 
> data Player = X | O
>   deriving (Show, Eq, Ord)
> 
> next X = O
> next O = X
> 
> data Result = Win Player Int [Loc] -- ^ This player can win in n moves
>             | Cats                 -- ^ Tie game
>   deriving (Show, Eq)
> 
> compareResultsFor :: Player -> (Result -> Result -> Ordering)
> compareResultsFor X = compare `on` resultToScore
>     where resultToScore (Win X n _) = (1/(1+fromIntegral n))
>           resultToScore Cats        = 0
>           resultToScore (Win O n _) = (-1/(1+fromIntegral n))
> compareResultsFor O = flip (compareResultsFor X)
> 
> type Loc = (Int,Int)
> type Board = Array Loc (Maybe Player)
> 
> emptyBoard :: Board
> emptyBoard = listArray ((0,0), (2,2)) (repeat Nothing)
> 
> showBoard :: Board -> String
> showBoard = unlines . map showRow . groupBy ((==) `on` (fst . fst)) . assocs
>   where showRow = concatMap (showPiece . snd)
>         showPiece Nothing  = " "
>         showPiece (Just p) = show p
> 
> data Move = Move Player Loc
>   deriving Show
> 
> makeMove :: Move -> Board -> Board
> makeMove (Move p l) b = b // [(l, Just p)]
> 
> data Game = Game Board           -- ^ The current board state.
>                  Player          -- ^ Whose turn is it?
>                  [Move]          -- ^ The list of moves so far (most
>                                  --   recent first).
>   deriving Show
> 
> initialGame = Game emptyBoard X []
> 
> -- | The full game tree for tic-tac-toe.
> gameTree :: Tree Game
> gameTree = unfoldTree (id &&& genMoves) initialGame
> 
> -- | Generate all possible successor games from the given game.
> genMoves :: Game -> [Game]
> genMoves (Game board player moves) = newGames
>   where validLocs = map fst . filter (isNothing . snd) . assocs $ board
>         newGames  = [Game (makeMove m board) (next player) (m:moves)
>                       | p <- validLocs
>                       , let m = Move player p
>                     ]
> 
> -- | Simple fold for Trees.  The Data.Tree module does not provide
> --   this.
> foldTree :: (a -> [b] -> b) -> Tree a -> b
> foldTree f (Node a ts) = f a (map (foldTree f) ts)
> 
> -- | Solve the game for player @p@: prune all but the optimal moves
> --   for player @p@, and annotate each game with its result (given
> --   best play).
> solveFor :: Player -> Tree Game -> Tree (Game, Result)
> solveFor p = foldTree (solveStep p)
> 
> -- | Given a game and its continuations (including their results),
> --   solve the game for player p.  If it is player p's turn, prune all
> --   continuations except the optimal one for p. Otherwise, leave all
> --   continuations.  The result of this game is the result of the
> --   optimal choice if it is p's turn, otherwise the worst possible
> --   outcome for p.
> solveStep :: Player -> Game -> [Tree (Game, Result)] -> Tree (Game, Result)
> solveStep p g@(Game brd curPlayer moves) conts
>   | Just res <- gameOver g = Node (g, res) []
> 
>   | curPlayer == p = let c   = bestContFor p conts
>                          res = inc . snd . rootLabel $ c
>                      in  Node (g, res) [c]
>   | otherwise      = Node (g, bestResultFor (next p) conts) conts
> 
> bestContFor :: Player -> [Tree (Game, Result)] -> Tree (Game, Result)
> bestContFor p = maximumBy (compareResultsFor p `on` (snd . rootLabel))
> 
> bestResultFor :: Player -> [Tree (Game, Result)] -> Result
> bestResultFor p = inc . snd . rootLabel . bestContFor p
> 
> inc :: Result -> Result
> inc (Win p n ls) = Win p (n+1) ls
> inc Cats         = Cats
> 
> -- | Check whether the game is over, returning the result if it is.
> gameOver :: Game -> Maybe Result
> gameOver (Game board _ _)
>   = getFirst $ mconcat (map (checkWin board) threes) `mappend` checkCats board
> 
> checkWin :: Board -> [Loc] -> First Result
> checkWin board = First
>                . (>>= winAsResult)      -- Maybe Result
>                . mapM strength          -- Maybe [(Loc, Player)]
>                . map (id &&& (board!))  -- [(Loc, Maybe Player)]
> 
> winAsResult :: [(Loc, Player)] -> Maybe Result
> winAsResult (unzip -> (ls,ps))
>   | Just p <- allEqual ps = Just (Win p 0 ls)
> winAsResult _ = Nothing
> 
> checkCats :: Board -> First Result
> checkCats b | all isJust (elems b) = First (Just Cats)
>             | otherwise            = First Nothing
> 
> allEqual :: Eq a => [a] -> Maybe a
> allEqual = foldr1 combine . map Just
>   where combine (Just x) (Just y) | x == y = Just x
>                                   | otherwise = Nothing
>         combine Nothing _         = Nothing
>         combine _ Nothing         = Nothing
> 
> strength :: Functor f => (a, f b) -> f (a,b)
> strength (a, f) = fmap ((,) a) f
> 
> threes :: [[Loc]]
> threes = rows ++ cols ++ diags
>   where rows     = [ [ (r,c) | c <- [0..2] ] | r <- [0..2] ]
>         cols     = [ [ (r,c) | r <- [0..2] ] | c <- [0..2] ]
>         diags    = [ [ (i,i) | i <- [0..2] ]
>                    , [ (i,2-i) | i <- [0..2] ]
>                    ]

Once we have a solved game tree, we can use it to generate a graphical map as follows.

> {-# LANGUAGE NoMonomorphismRestriction #-}
> 
> -- Maps of optimal tic-tac-toe play, inspired by similar maps created
> -- by Randall Munroe, http://xkcd.com/832/
> 
> import Diagrams.Prelude hiding (Result)
> import Diagrams.Backend.Cairo.CmdLine
> 
> import Data.List.Split (chunk)                   -- cabal install split
> import Data.Maybe (fromMaybe, catMaybes)
> import qualified Data.Map as M
> import Data.Tree
> import Control.Arrow (second, (&&&), (***))
> import Data.Array (assocs)
> 
> import Solve
> 
> type D = Diagram Cairo R2
> 
> x, o :: D
> x = (stroke $ fromVertices [P (-1,1), P (1,-1)] <> fromVertices [P (1,1), P (-1,-1)])
>   # lw 0.05
>   # lineCap LineCapRound
>   # scale 0.4
>   # freeze
>   # centerXY
> o = circle
>   # lw 0.05
>   # scale 0.4
>   # freeze
> 
> -- | Render a list of lists of diagrams in a grid.
> grid :: Double -> [[D]] -> D
> grid s = centerXY
>        . vcat' with {catMethod = Distrib, sep = s}
>        . map (hcat' with {catMethod = Distrib, sep = s})
> 
> -- | Given a mapping from (r,c) locations (in a 3x3 grid) to diagrams,
> --   render them in a grid, surrounded by a square.
> renderGrid :: M.Map Loc D -> D
> renderGrid g
>   = (grid 1
>   . chunk 3
>   . map (fromMaybe (phantom x) . flip M.lookup g)
>   $ [ (r,c) | r <- [0..2], c <- [0..2] ])
> 
>     `atop`
>     square # lw 0.02 # scale 3 # freeze
> 
> -- | Given a solved game tree, where the first move is being made by
> --   the player for whom the tree is solved, render a map of optimal play.
> renderSolvedP :: Tree (Game, Result) -> D
> renderSolvedP (Node (Game _ p _, _) [])   -- cats game, this player does not
>     = renderPlayer (next p) # scale 3     -- get another move; instead of
>                                           -- recursively rendering this game
>                                           -- just render an X or an O
> renderSolvedP (Node (Game board player1 _, _)
>                     [g'@(Node (Game _ _ (Move _ loc : _), res) conts)])
>     = renderResult res <>    -- Draw a line through a win
>       renderGrid cur   <>    -- Draw the optimal move + current moves
>       renderOtherP g'        -- Recursively render responses to other moves
> 
>   where cur = M.singleton loc (renderPlayer player1 # lc red)  -- the optimal move
>               <> curMoves board                                -- current moves
> 
> renderSolvedP _ = error "renderSolvedP should be called on solved trees only"
> 
> -- | Given a solved game tree, where the first move is being made by the
> --   opponent of the player for whom the tree is solved, render a map of optimal
> --   play.
> renderOtherP :: Tree (Game, Result) -> D
> renderOtherP (Node _ conts)
>     -- just recursively render each game arising from an opponent's move in a grid.
>   = renderGrid . M.fromList . map (getMove &&& (scale (1/3) . renderSolvedP)) $ conts
>   where getMove (Node (Game _ _ (Move _ m : _), _) _) = m
> 
> -- | Extract the current moves from a board.
> curMoves :: Board -> M.Map Loc D
> curMoves = M.fromList . (map . second) renderPlayer . catMaybes . map strength . assocs
> 
> -- | Render a line through a win.
> renderResult :: Result -> D
> renderResult (Win _ 0 ls) = winLine # freeze
>   where winLine :: D
>         winLine = stroke (fromVertices (map (P . conv) ls))
>                           # lw 0.2
>                           # lc blue
>                           # lineCap LineCapRound
>         conv (r,c) = (fromIntegral $ c - 1, fromIntegral $ 1 - r)
> renderResult _ = mempty
> 
> renderPlayer X = x
> renderPlayer O = o
> 
> xMap = renderSolvedP . solveFor X $ gameTree
> oMap = renderOtherP  . solveFor O $ gameTree
> 
> main = defaultMain (pad 1.1 xMap)
>        -- defaultMain (pad 1.1 oMap)

Announcing diagrams preview release

May 17, 2011

I am extremely pleased to announce a "developer preview" release of the diagrams framework for declarative drawing. This is a well-thought-out, well-documented, working release with all core functionality in place, but with many planned features still missing (for example, support for rendering text and higher-level tools for constructing curves). If you are interested in

  • trying out a new way of producing vector graphics,
  • providing feedback to help drive ongoing development, or
  • getting involved and contributing some code yourself,

please give it a try! On the other hand, if you are looking for a complete, full-featured package that will let you jump right into producing the graphics you need, you may want to wait for the 1.0 release.

If you are familiar with the diagrams package already on Hackage, this is a complete rewrite which has been in the works for over a year and a half.

What is it?

Diagrams is an embedded domain-specific library (EDSL) for creating diagrams, illustrations, and other sorts of vector graphics. The overall vision is for diagrams to become a viable alternative to systems like MetaPost, Asymptote, and PGF/TikZ.

Diagrams is:

  • Declarative: you specify what a diagram is, not how to draw it.

  • Compositional: diagrams can be combined in many ways to produce more complex diagrams. Diagrams are scale- and translation-invariant, so you never have to worry about a "global" coordinate system, only "local" ones.

  • Embedded: the full power of Haskell, including every library on Hackage, is available to help construct and manipulate diagrams.

  • 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. Features include:

    • Pluggable rendering backends — creating a new rendering backend is as simple as writing a type class instance.

    • Arbitrary vector spaces — the core diagrams library data types and primitives work for any vector space, so given a suitable rendering backend you can produce diagrams of any dimension, or even more exotic things…

Cool, how can I try it out?

Start by reading the quick tutorial, which has detailed information about how to install the necessary packages and will introduce you to the fundamentals of the framework.

Or, for the truly impatient:

cabal install diagrams-core diagrams-lib diagrams-cairo

How can I contribute?

There are lots of ways you can contribute! First, you may want to subscribe to the project mailing list, and/or come hang out in the #diagrams IRC channel on freenode.org.

  • Cairo is the only well-supported backend at the moment, but you might create another backend or contribute to an existing project.

  • The standard library is in need of additional features. Visit the Google Code site for a list of open tickets.

  • Create a higher-level module built on top of the diagrams framework (e.g. tree or graph layout, generating Turing machine configuration diagrams, Penrose tilings … your imagination is the only limit!) and submit it for inclusion in a special diagrams-contrib package which will be created for such higher-level user-contributed modules.

  • Use diagrams to create some cool graphics and submit them for inclusion in a gallery of examples (to be created soon).

  • Start your own project built on top of diagrams and let us know how it goes!

  • Last but certainly not least, just try it out for your pet graphics generation needs and contribute your bug reports and feature requests.

Happy diagramming!

Brought to you by the diagrams team:

  • Brent Yorgey
  • Ryan Yates

with contributions from:

  • Sam Griffin
  • Vilhelm Sjöberg
  • Luite Stegeman
  • Kanchalai Suveepattananont
  • Scott Walck

Things I have learned about vector spaces

November 20, 2010

Work on the diagrams library is coming along rather nicely. I’ll have more to say about it soon, but for now here are two things I have learned recently:

  • Normals transform as the inverse transpose (see Subject 5.27).
  • Be very careful about distinguishing between points and vectors. Otherwise you are likely to get bitten by translations doing the Wrong Thing.

diagrams slides

May 22, 2010

Today at Hac φ I gave a short talk about the past and future of the diagrams library. For anyone who is interested, I’ve put the slides up here. For the actual talk the slides had no text on them, so I’ve added a summary of what I said at the bottom of each slide (although I’ve left out some details, so if you’ve any questions feel free to ask in a comment, or via email).


Hac φ 2010!

May 20, 2010

Hac φ 2010 is starting tomorrow, and I’m super excited. I am finally buckling down to do some serious work on the newly redesigned version of diagrams, and it is going to be sweet. I know I’ve been saying that for like a year, so feel free to not believe me until I actually upload something to Hackage. But I’m serious.


Follow

Get every new post delivered to your Inbox.