If you’ve ever built a graph from scratch before, you know it might not exactly be a walk in the park. At Smartcar, we wanted to create a graph that would showcase our users' API usage over time and give them insights into the requests they've made, so we confronted this issue head-on.
And here's how you can do it too:
Getting set up 👩🏫
To kick things off, you’ll want to create a React component for your line chart with a few familiar lifecycle methods: `constructor`, `componentDidMount`, `componentDidUpdate`, `componentWillUnmount`, and of course `render`.
- First, let’s add an `<svg>` element, specify its dimensions, and give it a name. This will let React add our line chart to the DOM and make it easily accessible to D3. Since we’ll want it to be responsive, let’s have it fill the 100% of the available width. Now would also be a good time to start a `d3Config.js` file where you can keep track of various graph configuration options, like the height of your line chart.
- At Smartcar, we use Redux to pass shared state into our connected components, so in this example we'll assume that your `timeSeriesData` is fetched from another component and passed into `<LineChart>` as props. In `componentDidMount`, we'll want to build the framework for our chart, so let's create `d3Utils.js` and plan to add a method called `initializeChart` there to handle this. This function will be called with your time series data and the time frame over which you'd like to initially graph your data, like `monthToDate` or `oneYear`. For simplicity, we'll assume this is `monthToDate` at first.
Now you'll have something like this:
Building with D3 📈
Now we're ready to start building out `d3Utils.js` and actually put the pieces of our line chart together. Since we're building a time series graph, we'll be working a lot with dates and timestamps. At Smartcar, we wrap a library that can handle this, like moment.js or date-fns, in something we'll refer to as `dateUtils`. Alright, let's get started by creating the most fundamental part of the graph — the scales and axes.
- For our x- and y-scales, we'll need to set the domain (the set of possible input values) and the range (the set of possible output values). We'll also need functions that will be able to place our data points appropriately on the axes with these scales.
- For our x- and y-axes, we'll need to specify the orientation — top, left, bottom, or right — and the number and format of ticks on the axes.
- As we're building a time series graph, our x-axis will handle dates and our y-axis will handle numbers.
Now that we've set up these elements for our line chart, we'll also need to use D3 to specify their place on the DOM. Adding the axes and line to our graph will look something like this:
Awesome! Now we're ready to put it all together. Let's attach our axes and line to their designated DOM elements. Finally, let's write the method we mentioned a bit earlier: `d3Utils.initializeChart`.
Dynamically graphing data 🤓
In the previous two sections, we set up a basic time series line chart and rendered it in our React component. Now, let's enhance the dynamic capabilities of our graph and enable it to update when new data comes in. As you might have guessed, this starts with the lifecycle methods of `<LineChart>` , specifically `componentDidUpdate`. In this function, we'll watch for changes in our data and trigger readjustments when necessary.
Now that we have the React side of things ready to go, let's write our `d3Utils.handleNewData` method to make the necessary adjustments to the graph itself. When new data comes in, we'll mainly want to check for two things:
- If the time frame of the data changes, we'll need to make a change to the x-scale. The start and end values which we initially set to span the domain of our x-axis will no longer fit our new data, so we'll want to write an `adjustXScale `method to handle this.
- If the possible domain of values on the y-axis changes, we'll need to make a change to the y-scale. The maximum that we previously set for our y-scale's domain may no longer be appropriate to best display our new data, so we'll want to write an `adjustYScale` method to handle this.
Here's an example of how you might want to do that:
Finally, we can write our `handleNewData` method with the functions above. We'll have to re-draw the elements of our graph to render the changes on our line chart.
Voilà! Now your time series line chart can dynamically update to showcase the most recent data available.
Making your line chart responsive across browsers 📱
The final optimization we'll detail today is how to make your graph responsive across browsers. Because the scale of our x-axis is determined by the width of the graph, we'll need to adjust this accordingly for different screen sizes. To handle this functionality, let's first make our final changes to `<LineChart>`. We'll need to add an event listener to our component in order to detect changes in screen size (and remove it when the component unmounts) as well as write a method to set the new width. After all these changes have been made, our component will look something like this:
And last but not least, let's write `d3Utils.setWidth`. In order to get the current width of our line chart, we'll simply find the SVG on the DOM, measure its width, and adjust the range of our x-scale accordingly.
And there you go! Now you have a dynamic, responsive time series line chart built in React and D3. 👏👏👏
Bonus features ✨
At Smartcar, we've added a few extra bells and whistles to spice up our usage graph a bit. Give them a try or add something totally new to your shiny new line chart!
Here's a few of the ideas that we've tried:
- Draw the line with a gradient stroke to make the edges fade
- Add a filter to give your line a drop shadow
- Dynamically render ticks on your x-axis based on how long your time frame spans and how wide your graph is
- Style elements of your graph using CSS
We hope this helps all you developers out there the next time you need to create an awesome time series line chart with D3 and React. Thanks for reading and happy building!
P.S.: Interested in what else we're up to? Here's a secret: We're hiring! 🤫