Ask HN: How to quickly animate system sketches and 2D diagrams?
I had this problem too, so I wrote some code. It&#x27;s not open sourced yet and who knows if I&#x27;ll get round to it, but here&#x27;s a detailed description of how it works, it is <i>not a lot of code</i> so you could probably reproduce this easily..<p>The problem I wanted to solve is more whiteboarding and presentation: I often am in calls describing something that only needs fairly simple drawings - boxes, lines, labels - but whiteboard where you scribble or even lay out rectangles makes me sweat the small stuff, resizing and moving everything all the time when the diagram changes. Also, I wanted animation: to show transition between states. I&#x27;d tried using mingrammer to do diagrams as code, but I find graphviz layouts unpredictable. Most often what I want is: just boxes, lines, labels, size things to fit, keep everything &#x27;evenly spaced&#x27;.<p>So, I wrote some JS (it&#x27;s ~700 lines of code, no external libraries). I divide a web page into two areas: one textarea where I type commands line-by-line, and the drawing, which is svg. On selectionchange events for the textarea, I debounce for 500ms, so if I&#x27;ve paused the layout&amp;animation kick in. The drawing state has a map of nodes (boxes), which contain a title, an icon, map of connections to other boxes, and a list of children (ids of other nodes), as well as recording the node we are currently zoomed in to, the node we are currently editing, and a few other details. There is a node called &#x27;top&#x27; which is the starting point.<p>The commands are pretty simple and were added as I needed things, eg:<p><pre><code>    add internet # add a child to the currently edited node with id &#x27;internet&#x27;
                 # its title and icon are also set to &#x27;internet&#x27;
    edit internet
    add host1 icon host # this has id host1, title host1, icon host
    add host2 icon host title &quot;banana stand&quot; # other attrs are layout and stroke
    edit top
    edit aws # editing a non-existent node adds it
    add ec2
    arrow host1 ec2
    zoom aws # this node expands to fill the screen
    top # same as &#x27;zoom top&#x27;, zooms to the top level
</code></pre>
It&#x27;s interpreted line-by-line so that each line represents one state of the diagram. There are commands to delete nodes: when I delete a node I just remove it from its parent but leave it in the top-level state. That has the neat effect that if I re-add it, I get the node with all its descendents and connections restored in one step, which I can use to pre-diagram things I talk about often.<p>After calculating the drawing state by applying all the commands from the start to the current selection, the next step is to limit this to the visible pieces. I make a copy of the drawing state, starting from the currently zoomed node and following all children. Then I add all connections, if all the &#x27;to&#x27; ends of the connections are visible.<p>Next, I do layout. Starting with the visible tree, annotate all nodes with positions of the box (if any), the icon, and the label. The diagrams I&#x27;m drawing are similar to those produced by AWS Perspective: <a href="https:&#x2F;&#x2F;aws.amazon.com&#x2F;solutions&#x2F;implementations&#x2F;aws-perspective&#x2F;" rel="nofollow">https:&#x2F;&#x2F;aws.amazon.com&#x2F;solutions&#x2F;implementations&#x2F;aws-perspec...</a> , so if a node has no children I draw it as a large icon with a label below, if it has children, it is a box with a small icon to the top left, a centred label at the top. Each node can choose one of a small number of layouts that I can do automatically with just a list of children: &#x27;ring&#x27; (a circle of nodes), &#x27;row&#x27;, &#x27;column&#x27;, or &#x27;snake&#x27; (the default: alternate rtl-ltr rows to evenly fill the space; this will be a grid if that fits or could end up like 4-3-4-3 if it doesn&#x27;t). In ring &amp; snake, boxes are always 4:3; in row and column they are stretched to fit.<p>Next, I do animation. I keep around the previous layed-out state, and use window.requestAnimationFrame to calculate the position of boxes between the start and end state. A box that is in both start and end states is moved, if it is only in start or end I fade it in or out as need be. This lets me animate between _any_ two states of the drawing, so I can talk about one bit of the diagram, then jump back and forth by clicking in the command window, and it smoothly animates between them. I found animating for just 0.5s worked best for interactivity; it&#x27;s nice to see a slower move but it feels laggy when typing.<p>I calculate arrow positions after calculating the final position of boxes and icons. I chose to use circular arcs, because you will never get an awkward situation where an arc lies directly along the edge of a box; straight things are always boxes, curvy things are always arrows. SVG wants two endpoints and a centre to draw these. So, I start with an arc between the centres of the two boxes, choose a radius twice as long as the distance between these points; then I calculate the intersection of the arc with the boxes, and use those two intersection points as the start&#x2F;end of the arc. (this isn&#x27;t that difficult, the formula for the arc is in the svg spec, and it&#x27;s checking 4 straight lines, choose the intersection point closest to the other box). Like the boxes, the arrows fade in and out if they are not needed in one of the start or end states.<p>All of this then just replaces the content of the svg. It&#x27;s surprisingly smooth.<p>One last detail is icons. I&#x27;m using the icons from mingrammer (<a href="https:&#x2F;&#x2F;github.com&#x2F;mingrammer&#x2F;diagrams&#x2F;tree&#x2F;master&#x2F;resources" rel="nofollow">https:&#x2F;&#x2F;github.com&#x2F;mingrammer&#x2F;diagrams&#x2F;tree&#x2F;master&#x2F;resources</a>), which gives me about 1600(!). Finding an icon _while you type_ is awkward and initially I had to drop to the shell to find the file I was going to refer to. I tried giving the drawing tool a mode that would let me visually pick the icon, but 1600 is too many. So I changed it to use a fuzzy search to find an appropriate icon: it looks for the icon where the sequence of characters appear in the shortest substring of the icon path: eg for &#x27;ec2&#x27; it constructs the regex `.*(e.*?c.*?2)`, scoring the matching substring &#x27;ec2&#x27; better than &#x27;elastic2&#x27;, and the shorter containing string &#x27;aws&#x2F;compute&#x2F;ec2&#x27; better than eg &#x27;aws&#x2F;compute&#x2F;ec2-rounded&#x27;. (I have a further round of preferences so that the top level aws iconset is preferred to eg the ibm one, which has terrible icons). This gives you an icon for almost anything you type, and encourages a more playful approach to picking the icon than the exact-match approach.<p>There&#x27;s a bit more to it, I also accept some markdown which fades from the diagram to slides with bullet points, then back to the diagram if the current command is a diagramming command. But the description above is most of it. I could probably have done this better with eg d3 to do the drawing but I am not a front end developer at all and the whole thing was more of a hack over a couple of weekends. I should clean it up a bit, but it works.<p>I serve up pre-prepared pages with this js attached from github pages, I can walk through eg the flow of data clicking the down arrow to change the selection which causes it to animate to the next state which has the next arrow... and so on.