A simple dashboard using React hooks and D3 — Part I

In this tutorial I will explore the possibilities offered by ReactJS and D3 together to create a simple dashboard, an integration of three charts types that will respond to user interactions changing their base of data. Most people call the method explained in this article as “D3 for Maths and React for DOM manipulation”. I personally prefer using this approach since it keeps the two tools separated in their purposes and takes advantage of React state management benefits without modifying the original components design pattern. In this tutorial we will use the React hooks API, introduced in version 16.8, this will allow the creation of functions to define our components avoiding classes and life cycle methods.

This simple dashboard example is not an original personal work but is based on an HTML/JS/D3 example that can be found here. We will port its code in React and replace its main design components with jsx and state management.

The source code for this finished example can be found here.

And below it shows how it will look like when it is finished.

What we need to build the application

ReactJS: In this example we assume that you already have a basic setup for a React application. If you do not know how to set this up I recommend you use create react app, this is what I used in this example. Since we are going to use hooks we need to have a ReactJS version higher than 16.8, in this example we use version 16.3.1.

D3: famous data visualisation JavaScript library that we will plot our charts using the DOM, documentation here.

The approach we are going to use in this tutorial will not include more libraries. As said earlier, this will allow us to integrate the two tools and let them do what they are mainly designed for (D3 manipulates the DOM and React manipulates the data model). The version used in this example is 5.16.0, to install D3 we will do.

npm install --save d3

The dashboard structure

In our original HTML/JS dashboard there are three types of chart (pie, line and bars), static and dynamic text (text that changes in reaction to events) and animated transitions that change the appearance of text and charts according to the focus on a particular piece of data. Easily enough, the only user interactive elements are the slices of the pie chart, which we will call Donut, since in its hole is displayed the text that will change to reflect the user slice selection. The selection on the slice will filter the data focus and animate the other two charts, representing the filtered data divided by different levels of aggregation, total of sales revenues divided by selling items.

The React version of the dashboard in detail

In this example we make our life easier and we will keep the data separated by chart. For those of you having React experience it will look obvious to translate each chart in a React component, which in turn can be broken down into smaller components. Moreover, since it is the Donut chart the only one that users can interact with, it makes sense to design this as the only stateful component, all the others will be stateless. Here is where React hooks come handy, we will create the Donut chart component as a function and not a class as we would do with state management. We will use useState to “hook into” the component state and manipulate it. We will propagate the Donut component state changes to the stateless components using props; these will react to props changes with the useEffect hook (that we will design) and will update their display whenever a state change occurs on the Donut component. Since both React and D3 work applying changes to the DOM we will split the work between the two. First, we will let React pass properties across components and change them if they do not require an animation in the render method. D3 will come into action in the useEffect hook, called after the render has finished, changing the appearance of the charts using transitions. Now, let’s dive into the code defining the parent App component that will handle all our children components together.

The App parent component

This component groups all children in the viewBox, the svg viewport of our dashboard. The values given to the this property will set our svg coordinates starting in a slightly lower/right position (not so close to the page border) and having pixels as measure units (no unit measure defined defaults to pixels). Since the Donut component events change the application state and this will be passed down to the children, it also makes sense to design a function that will hook into the App component state and modify it. Modifications to the App component state will automatically change the children component props and trigger a re-render, like in any other ReactJS application regardless of the design of its state management. The App.js component looks as follows.

The Donut Chart Component

We could describe the Donut component as a Pie chart with as many slices as the number of items in a data array. The slices are arc shaped svg <path> elements with an inner radius property larger than 0 (to create the donut hole) and start and end angle properties which will be proportional to the quantities expressed in the data passed as props to the component. We won’t have to worry about translating the data into arc angles since this is will be done by d3 with the pie() function. All we have to do is to wrap the svg DOM nodes in React components and pass the data as props, d3 will take care of drawing the geometries and animate them when needed.

Our Donut chart component takes advantage of pie function. We have got to define the .innerRadius and .outerRadius of our slice in a new arc d3 object. The pie function generates a JavaScript object which properties will be used by d3 to draw the arc we have just created as an SVG path element, storing the calculated values in the d attribute of our path node.

/*creation of a new arc object*/const sliceArc = arc()
.innerRadius(innerRadius)
.outerRadius(outerRadius);
/*definition of the path (value are the props passed from Donut Component)*/<path d={sliceArc(value)} />

In the original version of the dashboard the Donut component animates when we hover in/out each slice, the hovered slice inner radius changes to show the slice the mouse cursor is currently on and it changes back to its original shape as soon as the mouse leaves the slice.

In the slice component state we store for each slice the value variable which is the object passed from the parent component (the Donut) and containing all the information that d3 needs to draw the arc path. Whenever we mouseenter/mouseleave we change its state using the React hooks setHoveredlSlice/setUnohoveredSlice, we record the hovered slice, the unhovered one and we subsequently trigger useEffect. This hook is triggered after the slice re-rendering and in this context makes up for the componentDidUpdate life cycle method. In fact, it will only be triggered when the hoveredSlice and unHoveredSlice props change, this can be controlled by the second argument of the hook, an array of variables which state change will make it trigger, if the prop is in the array useEffect will fire up, if the prop is not in the array it will not. In useEffect we invoke animateSlice, we pass to this function the slice DOM node we refer with useRef(). With d3 we create an arc path with the same geometry as the hovered one but with a larger inner radius and assign its geometry svg path definition to the referenced slice arc path with the d attribute. Using a transition we will animate the change of the inner radius, making it look as the slice is expanding towards the centre of the donut. The mouseleave browser event on the slice will use the same routine as mouseenter, this time instead of a larger inner radius we pass to animateSlice the original value for the unhovered slice. It is important to point out that the animateSlice function is not defined within the slice component because it does not use any variables of the component state, this is not a requirement but it feels more appropriate to keep it separated as it doesn’t actually read nor manipulate the slice component state and we do not need to declare the same function each time we render it.

Besides the hover animations we define the main slice user interaction, responding to a mouse click, passing the label, fill and value props to the Donut component, using the onClicklSlice function. In the Donut component we use hooks to set the text appearing at its centre, having label and value.data as text and percentage of the clicked slice and fill as text colour (the same of the clicked slice). Moreover, defining the onChangeGroup function we will climb up to the App component (the parent) that will use the label and fill props in the Donut siblings components, the bar and line chart that we are going now to design.

For readability reasons I preferred to keep the creation of the three components separated, you can follow the tutorial clicking on the titles.

The Bar Chart Component

The Line Chart Component

Conclusions

It took me a while to understand how to integrate React and d3 and how hooks can be implemented in this process, I admit it is not the easiest concept to learn. For me the hardest bit is to “break free” from the d3 pattern of creating svg elements (mainly with the select command). Using jsx will force us to have a “vision” of how the svg element will ultimately look like and how it will interact with its parents/children since we do not want to manipulate it anymore with D3 in our code. I think the example provided offers a decent result with maximum simplicity, it does not include complicated animations but it can certainly be a base for you to start building more fancy ones, I hope you enjoyed this tutorial and please leave questions or comments.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store