This article shows our process at Rescale for creating diagrams using React, SVG, and Facebook's css-layout library. At Rescale, we provide a web UI for our customers to set up a license proxy server for communication between our compute nodes and their on-premise license servers. A diagram is a great way to show our users how these servers communicate with each other.
Here's an example of the diagram we created with this technique. The controls are just to show that React can rerender the diagram when state changes.
SVG is a good choice for creating diagrams within React because it's just part
of the DOM. However, laying out SVG elements is not as simple as using CSS with
HTML because SVG element coordinates are usually specified absolutely, (imagine
CSS where everything is position: absolute
). That's where Facebook's
css-layout comes in. It allows us to use CSS's flexbox model to lay
out our SVG elements.
With React, SVG, and css-layout, we are able to:
- Dynamically change the diagram due to changes in a component's
props
orstate
- Iterate quickly on requirement changes
- Localize to other languages using our existing method of localizing the UI
Wireframing
To begin creating the diagram, we wireframe it using HTML with the default styles as written in css-layout's README:
The wireframe of the diagram in HTML looks like this:
Rescale Compute License Proxy On-Premise License Server 1 License Server 2
This produces:
Borders were added to each div
just to show their boundaries. A couple of
things to keep in mind:
- We should only be using css-layout's supported attributes
- Positioning text in SVG will still be a somewhat manual process, especially vertical centering
Creating the css-layout tree from the wireframe
In css-layout, a node is just an object with { style: { ... }, children: [ nodes ] }
. The root node is considered the tree. Running computeLayout()
on
the tree will populate all of its nodes with a layout
object that contains
width
, height
, top
, left
, right
, and bottom
. This is what we'll use
to position the SVG elements.
Creating the tree from the wireframe is just a simple read off of the HTML.
id
s are given to each node so that we can access the layout
property of each
node by id
later.
const computeLayout = ;const range = ; { return n * 12;} const numServers = 2; const nodeTree = id: 'root' style: alignItems: 'center' children: id: 'rescaleBox' style: padding: width: children: id: 'rescaleLabel' style: height: id: 'computeNodes' style: height: marginBottom: marginTop: id: 'licenseProxy' style: height: id: 'onPremiseBox' style: marginTop: children: id: 'onPremiseLabel' style: height: margin: id: 'onPremiseServers' style: flexDirection: 'row' marginBottom: paddingLeft: paddingRight: children: ; ;
Rendering with React
We have each of our nodes populated with the layout
property. Making these
layout
objects accessible by id
is pretty simple:
{ if objid mapobjid = objlayout; if objchildren for let i = 0; i < objchildrenlength; i++ ; return map;} const l = ;
The l
object contains all of the layout information accessible by the id
given to each node. With it we can now use a combination of <g>
, <rect>
, and
<text>
to render the diagram in React:
{ // ... all the other stuff above ... const l = ; return <svg width=lrootwidth height=lrootheight xmlns="http://www.w3.org/2000/svg"> <g transform=`translate(, )`> <rect width=lrescaleBoxwidth height=lrescaleBoxheight stroke="#70a5c3" strokeWidth="3" fill="#f9f9f9" /> <text x=lrescaleLabelleft y=lrescaleLabeltop dy="1em" fontSize=> </text> <g transform=`translate(, )`> <rect width=lcomputeNodeswidth height=lcomputeNodesheight fill="#ffffdd" stroke="#333333" strokeWidth="2" /> <text x=lcomputeNodeswidth / 2 y=lcomputeNodesheight / 2 textAnchor="middle" dy="0.3em" fontSize= > </text> </g> <g transform=`translate(, )`> <rect width=llicenseProxywidth height=llicenseProxyheight fill="#ffffdd" stroke="#333333" strokeWidth="2" /> <text x=llicenseProxywidth / 2 y=llicenseProxyheight / 2 textAnchor="middle" dy="-.3em" fontSize= > </text> <text x=llicenseProxywidth / 2 y=llicenseProxyheight / 2 textAnchor="middle" dy="1em" fontSize= > licenseProxyIp </text> </g> // ... and so on, the rest is left as an exercise for the reader </g> </svg> ;}
As you can see, we're using the layout
properties computed by css-layout to
position the elements or set their widths and heights. Things to note:
- The
top
,right
,bottom
, andleft
properties in thelayout
objects are relative to its parent node. In SVG, we use<g transform={`translate(${left}, ${top})`}>
so that each child of the<g>
element is positioned relative to that transformation. - Positioning text is somewhat manual as mentioned above. The
textAnchor
property can handle horizontal centering, but we need to use thedy
property with some magic numbers to handle vertical centering.
This technique was inspired by this blog post showing how to lay out D3 charts using css-layout.
This article was crossposted on Rescale's blog.