D3.js, React, and the struggle for the DOM
If you’re interested in the intersection of data analysis, visualization, and interactivity, your go-to library is probably d3.js.
React is becoming increasingly popular, so how do you best integrate d3.js into React? Let’s explore the consensus of many lectures, projects, and blog posts on how to best use these two libraries together.
Wrestling for control over the DOM
React and d3.js are both declarative, in that they encourage you to specify what needs to be done, rather than how to do it.
They are different, however, in their approach, with d3.js being more functional, and React being more object-oriented. This difference a lot of enthusiasm about linking both libraries.
In his blog, Nicolas Hery argues:
I think that using React and D3.js together makes sense because they share a common philosophy of “give me a set of data, tell me how it should be rendered, and I’ll figure out which parts of the DOM to update for you.” Indeed, React has its virtual DOM diffs, and D3.js has its update selections, making both quite efficient in the business of keeping the UI in sync with data changes.
But there is one thing: both React and d3.js try to leverage use of HTML and CSS resources guided by the DOM hierarchical representation. The problem is one of miscommunication, where d3.js and React could end up fighting for the same resources without notifying each other.
If you give an element React created to D3 and say “hey, D3, set the width to 100, thanks” it’ll happily oblige. Then React will notice and get pretty upset that you went behind its back and messed with its perfectly reconciled DOM. This isn’t great, as you can imagine.
The problem of combining React and d3.js become more evident when interactivity is involved. A question for the designer seems to be how do I design the hierarchical structure so these conflicts are avoided or at least reduced?
When it comes to web designing, React is master and d3.js is servant: probably an insurrected one. As a general rule the good design practice should consists in passing data top-down, being the top level the “only source of Truth”. Furthermore, all dependent components should be stateless (i.e. rendering the same output when getting the same input) and not make assumptions about handling exceptional cases. However that doesn't seem to be enough.
When solving the DOM control drama, I found there were at least 2 ways :
- If the servant tries to steal the master, let it be but chained! This is a case very similar to what you would find in angular 1.3, using custom directives for d3.js.
- If the servant tries to steal the master, just cut it in pieces and use what you need! In this case, people use only those aspects of d3.js that don’t conflict with React and use React to render the viz.
Let's have a look at them with examples before a short discussion.
The chained thief servant
As I mentioned this method shares some similarities with angular 1.3 directives. In this case, d3.js is confined to a component very low in the hierarchy but React conceded to it the DOM control. If you know Angular 1.3 you will find some similarities between this strategy and the use of directives for d3.js.
In both projects:
- Only a single visualization frame was inserted,
- There were few React controllers, all independent between them,
- there was no extra manipulation of data, and
- d3.js had full control of the DOM
From both, the Nicolas Hery’s project is the most elaborated and React-ified, with several controls. In order to solve the communication between React and d3.js Hery tried what it seemed to me a marshalling-like technique: shadow functionalities between React top level and d3.js level that simulated the d3.js enter-update-exit cycle. So every time it was a change at React (the top level only Source of Truth), that change was passed to d3.js through the right channel (either an enter event, update event, or exit event).
Although the Hery’s demo looks great, it comes with the discomfort of having to create an interface between React and d3.js to solve the miscommunication. This seems to be a relevant challenge for complex visualizations: how many interfaces should be then created?
Perhaps it is that by thinking on those potential issues some people prefer not to have any mercy: if it is misbehaving, just chop it off.
Chop off the misbehaving servant!
When it is about keeping the subject in its place, some serious people opted for letting React control the DOM and not to use d3.js DOM manipulation functionalities. The first targets are d3.js selections and transitions. Others go a bit further and only take from d3.js the parts that are peripheral to the whole viz, like axes, tips, legends, layouts, etc., while letting React to handle the most changing elements.
You’re looking at a histogram of salaries given to H1B workers in the software industry. You can filter by year, job…swizec.github.io
(NOTE: Unfortunately the Nation/Dato project doesn’t have a live demo; you should have to install it and watch it locally.)
In both projects:
- the number of controllers is larger or the dependency is higher between them than in the previous demos, with controllers used for either filtering or faceted search
- they both try more realistic data, requiring some pre- and or post-processing
Teller used a demo for faceted search visualization as the main example for his book, React + d3.js (2016, leanpub.com). Although worth looking at, his demo had some undesired behaviours. The resulting demo speaks of the challenges of creating a workable project when implementing faceted controllers with d3.js + React.
Nation/Dato demo is more extensive in terms of data manipulation and results. For their demo they formulated a simple server architecture that looked like Flux as “the only source of Truth”, coupled with a data visualization flow model consisting in 3 steps:
- (Data) Aggregation: there is a pre-processing of the data before injected into lower visualization components
- Visual Elements: here, only those d3.js features that are peripheral to the visualization are called
- Rendering: then React is used to take processed data and selected visual elements and "injects" them as attributes of the rendered objects (eg. SVG elements) in the form of anonymous functions
The project is worth looking at, being an excellent example for this case of React+d3.js coupling technique. In fact it has to be seen not only in terms of how React+d3.js could work together, but also as an example on how to design a data visualization application in general.
If it is about summarising how to make d3.js to work in the React world, you should watch the presentation by Benjamin Malley for the Midewest JS group.
Malley was interested in making charts as interaction interfaces for faceted search models. Malley is not prescriptive but he does argue that implementing full d3.js in React using bootstrapping techniques is close to an anti-pattern. On the other hand, for implementations where React becomes the rendering functionality he suggests that instead of just inserting events in the form of anonymous functions a better approach could be to disaggregating the design into scope-agnostic wrapper functions that could be called wherever they are needed, making functionality more explicit, generalizable and modifiable. If intercommunication between children and parents is required, he also suggests to make a substantial (ab-)use of event handlers between them. However, he recognises that this might not be always the best approach.
An interesting library going in the wrapper direction seems to be victory.js.
Actually, although none of the references mentioned, it appears to me that this battle will be settled in favour of React more than d3.js: I think the future is bright for React-based libraries specifically dedicated to data visualization, where d3.js may or may not be part of them.
Meanwhile, there is a point at which you as web designer would have to decide about the pertinence of using any of those tools for a visualization project, and when there is no other option then you would have to think about how to make them work together.
If you don’t have other option, I personally think that the bootstrapping/marshalling methods shouldn't be neglected, but they are suitable for relatively small projects with a careful selection of not-interdependent controllers. However as the project grows in complexity a more Flux-like architecture as only source of truth could be beneficial, as well as more React-based rendering.