Author’s note: I’ve followed up on this post with another one. I’m no longer a fan of building up SVG elements in React components the way I propose in this post. You should read the other post to see what I recommend.
Imagine the entirety of your organization’s chatroom communications. Imagine making sense of those communications in a single interactive visualization, one that factors in date and time, chatroom name, individual participants’ names, and message content.
I recently implemented just such a feature. While something like this of course requires back end analytics, aggregations of data, and “data science” that can handle such “big data,” it also relates to user interface (UI), the subject of this blog post.
Until recently, this app’s client-side UI was built entirely in Ember.js, a framework intended for “ambitious” applications (and thus a good fit!). Over time, however, the UI team came to realize some of Ember’s limitations, some of those conventions and patterns inherent to the framework that—rather than making developers’ lives easier, as is any framework’s aim—posed challenges to the organization and maintenance of our codebase.
Enter React.js, a UI library that solely addresses issues in the view layer. Over the last 5-6 months, we have been porting Ember code over to React, started using React for all greenfield components, and made React the standard for our UI. This blog post won’t cover the litany of (fiercely debated) pros and cons of Ember vs. React, but suffice it to say that React has made us on the product development team unanimously happier.
All of that is just background to the feature I initially described, because a data visualization isn’t implemented solely in Ember or React. Or is it?
The old way
d3.select('body').append('svg') #... and, from there, append rectangles and lines, bind click and hover actions, etc. Not so different from a basic jQuery application (
That said, what D3 ultimately produces is a series of DOM elements, specifically SVG elements. Some basic D3 code might look like:
1 2 3 4 5 6 7 8 9 10 11
That code then maps to SVG elements in the DOM, looking something like this:
1 2 3 4 5
There are multiple ways to wire D3 up to a given web framework, but it’s ultimately a script that runs to build the component in the DOM. Our old pattern was loosely the following:
- fetch model in the route
- set up component properties in the controller
- render the component in the template:
- in Ember’s
didInsertElementhook in the component, run the D3 script that selects body and appends SVG
Until recently, we had been able to maintain and reuse our Ember D3 components, but this chat timelines visualization required a brand new D3 component, one we decided to write in React.
My initial instinct, as with simpler React components, was to render the component with properties and run the D3 script in React’s
componentDidMount hook. What became clear, however, was that we didn’t need to run the D3 script at all. In place of
d3.select(...).append(...) we could simply build up svg elements in the
This approach, while going against my initial instinct of using D3’s pattern, aligns well with React’s strengths of one-way data flow and components that are easier to reason about than traditional data binding. It’s a declarative approach that expresses what it does, as opposed to an imperative approach that expresses how it’s done. And it has benefits of composibility and extensibility—rather than selecting and appending as additional design specs come in, we can componentize everything—bars, axes, labels, plots—to reuse later or modify with greater control.
And that earlier question about data visualizations being written entirely in a framework? Considered this way, we can construct the SVG elements directly in React, something like this:
1 2 3 4 5 6 7 8 9 10
You can pretty quickly see how the inner rectangles could be pulled out as components of their own, as could axes, labels, etc. We’ve found this pattern to be much easier to reason about when building visualizations in our UI. So here’s to rethinking UI patterns and, as a result, writing code that’s easier to reason through.