I’ve been working on an Elm front end for my lecture-notes-sharing project, getting ready for the Elm Europe conference June 8–9, and also having a little fun with some side projects, like making the “abstract art” in images #1 and #2.
No big deal, but I’ve learned a lot, can see that one can do much more, and thought I would share my adventures. The code is on GitHub: https://github.com/jxxcarlson/elm-drawing.
The Journey begins
After a bit of searching I found that there is an interface to SVG, and that is what I decided to use. It is fairly low level, but can write Elm functions that make drawing easy. Below is the “Hello world” program for drawing. It just puts a black disk on the screen.
import Svg exposing (..)
import Svg.Attributes exposing (..)
[ width "100", height "100" ]
circle [ cx "60", cy "60", r "30" ] [ ]
Pretty simple, eh? Here
svg is a function with two arguments, a list of attributes (width, height), and a list of children, of which there is just one at the moment. This only child has the same form as does
circle ATTRIBUTES CHILDREN. In the case at hand, the attributes define the center and radius of the circle. There can be more attributes. Thus
circle [ cx “60”, cy “60”, r “30”, fill “red”] will paint a red circle instead of the default black one.
More complicated drawings
What about more complicated drawings? Well, just have more children, e.g.,
[ width "100", height "100" ]
circle [ cx "60", cy "60", r "30", fill "red" ] [ ],
circle [ cx "120", cy "60", r "30", fill "blue ] [ ]
This gives you two circles side-by-side. Here is the
main function for Drawing #1:
[ viewBox “0 0 400 400” ]
Each element of the drawing is defined by an Elm function call, e.g.,
yellowEllipse = circle [ fill “rgba(240,240,10,0.5)”
, cx “60” , cy “120” , r “30” , G.transform commands ] [ ]
commands = [ G.translate 60.0 120.0, G.scale 1.3 2.5, G.translate -60.0 -130.0 ]
This was definitely overkill — I could have just used Elm’s
ellipse function. But I wanted to find a way to do transformations and chain them together for other “art” projects that I had in mind.
The main reference for Elm’s SVG functions is at the elm-lang site. There you will find, for example, how to use
circle . But the information given is pretty spare. Thus, for the related
ellipse command, you find only the type signature,
ellipse : List (Attribute msg) -> List (Svg msg) -> Svg msg . By guessing or looking at online sources for the HTML 5 SVG package, you can usually figure out what attributes to use for the first argument (I’ve not used the second one yet).
The code written for drawing #1 is not the most elegant, simple, or efficient, but it did give me the chance to experiment with ways to do various transformations. In particular, while SVG attributes are written using strings, I wanted something that worked directly with numbers. This is what
import Geometry as G gives you via the functions
transform . The latter takes a list of
Geometry function calls and translates it into the correct SVG
transform attribute. Overkill for the moment, but it was fun and might be useful.
Onwards and upwards
More serious — and more fun — is creating a drawing like #2. The idea is to set up one circle, the repeatedly translate and rescale it to produce a list of circles — the list of children of
svg .The solution I came up with was first to define an
AbstractCircle as a record with three numerical fields — coordinates of the center and the radius. No strings, please! Thus we have
circle1 = AbstractCircle 35.0 40.0 31.0 . This is done in the
Shape module. Also in the Shape module is defined a function
transformCircle dx dy k abstractCircle
which moves (translates by
dx dy) and shrinks (rescales by
k) an abstract circle. With this in hand, it is easy to define
move k abstractCircle , a function which takes an AbstractCircle as input and produces as output an AbstractCircle on the right-hand side of the given circle which (a) just touches the given circle and (b) is smaller by a factor of k.
What is left to do? Well, in the case of drawing #2, we repeatedly apply
move 0.7 to
circle1 to get a list of circles. The little module
Iterate in the Github repo does this using the function
abstractCircles = orbit (move 0.7) 7 circle1 .
It remains to convert those circles into SVG circles:
circles = List.map (\c -> makeCircle c) abstractCircles
makeCircle is a function defined in the
Shape module which takes an abstract circle as input and produces an SVG circle as output.
That’s it! The real work in doing drawing #2 is done by three lines of code. The workhorse is module
Iterate contains just three functions, each only a few lines long.
May you go forth and make beautiful art with Elm, or do something else insanely cool.