D3 and React-Native — An Essential Guide to Line Graphs
Your guide to making beautiful, animated, line graphs in React Native
D3.js is the best, most powerful tool we have in React-Native (and maybe even all mobile development) for creating charts and data visualizations 📈. If there is any doubt in your mind I recommend checking out the D3 gallery.
The amazing amount of variety in the gallery demonstrates the versatility of D3 when it comes to visualizing data. D3 is the way to go if you want full freedom over what your data visualizations look like.
I was inspired to write this article because there aren’t many resources that exist on D3 line charts let alone D3 and React-Native. I assume that readers of this post are already familiar with SVG Animations in React-Native.
If not, you can learn about them from my last post here. I want this article to serve as a guide for React-Native developers looking to get into D3 for themselves and start crafting beautiful charts.
What are we Building?
As always with my how-to guides, I like to start by showing what we are building. We will be creating the line chart you see in the GIF below complete with the animation between line graphs.
Getting Started
Unlike my other tutorials, I am going to start this one from a boilerplate. If you want to follow along, you can clone the starter project here. You should be in the main
branch at this point.
There won’t be anything to set up since I’ve already installed the ReAnimated and SVG libraries for you. All you need to do is run yarn install
and then cd ios && pod install
in your terminal from the root of the project. If you get stuck and want to view the solution, check out the solution
branch.
Note that the boilerplate does not include all imports. You’ll need to import library values as you go with cmd + .
in your VSCode editor. Your project so far should be an empty card with some buttons that looks like this.
Familiarizing Ourselves with the Data
Let’s start by familiarizing ourselves with the structure of our data. Each point has these values:
export type DataPoint = {
date: string;
value: number;
};export const originalData: DataPoint[] = [
{date: '2000-02-01T05:00:00.000Z', value: 250},
{date: '2000-02-02T05:00:00.000Z', value: 300.35},
{date: '2000-02-03T05:00:00.000Z', value: 150.84},
...
All the data for this app can be found in the Data.ts
file under the src
directory. Notice the dates are saved in ISO-8601 string format. This is because ReAnimated for Android isn’t able to handle JavaScript date objects well. The only way to pass our dates to ReAnimated
later on will be in the form of ISO date strings. Our values are numbers representing real-life dollar amounts.
Organizing our Data
To build a chart, we need to organize our data so it can be handled by D3. The best way to start things off is to create a method that returns an object with our processed chart data in the App.tsx
file.
const makeGraph = (data: DataPoint[]) => {};
The next step we’ll take is calculating the max and min values in our dataset. These values are used to form the y domain of our chart. This is pretty simple to do, we can just feed the values of our data into the Math.max
and Math.min
methods.
const max = Math.max(...data.map(val => val.value));
const min = Math.min(...data.map(val => val.value));
After that, creating the y axis is as easy as calling scaleLinear
in the D3 library. Note the use of GRAPH_HEIGHT
in the range. This will ensure all our data points are mapped to y coordinates on the screen that is no greater then the graph’s height.
const y = scaleLinear().domain([0, max]).range([GRAPH_HEIGHT, 35]);
For the x-axis, we know our data is always going to be fifteen days so we’ll set the starting point and ending points at the first and fifteenth of the month respectively. Note the use of GRAPH_WIDTH
in the range. Like the y axis, it maps our values to x coordinates on the screen and makes sure they are no greater than the graph’s width.
const x = scaleTime()
.domain([new Date(2000, 1, 1), new Date(2000, 1, 15)])
.range([10, GRAPH_WIDTH - 10]);
We then finish off by creating the line chart using the line
function from D3.
const curvedLine = line<DataPoint>()
.x(d => x(new Date(d.date)))
.y(d => y(d.value))
.curve(curveBasis)(data);
We return the max
, min
and curvedLine
values from our function. They will be needed soon to draw our chart.
return {
max,
min,
curve: curvedLine!,
};
Altogether, our makeGraph
function should look like this:
Drawing the Graph
The next thing we’ll do is draw our graph. Let's start by adding our first data set to the array of graph data sets in App.tsx
.
const graphData: GraphData[] = [makeGraph(originalData)];
Since graphData
is already passed into the LineChart
component, lets jump back over into the LineChart.tsx
file. Start by de-structuring all component values in the method header.
const LineChart: FC<LineChartProps> = ({
height,
width,
data,
bottomPadding,
leftPadding,
}) => {
When we called the line
method earlier, D3 took in a set of domain and range values and used them to construct an SVG path. Our chart then is really just an SVG we display on the screen. To host our line graph, we add the SVG canvas to our LineChart
component.
<Animated.View style={styles.chartContainer}>
<Svg width={width} height={height} stroke="#6231ff"></Svg>
</Animated.View>
At this point, we’ll see our graph snap into its proper height.
Now we’ll add some gridlines to make the graph stand out a more. We can do this by drawing a few SVG lines.
Note the use of x1
, x2
, y1
, andy2
values here. The first value of x or y is the coordinates of where you want the line to start. The second value for the coordinates is where you want the line to end. Our result will be this empty chart:
Finally, the moment has come, let's place our graph on the canvas. Add the following code to the file under the lines we drew earlier.
<Path d={data[0].curve} strokeWidth="2" />
The result will be a graph that looks as follows.
Your code for the LineChart
component so far should look like this:
Animating Multiple Graphs
That was exciting! Not only did we draw our graph to the screen, but we were also able to add guidelines for good measure.
The next phase of this article will focus on switching between multiple line charts and animating those transitions. This section is only possible because of the react-native-redash
TypeScript library authored by the Godfather of React Native animations William Candillon.
To start the process of animating our graph, start back in the App.tsx
by adding the other datasets to our array of graphs:
const graphData: GraphData[] = [
makeGraph(originalData),
makeGraph(animatedData),
makeGraph(animatedData2),
makeGraph(animatedData3),
];
You will also need to run redash parse
on the curve attribute of makeGraph
and adjust your types accordingly.
import {parse, Path as RePath} from 'react-native-redash';
...
export type GraphData = {
...
curve: RePath;
};const makeGraph = (data: DataPoint[]) => {
...
return {
max,
min,
curve: parse(curvedLine!),
};
};
Navigate back to your LineChart.tsx
file and create a sharedValue
for the graph you’ve currently selected. This is also a good time to make a function that can transition between graphs (without animating). Altogether the code should look like this:
const selectedGraph = useSharedValue(data[0]);
const onQuarterTapped = (quarter: number) => {
selectedGraph.value = data[quarter - 1];
};
const animatedProps = useAnimatedProps(() => {
return {
d: selectedGraph.value.curve,
};
});
const q1Tapped = () => onQuarterTapped(1);
const q2Tapped = () => onQuarterTapped(2);
const q3Tapped = () => onQuarterTapped(3);
const q4Tapped = () => onQuarterTapped(4);
You can also add these functions to the button section at the bottom of the file:
<ButtonSection
q1Tapped={q1Tapped}
q2Tapped={q2Tapped}
q3Tapped={q3Tapped}
q4Tapped={q4Tapped}
/>
Your code will appear like this so far.
At this point, you’ll have a chart that can jump between graphs like in the GIF below.
Obviously, this looks a little weird, so let's add in the animation now. Back in LineChart.tsx
we add a state for our current graph, the graph we are transitioning from, and the status of our transition.
const selectedGraph = useSharedValue(data[0]);
const previousGraph = useSharedValue({...data[0]});
const isAnimationComplete = useSharedValue(true);
const transition = useSharedValue(1);
We will need to update the animatedProps
to reflect the transition between these graphs. The new code will be as follows:
const animatedProps = useAnimatedProps(() => {
return {
d: mixPath(
transition.value,
previousGraph.value.curve,
selectedGraph.value.curve,
),
};
});
Note the use of the function mixPath.
This redash
function interpolates two paths with an animation value that goes from 0 to 1. Let’s finish by updating our onQuarterTapped
function to animate from one state to the next.
The above code does a number of things, so I’ll break it down here. First, we have an if statement that blocks new animations from happening if there is already an animation in progress.
If there are no active animations, we set the selected graph as the graph we tapped on.
At this point we use the withTiming
to slowly increase the transition value to 1 which allows mixPath
to animate the chart. Finally, when the animation is complete, we set isAnimationComplete
to true so that the user is free to tap the buttons again and transition to another chart.
Bonus: Animating Text
Let’s finish up by animating the text at the top of the screen where we see the dollar amount. Start by adding a mostRecent
property to the makeGraph
function. You can do it like this:
const makeGraph = (data: DataPoint[]) => {
...
return {
max,
min,
curve: parse(curvedLine!),
mostRecent: data[data.length - 1].value,
};
};
We can then create a derived value in LineChart.tsx
so that this string is visible to the UI Thread.
const mostRecent = useDerivedValue(() => {
return `$${selectedGraph.value.mostRecent}`;
});
Finally we can use ReText
from react-native-redash
to show the value on the UI:
<View style={styles.titleContainer}>
<Text style={styles.titleText}>FACEBOOK</Text>
<ReText style={styles.priceText} text={mostRecent} />
</View>
Our completed code is as follows:
Our final result is the graph in this GIF
Final Thoughts
Wow that was a tough one! D3 definitely has a steep learning curve and can be a bit tricky at times. I hope by using this article you were able to get through this exercise and learn more about how to use it.
I’d love to see the React-Native community rally around this technology and cast more light on fun and interesting charts we can add to our apps. Until next time, happy coding.