D3 and React-Native — An Essential Guide to Line Graphs

Your guide to making beautiful, animated, line graphs in React Native

Daniel Friyia
Better Programming

--

Photo by Isaac Smith on Unsplash

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.

Our final result running on iOS

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.

Starting Canvas Running on iOS

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.

Resized Canvas Running on iOS

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:

Canvas with Graph Lines on iOS

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.

A Simple Graph Running on iOS

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.

Pre-Animated Chart running on Android

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

Our final result running on Android

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.

--

--

Daniel is an Agile Software Developer specializing in all forms of mobile development, Native iOS, React-Native and beyond