React + D3: A Starter’s Guide

At Megagon Labs, I create demos to clearly showcase the hard work of our natural language processing/machine learning researchers and engineers. In all my demos, I relied heavily on React. Its declarative approach, state management, and control over the DOM made sense to me. Particularly for data-intensive machine learning (ML) applications, React efficiently updates state and renders DOM elements as much as is needed upon receiving rich data from ML backends. 

My React applications range from stepwise API demos that illustrate how a specific function is performed to more functional prototypes that demonstrate how a specific technology could be implemented in a product. For example, in the Voyageur demo, our subjective search technology was applied as part of a travel experience, search-engine prototype. But often, ML results are best explained through data visualizations. Let’s take Extreme Reader, an interactive review summary explorer, as an example. Extreme Reader required a node-link diagram to visualize tens of thousands of reviews. This visual aid would demonstrate trends in opinions, as well as explain the connections between each trend. Being new to data visualization, I searched for visualization libraries available. While I came across libraries especially made for ReactJS, one that I found to be the most popular and suitable was D3.js, also known as Data-Driven Documents. 

During the data visualization implementation process, I learned a few things about integrating D3 into a React project. Mainly, I learned there are many ways they can work well together, though their integration comes with tradeoffs. While this isn’t a tutorial and I won’t go into specifics, these tips are meant to help you in preparing for implementation.

Why D3?

D3 has an amazing capacity for creating rich data-intensive interactive applications. Its ability to bind data to SVG elements, create a data-driven layout, style, and transitions can be used to create a variety of interactive charts and custom visualizations. All these capabilities make it much more appealing to use. D3 also gives you the ability to customize each aspect of your chart with HTML and CSS. D3’s API grants you full control of your visualization’s look and functionality, and it works efficiently to create data-intensive applications, suiting our needs in building machine learning tech demos.

The Challenges of Pairing React and D3

React and D3 pair well, but coupling them may not be straightforward for some React developers. There are basics we need to understand before diving into our first D3-React application.

The DOM is where it gets tricky to implement a D3-React application out of the box. Both D3 and React want full control of the DOM for efficient rendering of elements. While D3 directly makes changes to the JavaScript DOM, React has the ability to limit the changes to the real DOM by first using a virtual representation of the DOM (virtual DOM) to track changes. React then triggers the changes necessary in the real DOM. In this scheme, the real DOM doesn’t have to re-render the entire page, or chart in our case, upon every individual change. React’s use of the virtual DOM makes for faster changes without overloading the browser. 

Let’s discuss below some options for divvying up control of the DOM to utilize the best of D3 and React.

Preparing to Implement D3 in your ReactJS Project

Decide how to structure your code.

Since DOM manipulation is one of the biggest selling points of React, it may not be so straightforward for developers to oversee DOM manipulation for D3 components. Having proper component structure will help you significantly as you will need to override state management and event processing.

Have a clear list of needs.

How you divvy up DOM manipulation creates a sliding scale of benefits. There are many ways of implementing D3 in React, but with each choice, you gain some functionality and lose others. In a sense, you can’t have everything of both; there are tradeoffs.

Here are some options for your React D3 project set up:

React Faux DOM

React Faux DOM is a npm package by Oliver Caldwell. It’s a way to keep most of D3’s utility and combine it with React. The faux DOM is placed to allow D3 a virtual DOM so you can still access all of D3’s API. At the same time, React remains in control of the virtual DOM and ultimately the real DOM, allowing it to control state and transitions. Like we said before, there are tradeoffs. The downside is the weight of having an extra library to install. With this option, extra DOM, transition, animations, and interactions (dragging, zooming in, etc.) are limited. It makes it a heavy way of developing and possibly too limiting for a large data visualization. Caldwell and the contributors are currently working on making it more lightweight without loss of functionality.

React for the DOM and D3 for Calculations

In this option we will use D3’s data-driven layouts and style but data will be passed on to React for eventual rendering. Likewise, the user input will be taken in by React, recorded in state and passed to D3 for recalculation. The tradeoff: we lose the D3 transition suit and updating but are still able to have dynamically changing charts due to React’s DOM instant updates. Let me break down two ways to do this:

D3 within React

This method simply means writing all the D3 code within the componentDidMount( ) lifecycle method in a class or in the useEffect( ) React Hook within a functional component. With this method, you also set shouldComponentUpdate( ) to false so the chart isn’t updated any time the class gets updated.

				
					class NetworkGraph extends React.Component {
	constructor(props) {
		super(props);
		this.netGraph = React.createRef();
	}

	componentDidMount() {
		// D3 code here 
	}

	shouldComponentUpdate() {
		return false
	}

	render() {
		return <div ref = {	this.netGraph }/>;
	}

}
				
			

This method allows you to quickly and easily add a D3 chart to your project, but it severely limits the functionality of the chart. As you might imagine, adding all your code for D3 in componentDidMount( ) can also lead to a very lengthy and ugly file. The magic of React is modularization, so when using React, let’s modularize with Lifecycle Methods Wrapping explained in my next point.

Lifecycle Methods Wrapping

This allows more of a modularized implementation. It’s also the approach I took for Extreme Reader. If you are already good at D3 and a beginner to React, this method allows you to take a chart or visualization with which you are purely using D3 and easily implement it in your React project by importing the JavaScript class into a React container component. The container component then wraps the visualization in its React lifecycle methods.

reactd3structure

Sample React container component:

				
					const NetGraphContainer = ({ data }) => {
	const netGraphRef = useRef(null)
	const [graph, setGraph] = useState(null)

	useEffect(() => {
		if (!graph) {
			setGraph(new NetGraph(netGraphRef.current))
		} else {
			graph.update(data)
		}
	}, [graph, data])

	return ( 
	    <div ref = { netGraphRef }> </div>
	)
}

export default NetGraphContainer
				
			

A little about the container: 

We have a file with the D3 code and a separate file with a container component in React. This container-component file acts as a wrapper and imports the D3 code as a class. Our container serves to initialize and update the D3 chart with useEffect( ), comparable to componentDidMount( ) and componentDidUpdate( ) in class components. If the chart needs updating, the ref which links us to the D3 chart node will receive the new data and recalculate a chart. 

The ref created with the useRef( ) hook is for us to manage the chart between re-rendering. Pairing this with the conditional in the useEffect( ) hook, we have more control of when the chart is redrawn. We are not allowing React to automatically redraw the chart with every prop change. Instead, we pass a conditional that then dictates when it is re-calculated and re-rendered.

Additionally, we are holding the chart in state using the useState( ) hook. The chart state sets or updates when the useEffect( ) conditional is met.

Sample Graph:

				
					import * as d3 from 'd3';

class NetGraph {
	constructor(element) {
		//initialize graph
		let vis = this
		vis.g = d3.select(element)
		vis.update()
	}
	//update the graph when props change 
	update() {
		let vis = this
	}
}
export default NetGraph
				
			

The class component holding your D3 code receives the props to trigger updates and code for initializing the chart. In our constructor, we initialize our chart. This is code that only needs to be run once. After that, we have our update and our restart functions, both of which are triggered by the interactions to our chart. Our parent/container component informs the chart component when this occurs. Instantaneously, D3 works its layout magic to introduce, remove, and reposition any nodes being adjusted.

Conclusion

React and D3 are great tools to pair together for your visualizations within a flexible and fast UI. 

D3 is great with math, scaling, layouts, utilities, and more. Meanwhile, React excels at DOM manipulation, state control, and modularization. Being able to modularize your D3 visualizations makes them reusable in other projects and easy to read.

I believe, as a front-end developer, that learning D3 is a great skill to have especially as an engineer at an AI research lab. Our research team works tirelessly to bring about amazing results, and it is my job to translate those results to our community, clients, and stakeholders. For me, there’s no better tool than a versatile chart library that allows for creativity and has all the support I need to be successful in implementation. 

Follow us on LinkedIn and Twitter to stay up to date with us.

Written by Natalie Nuño and Eser Kandogan and Megagon Labs.

Share:

More Blog Posts: