ReactJS and D3 Part I: Layouts
I love D3 — it was one of the reason I became a developer. It introduced me to the concept of functional UI. And, given the direction of the Front-End ecosystem, re-rendering based on diffed arrays (if not diffing the resultant SVG) was clearly ahead of its time and part of the inspiration for React.
On the other hand, I’ve never liked the selectAll()
enter()
update()
exit()
transition()
and data()
apis. With great deference to Mike Bostock’s N dimensional hyper-brain, I’ve never found it especially easy to reason about or explain to others.
This came to a head breaking ground on a recent client project. It went like this:
Me: “We’re about to start a really complex project. A lot of the interface will be in D3. You know SVG and CSS, so this shouldn’t be too hard to explain.”
Designer: “Ok!”
Five minutes later…
Me: “…and then
enter()
, which is kind of the beginning of the magic and obscures an internal looping over the data…”Ten minutes later…
Me: “…so what I’m trying to say is that when you say
node.append('g')
, you’re really not talking about a single node. Node is singular, but it’s actually an array. And you can think of it as singular, because again, there is an invisible looping over the data that is happening here. That’s why all of these lambdas have access to a particular data point as the markup travels through the chain of functions that constructs it…”Five minutes later…
Me: “Let me think more about this and get back to you.”
Upon reflection, a few things stuck out. Keeping track of adding and removing of data under the hood in update()
and exit()
in D3’s model is more like React’s model than jQuery’s model. D3 needed its own model for .transition()
to work magically (i.e., intelligently re-rendering every time the data changes). The whole D3 framework is built around making those behaviors work without effort.
Since D3 has it’s own DOM model (and in part because it uses smash, a custom build system with 171 GitHub stars to D3’s 37,880 at last count), developers are shipping a lot of extra methods. In fact, the minified distribution is 151 kb. Back in 2010, this was surely necessary, but I started to think it might not be necessary now that we have React. Generally, switching to a more modular modern build system will help with this, and it’s on the roadmap to D3 V4.0 However, regardless of the build, it would be nice to not ship multiple DOM interface abstractions if possible.
D3’s core contribution is not its DOM model but the math it brings to the client — things like the Reingold Tilford tidy tree layout algorithm and a novel alternative to embedded spring forces on force layouts. What if we could use that math and delegate all the rendering to React? If we could componentize everything, it would mean writing SVG as markup and getting the designers back in the game. React would take care of the rest under the hood, and it would probably mean D3 could be broken out into nice little modules that simply return math. It seemed promising, so I set off to try it.
Here are three of the results. These keep things simple to show the concept – no transitions, no enter()
or exit()
or transition()
– those will be covered in the next two blog posts. This is just an aperitif.
## A Dendogram
How it works:
- Go fetch the data
- Pass the data to the instantiation of a component called
<Tree />
<Tree />
sets up the layout (i.e. functions that return the math)- On
render
, construct the SVG by callingthis.drawNodes()
andthis.drawLinks()
… - …which map over the nodes and links data respectively, instantiating a
<Node />
and<Link />
component for each element in each array - The
<Node />
and<Link />
components take props, and fill in the attributes.
SVG gets to be SVG, which is much more concrete. Previously, I had to keep that in my head. Also, no more lambdas because props.
## Force Layout
How it works:
- Go fetch the data
- Pass the data to the instantiation of a component called
<Graph />
<Graph />
sets up the layout (i.e. functions that return the math) oncomponentDidMount
and calls.start()
.start()
forces the component torender
on every ‘tick’- On
render
, calldrawNodes
anddrawLinks
, which map over the arrays of links and nodes produced by D3 on each tick and instantiate a component for each - The
<Node/>
and<Link/>
components contain the SVG markup
I’m not completely sure whether or not there are performance gains achieved by React. Since the components are subject to diffing, it’s possible only the attributes are changing. At any rate, it seems to be roughly on par with D3’s frame rate.
## Treemap
Conclusion
I’ve looked at, and am not a fan of, many of the D3 & React strategies out there that simply wrap D3 in components. They ignore the hard problems and customizability of D3 that we’ll cover in Part II: Transitions. I’m incredibly excited for this future, especially because it means using Radium for computed styles as well. All together, this yields a concrete yet highly expressive toolchain for interactive information design.