Drawing State Diagrams in SVG

SVG is ideal for a lot of drawing applications that are vector-oriented in nature. Specifically it’s very good for drawing diagrams and charts. A diagram drawn in SVG will take up far less space on the hard drive than one drawn in a raster graphics editor like GIMP, and it will also be far easier to edit and correct various parts of the diagram as it’s simply a matter of adjusting number fields in the text.

Recently I had to draw a couple state diagrams for a technical article I was writing for this site. I chose to do the diagrams using SVG mainly because I couldn’t find any software suitable for doing what I wanted to do. All the available packages and online utilities for drawing state diagrams used rectangular boxes for the states, which is not a format that I like as it can easily get confused with entity-relationship diagrams, which also use rectangular boxes. The way I see it, you should use different formats for all the different types of diagrams: rectangles, parallelograms, and diamonds for flowcharts; rectangles with rounded corners for entity-relationship diagrams; and circles with lines and arcs between them for state diagrams. So lacking a suitable software package to automate the process for me, I decided to do the state diagrams manually in SVG. Much more difficult, but it would allow me to get them to look exactly how I wanted them to look, and I am somewhat of a perfectionist.

You might remember the state diagrams from the article in question. For those who haven’t seen them, or just want a refresher, here they are:

Finite automaton for parsing a CSV string

Finite automaton for parsing CSV numbers

In that (rather long) post I talked about the automata that these diagrams represent and how they can be used to parse CSV files. In this post I will be talking about the diagrams themselves and how I went about drawing them, because there were quite a lot of interesting mathematical calculations as well as coding work involved, which I think will make for an interesting article.

The principle problem I was faced with was really a matter of making the state diagrams aesthetically pleasing. If I wanted to, I could have done the diagrams in MS Paint in a matter of minutes. But the problem is they would have looked like shit. I wanted them to look professionally done, because like I said, I’m a perfectionist. That meant that all the different parts of the diagrams had to line up, and each state transition had to start and end at a state boundary in at least roughly a right angle to the edge of the circle. It also meant the composition of the diagrams had to be well-balanced, with as little empty space as possible.

Because the states themselves were fairly easy to draw in SVG (I just used <circle>), I will not be looking at those. The part where real math comes into play is in drawing the transitions between states – both the lines/arcs going from one state to the next, and the arrowheads at the ends. There are two classes of transitions in these diagrams: straight line segments and elliptical arcs. I will focus on the straight lines first, since those are simpler.

The problem of drawing a straight line between two states is fairly simple if the circles for those states are aligned in some way, meaning they have either the same x coordinate or the same y coordinate for their centers. Say two circles are horizontally aligned (they have the same y coordinate), and say you want to draw a line from the edge of one to the edge of the next. In SVG we specify a line segment by giving the x and y coordinates of its two endpoints. In this case the y coordinates would both be equal to that of the centers of the circles, the first x coordinate would be equal to that of the circle on the left plus the radius of that circle, and the second x coordinate would be equal to that of the circle on the right minus the radius of that circle (in the diagrams I’ve drawn, all circles have the same radius of 50 pixels). The case of drawing a line between two vertically aligned circles is analogous. Looking at the state diagrams above, it is fairly easy to convince yourself of all of this.

The problem of drawing a straight line between two circles that are not horizontally or vertically aligned is somewhat more difficult, and involves the use of trigonometric functions. Basically we want to find the line that goes through the center of both those circles, and then find the two points where that line intersects with the near edges of those circles. To find the line, we set the center of one circle as the origin and set the center of the other one according to its x and y distances from the first circle. All of the diagonal lines I have drawn in my state diagrams are between circles whose x coordinates differ by 250 pixels and whose y coordinates differ by 200 pixels. Therefore the equation for the line is y = ±(200/250)x.

Drawing a line between two circles in SVG

To find the x and y coordinates of the intersection points, we take the radius of the circle and multiply it by the cosine and sine of the angle of the line as shown in the figure, then we add or subtract (depending on the relative placement of our circles) that value from the x or y coordinate of the center of the circle. Once we have done this, we add the coordinates of the origin circle to get the actual coordinates of the endpoints of the line segment. In this case to find the x coordinate we would take 50*cos(tan-1(200/250)) and add/subtract it to/from the x coordinate of the center, and to find the y coordinate we would take 50*sin(tan-1(200/250)) and add/subtract it to/from the y coordinate of the center.

The next problem is in drawing the arrowhead at the end of the transition line. For this I just wanted something simple: a couple line segments about 15 pixels long, radiating out from the intersection point at a 45° angle on either side of the transition line. Part of our work here is cut out for us because one endpoint of both line segments is already known: it’s the endpoint of the line we just drew. We can use this as the starting point for both of the arrowhead lines.

Drawing an arrowhead in SVG

To find the end points of the arrowhead lines, we first need to calculate the angle, then we need to calculate the sine and cosine times 15 and add/subtract this to/from the start points. The formula for the x distance is x = 15*cos(tan-1(200/250)±π/4) and the formula for the y distance is y = 15*sin(tan-1(200/250)±π/4).

As for the elliptical arcs, it was often difficult or impossible to draw these arcs in such a way that they were exactly perpendicular to the circles while also being economical with space, not to mention the calculations involved would be a lot more convoluted (and would require concepts from analytic geometry and maybe even calculus), so I resigned myself to doing most of it through guesswork and trial-and-error. The task still required a high degree of mathematical intuition though, so I was still doing calculations, even if I was doing them using the fuzzy logic circuits of my brain rather than the binary logic circuits of my graphing calculator. 😛

Elliptical arcs are a rather intricate feature of SVG, and there are a lot of parameters involved. To draw an elliptical arc in SVG you use the <path> element with a rather cryptic sequence in what I like to call the SVG Path mini-language, encapsulated by the d attribute. This sequence specifies first the start coordinates, then the dimensions of the ellipse, then the angle of rotation, then two binary parameters, then the distance to move to the end point. The two binary parameters are the large arc flag and the sweep flag. The large arc flag determines which of two ellipses is used, because there are two ellipses with the given parameters cutting through the given endpoints, and we can use either the one with the small arc or the one with the large arc. The sweep flag determines which direction the arc goes in. The distinctions between these different arcs (there are four of them in total) are shown in the diagram below.

Elliptical arcs in SVG

If you look at the two state diagrams I drew, you can see that I used the larger section of the ellipse for transitions going from a state back into itself, and I used the smaller section of the ellipse for the one elliptical arc going between two different states.

Now let’s see how this all comes together. For reference, here’s the state diagram for the string DFA:

Finite automaton for parsing a CSV string

And here is the complete SVG code for this diagram:


 1 <?xml version="1.0" standalone="no" ?>
 2 <svg width="800px" height="500px" xmlns="http://www.w3.org/2000/svg" version="1.1">
 3 <title>String DFA</title>
 4 
 5 <!-- S_START -->
 6 <circle cx="100" cy="200" r="50" stroke="black" stroke-width="2" fill="#ddddff" />
 7 <text text-anchor="middle" x="100" y="205" font-family="Helvetica" font-weight="bold" font-size="18px">START</text>
 8 
 9 <!-- State transition from S_START to S_TEXT -->
10 <line x1="150" y1="200" x2="300" y2="200" stroke="black" stroke-width="4" />
11 <text text-anchor="middle" x="225" y="190" font-family="Helvetica" font-weight="bold" font-size="18px">"</text>
12 <line x1="300" y1="200" x2="290" y2="190" stroke="black" stroke-width="4" />
13 <line x1="300" y1="200" x2="290" y2="210" stroke="black" stroke-width="4" />
14 
15 <!-- S_TEXT -->
16 <circle cx="350" cy="200" r="50" stroke="black" stroke-width="2" fill="#ddddff" />
17 <text text-anchor="middle" x="350" y="205" font-family="Helvetica" font-weight="bold" font-size="18px">S_TEXT</text>
18 
19 <!-- State transition from S_TEXT to S_TEXT -->
20 <path d="M 320 160 a 40 50, 0, 1 1, 60 0" stroke="black" stroke-width="4" fill="none" />
21 <text text-anchor="middle" x="350" y="70" font-family="Helvetica" font-weight="bold" font-size="18px">*</text>
22 <line x1="380" y1="160" x2="394" y2="156" stroke="black" stroke-width="4" />
23 <line x1="380" y1="160" x2="376" y2="146" stroke="black" stroke-width="4" />
24 
25 <!-- State transition from S_TEXT to S_FINAL -->
26 <line x1="400" y1="200" x2="550" y2="200" stroke="black" stroke-width="4" />
27 <text text-anchor="middle" x="475" y="190" font-family="Helvetica" font-weight="bold" font-size="18px">"</text>
28 <line x1="550" y1="200" x2="540" y2="190" stroke="black" stroke-width="4" />
29 <line x1="550" y1="200" x2="540" y2="210" stroke="black" stroke-width="4" />
30 
31 <!-- S_FINAL -->
32 <circle cx="600" cy="200" r="50" stroke="black" stroke-width="2" fill="#ddddff" />
33 <circle cx="600" cy="200" r="45" stroke="black" stroke-width="2" fill="#ddddff" />
34 <text text-anchor="middle" x="600" y="205" font-family="Helvetica" font-weight="bold" font-size="18px">S_FINAL</text>
35 
36 <!-- S_TRAP -->
37 <circle cx="350" cy="400" r="50" stroke="black" stroke-width="2" fill="#ddddff" />
38 <text text-anchor="middle" x="350" y="405" font-family="Helvetica" font-weight="bold" font-size="18px">TRAP</text>
39 
40 <!-- State transition from S_START to S_TRAP -->
41 <line x1="139" y1="232" x2="311" y2="369" stroke="black" stroke-width="4" />
42 <text x="235" y="290" font-family="Helvetica" font-weight="bold" font-size="18px">*</text>
43 <line x1="311" y1="369" x2="310" y2="355" stroke="black" stroke-width="4" />
44 <line x1="311" y1="369" x2="297" y2="370" stroke="black" stroke-width="4" />
45 
46 <!-- State transition from S_FINAL to S_TRAP -->
47 <line x1="561" y1="232" x2="389" y2="369" stroke="black" stroke-width="4" />
48 <text x="465" y="290" font-family="Helvetica" font-weight="bold" font-size="18px">*</text>
49 <line x1="389" y1="369" x2="390" y2="355" stroke="black" stroke-width="4" />
50 <line x1="389" y1="369" x2="403" y2="370" stroke="black" stroke-width="4" />
51 
52 <!-- State transition from S_TEXT to S_TRAP -->
53 <line x1="350" y1="250" x2="350" y2="350" stroke="black" stroke-width="4" />
54 <text x="355" y="290" font-family="Helvetica" font-weight="bold" font-size="18px">\n</text>
55 <line x1="350" y1="350" x2="338" y2="338" stroke="black" stroke-width="4" />
56 <line x1="350" y1="350" x2="362" y2="338" stroke="black" stroke-width="4" />
57 
58 </svg>

Here is the state diagram for the number DFA:

Finite automaton for parsing CSV numbers

And here is the complete SVG code for this diagram:


  1 <?xml version="1.0" standalone="no" ?>
  2 <svg width="800px" height="1000px" xmlns="http://www.w3.org/2000/svg" version="1.1">
  3 <title>Number DFA</title>
  4 
  5 <!-- N_START -->
  6 <circle cx="150" cy="200" r="50" stroke="black" stroke-width="2" fill="#ddddff" />
  7 <text text-anchor="middle" x="150" y="205" font-family="Helvetica" font-weight="bold" font-size="18px">START</text>
  8 
  9 <!-- State transition from N_START to N_MINUS -->
 10 <line x1="150" y1="250" x2="150" y2="350" stroke="black" stroke-width="4" />
 11 <text x="155" y="300" font-family="Helvetica" font-weight="bold" font-size="18px">-</text>
 12 <line x1="150" y1="350" x2="138" y2="338" stroke="black" stroke-width="4" />
 13 <line x1="150" y1="350" x2="162" y2="338" stroke="black" stroke-width="4" />
 14 
 15 <!-- N_MINUS -->
 16 <circle cx="150" cy="400" r="50" stroke="black" stroke-width="2" fill="#ddddff" />
 17 <text text-anchor="middle" x="150" y="405" font-family="Helvetica" font-weight="bold" font-size="18px">N_MINUS</text>
 18 
 19 <!-- State transition from N_MINUS to N_BEFORE -->
 20 <line x1="150" y1="450" x2="150" y2="550" stroke="black" stroke-width="4" />
 21 <text x="155" y="500" font-family="Helvetica" font-weight="bold" font-size="18px">#</text>
 22 <line x1="150" y1="550" x2="138" y2="538" stroke="black" stroke-width="4" />
 23 <line x1="150" y1="550" x2="162" y2="538" stroke="black" stroke-width="4" />
 24 
 25 <!-- N_TRAP1 -->
 26 <circle cx="400" cy="200" r="50" stroke="black" stroke-width="2" fill="#ddddff" />
 27 <text text-anchor="middle" x="400" y="205" font-family="Helvetica" font-weight="bold" font-size="18px">TRAP</text>
 28 
 29 <!-- State transition from N_MINUS to N_TRAP1 -->
 30 <line x1="189" y1="369" x2="361" y2="232" stroke="black" stroke-width="4" />
 31 <text x="265" y="300" font-family="Helvetica" font-weight="bold" font-size="18px">*</text>
 32 <line x1="361" y1="232" x2="360" y2="246" stroke="black" stroke-width="4" />
 33 <line x1="361" y1="232" x2="347" y2="231" stroke="black" stroke-width="4" />
 34 
 35 <!-- State transition from N_START to N_TRAP1 -->
 36 <line x1="200" y1="200" x2="350" y2="200" stroke="black" stroke-width="4" />
 37 <text text-anchor="middle" x="275" y="190" font-family="Helvetica" font-weight="bold" font-size="18px">*</text>
 38 <line x1="350" y1="200" x2="340" y2="190" stroke="black" stroke-width="4" />
 39 <line x1="350" y1="200" x2="340" y2="210" stroke="black" stroke-width="4" />
 40 
 41 <!-- State transition from N_START to N_BEFORE -->
 42 <path d="M 105 222 a 1000 500, 0, 0 0, 0 355" stroke="black" stroke-width="4" fill="none" />
 43 <text x="20" y="400" font-family="Helvetica" font-weight="bold" font-size="18px">#</text>
 44 <line x1="105" y1="577" x2="91" y2="574" stroke="black" stroke-width="4" />
 45 <line x1="105" y1="577" x2="107" y2="562" stroke="black" stroke-width="4" />
 46 
 47 <!-- N_BEFORE -->
 48 <circle cx="150" cy="600" r="50" stroke="black" stroke-width="2" fill="#ddddff" />
 49 <circle cx="150" cy="600" r="45" stroke="black" stroke-width="2" fill="#ddddff" />
 50 <text text-anchor="middle" x="150" y="605" font-family="Helvetica" font-weight="bold" font-size="15px">N_BEFORE</text>
 51 
 52 <!-- State transition from N_BEFORE to N_BEFORE -->
 53 <path d="M 120 640 a 40 50, 0, 1 0, 60 0" stroke="black" stroke-width="4" fill="none" />
 54 <text text-anchor="middle" x="150" y="750" font-family="Helvetica" font-weight="bold" font-size="18px">#</text>
 55 <line x1="180" y1="640" x2="194" y2="644" stroke="black" stroke-width="4" />
 56 <line x1="180" y1="640" x2="176" y2="654" stroke="black" stroke-width="4" />
 57 
 58 <!-- State transition from N_BEFORE to N_POINT -->
 59 <line x1="200" y1="600" x2="350" y2="600" stroke="black" stroke-width="4" />
 60 <text text-anchor="middle" x="275" y="590" font-family="Helvetica" font-weight="bold" font-size="18px">.</text>
 61 <line x1="350" y1="600" x2="340" y2="590" stroke="black" stroke-width="4" />
 62 <line x1="350" y1="600" x2="340" y2="610" stroke="black" stroke-width="4" />
 63 
 64 <!-- N_POINT -->
 65 <circle cx="400" cy="600" r="50" stroke="black" stroke-width="2" fill="#ddddff" />
 66 <text text-anchor="middle" x="400" y="605" font-family="Helvetica" font-weight="bold" font-size="18px">N_POINT</text>
 67 
 68 <!-- State transition from N_POINT to N_AFTER -->
 69 <line x1="450" y1="600" x2="600" y2="600" stroke="black" stroke-width="4" />
 70 <text text-anchor="middle" x="525" y="590" font-family="Helvetica" font-weight="bold" font-size="18px">#</text>
 71 <line x1="600" y1="600" x2="590" y2="590" stroke="black" stroke-width="4" />
 72 <line x1="600" y1="600" x2="590" y2="610" stroke="black" stroke-width="4" />
 73 
 74 <!-- N_AFTER -->
 75 <circle cx="650" cy="600" r="50" stroke="black" stroke-width="2" fill="#ddddff" />
 76 <circle cx="650" cy="600" r="45" stroke="black" stroke-width="2" fill="#ddddff" />
 77 <text text-anchor="middle" x="650" y="605" font-family="Helvetica" font-weight="bold" font-size="18px">N_AFTER</text>
 78 
 79 <!-- State transition from N_AFTER to N_AFTER -->
 80 <path d="M 620 640 a 40 50, 0, 1 0, 60 0" stroke="black" stroke-width="4" fill="none" />
 81 <text text-anchor="middle" x="650" y="750" font-family="Helvetica" font-weight="bold" font-size="18px">#</text>
 82 <line x1="680" y1="640" x2="694" y2="644" stroke="black" stroke-width="4" />
 83 <line x1="680" y1="640" x2="676" y2="654" stroke="black" stroke-width="4" />
 84 
 85 <!-- N_TRAP2 -->
 86 <circle cx="400" cy="800" r="50" stroke="black" stroke-width="2" fill="#ddddff" />
 87 <text text-anchor="middle" x="400" y="805" font-family="Helvetica" font-weight="bold" font-size="18px">TRAP</text>
 88 
 89 <!-- State transition from N_BEFORE to N_TRAP2 -->
 90 <line x1="189" y1="632" x2="361" y2="769" stroke="black" stroke-width="4" />
 91 <text x="285" y="690" font-family="Helvetica" font-weight="bold" font-size="18px">*</text>
 92 <line x1="361" y1="769" x2="360" y2="755" stroke="black" stroke-width="4" />
 93 <line x1="361" y1="769" x2="347" y2="770" stroke="black" stroke-width="4" />
 94 
 95 <!-- State transition from N_AFTER to N_TRAP2 -->
 96 <line x1="611" y1="632" x2="439" y2="769" stroke="black" stroke-width="4" />
 97 <text x="515" y="690" font-family="Helvetica" font-weight="bold" font-size="18px">*</text>
 98 <line x1="439" y1="769" x2="440" y2="755" stroke="black" stroke-width="4" />
 99 <line x1="439" y1="769" x2="453" y2="770" stroke="black" stroke-width="4" />
100 
101 <!-- State transition from N_POINT to N_TRAP2 -->
102 <line x1="400" y1="650" x2="400" y2="750" stroke="black" stroke-width="4" />
103 <text x="405" y="690" font-family="Helvetica" font-weight="bold" font-size="18px">*</text>
104 <line x1="400" y1="750" x2="388" y2="738" stroke="black" stroke-width="4" />
105 <line x1="400" y1="750" x2="412" y2="738" stroke="black" stroke-width="4" />
106 
107 </svg>

2 thoughts on “Drawing State Diagrams in SVG

    1. Kinda looks like the language I was planning to implement for creating state diagrams, but more general. I was going to write an interpreter that would basically take an encoding for a state diagram and convert it to SVG. Working with raw SVG code can be a pain in the ass sometimes.

      Liked by 1 person

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s