We'll create React components for 3 chart types: line, bar and doughnut. The dashboard will also update if the data updates.
Background
React is one of the most popular JavaScript libraries for creating user interfaces. Likewise Chart.js is one of the most popular JavaScript libraries for creating charts. As we'll see in this tutorial the two libraries integrate smoothly.
Chart.js renders to the Canvas element which means we don't have to worry about which library manages the DOM. (React will take care of everything DOM related while Chart.js is responsible for drawing to a Canvas element.)
Chart.js has built-in support for tooltips, animation and pretty good support for responsiveness. Yes... this dashboard is responsive!
Our approach
This tutorial shows how to integrate React and Chart.js without the use of another library (such as react-chartjs-2). We're not going to use a wrapper in this tutorial because:
- we'd like to retain as much direct control over Chart.js as we can
- we'd like to deepen our understanding of integrating a charting library such as Chart.js with React
- we'd like to abstract away Chart.js's data structures in our chart components
- it's not all that difficult to integrate React and Chart.js
Our chart components will have an interface similar to:
<BarChartdata={myData}
title="My amazing data"
color="#70CAD1"
/>
where the function getFeeds() {
let feeds = [];
feeds.push({
title: 'Visits',
data: getRandomDateArray(150)
});
feeds.push({
title: 'Categories',
data: getRandomArray(20)
});
feeds.push({
title: 'Categories',
data: getRandomArray(10)
});
feeds.push({
title: 'Data 4',
data: getRandomArray(6)
});
return feeds;
}4 attribute is an array of objects:
{
"label": "Apples",
"value": 10
},
{
"label": "Oranges",
"value": 17
},
...
]
The complete code is in this Codepen pen. (I originally created this dashboard using create-react-app then copy and pasted the code into Codepen.)
Test data generation
We'll start by writing a function function getFeeds() {
let feeds = [];
feeds.push({
title: 'Visits',
data: getRandomDateArray(150)
});
feeds.push({
title: 'Categories',
data: getRandomArray(20)
});
feeds.push({
title: 'Categories',
data: getRandomArray(10)
});
feeds.push({
title: 'Data 4',
data: getRandomArray(6)
});
return feeds;
}5 that generates the test data:
let feeds = [];
feeds.push({
title: 'Visits',
data: getRandomDateArray(150)
});
feeds.push({
title: 'Categories',
data: getRandomArray(20)
});
feeds.push({
title: 'Categories',
data: getRandomArray(10)
});
feeds.push({
title: 'Data 4',
data: getRandomArray(6)
});
return feeds;
}
This function creates an array containing 4 datasets (or feeds). The first feed is an object representing a time series:
{"title": "Visits",
"data": [
{
"time": Tue May 01 2018 00:00:00 GMT+0100 (British Summer Time),
"value": 39
},
{
"time": Wed May 02 2018 00:00:00 GMT+0100 (British Summer Time),
"value": 60
},
...
]
}
The remaining 3 feeds are objects representing categorical data. For example:
{"title": "Categories",
"data": [
{
"label": "A",
"value": 46
},
{
"label": "B",
"value": 87
},
...
]
}
(function getFeeds() {
let feeds = [];
feeds.push({
title: 'Visits',
data: getRandomDateArray(150)
});
feeds.push({
title: 'Categories',
data: getRandomArray(20)
});
feeds.push({
title: 'Categories',
data: getRandomArray(10)
});
feeds.push({
title: 'Data 4',
data: getRandomArray(6)
});
return feeds;
}5 calls two functions function getFeeds() {
let feeds = [];
feeds.push({
title: 'Visits',
data: getRandomDateArray(150)
});
feeds.push({
title: 'Categories',
data: getRandomArray(20)
});
feeds.push({
title: 'Categories',
data: getRandomArray(10)
});
feeds.push({
title: 'Data 4',
data: getRandomArray(6)
});
return feeds;
}7 and function getFeeds() {
let feeds = [];
feeds.push({
title: 'Visits',
data: getRandomDateArray(150)
});
feeds.push({
title: 'Categories',
data: getRandomArray(20)
});
feeds.push({
title: 'Categories',
data: getRandomArray(10)
});
feeds.push({
title: 'Data 4',
data: getRandomArray(6)
});
return feeds;
}8 which generate arrays of objects. See the finished code if you're interested in seeing how these functions are implemented.)
Bar chart component
Now we'll create a component for making a bar chart. Our component will have 3 attributes:
<BarChartdata={myData}
title="My amazing data"
color="#70CAD1"
/>
The first attribute function getFeeds() {
let feeds = [];
feeds.push({
title: 'Visits',
data: getRandomDateArray(150)
});
feeds.push({
title: 'Categories',
data: getRandomArray(20)
});
feeds.push({
title: 'Categories',
data: getRandomArray(10)
});
feeds.push({
title: 'Data 4',
data: getRandomArray(6)
});
return feeds;
}4 is an array of data:
{
"name": "A",
"value": 46
},
{
"name": "B",
"value": 87
},
...
]
while {
"title": "Visits",
"data": [
{
"time": Tue May 01 2018 00:00:00 GMT+0100 (British Summer Time),
"value": 39
},
{
"time": Wed May 02 2018 00:00:00 GMT+0100 (British Summer Time),
"value": 60
},
...
]
}0 is the chart title and {
"title": "Visits",
"data": [
{
"time": Tue May 01 2018 00:00:00 GMT+0100 (British Summer Time),
"value": 39
},
{
"time": Wed May 02 2018 00:00:00 GMT+0100 (British Summer Time),
"value": 60
},
...
]
}1 is the bar colour.
Start by creating a simple class component named {
"title": "Visits",
"data": [
{
"time": Tue May 01 2018 00:00:00 GMT+0100 (British Summer Time),
"value": 39
},
{
"time": Wed May 02 2018 00:00:00 GMT+0100 (British Summer Time),
"value": 60
},
...
]
}2:
render() {
return <canvas />
}
}
This renders a single {
"title": "Visits",
"data": [
{
"time": Tue May 01 2018 00:00:00 GMT+0100 (British Summer Time),
"value": 39
},
{
"time": Wed May 02 2018 00:00:00 GMT+0100 (British Summer Time),
"value": 60
},
...
]
}3 element that'll contain our chart. We'll need to reference the Canvas element when the chart is created so add a React ref to the component:
constructor(props) {
super(props);
this.chartRef = React.createRef();
}
render() {
return (
<canvas ref={this.chartRef} />
);
}
}
(We're using the {
"title": "Visits",
"data": [
{
"time": Tue May 01 2018 00:00:00 GMT+0100 (British Summer Time),
"value": 39
},
{
"time": Wed May 02 2018 00:00:00 GMT+0100 (British Summer Time),
"value": 60
},
...
]
}4 API introduced in React 16.3.)
Now add a {
"title": "Visits",
"data": [
{
"time": Tue May 01 2018 00:00:00 GMT+0100 (British Summer Time),
"value": 39
},
{
"time": Wed May 02 2018 00:00:00 GMT+0100 (British Summer Time),
"value": 60
},
...
]
}5 method which instantiates a Chart.js bar chart:
this.myChart = new Chart(this.canvasRef.current, {
type: 'bar'
});
}
Note that the first argument to the Chart constructor is the reference to the canvas element.
Chart.js requires its function getFeeds() {
let feeds = [];
feeds.push({
title: 'Visits',
data: getRandomDateArray(150)
});
feeds.push({
title: 'Categories',
data: getRandomArray(20)
});
feeds.push({
title: 'Categories',
data: getRandomArray(10)
});
feeds.push({
title: 'Data 4',
data: getRandomArray(6)
});
return feeds;
}4 property to be structured like:
{
"label": "Apples",
"value": 10
},
{
"label": "Oranges",
"value": 17
},
...
]0
so we need to transform {
"title": "Visits",
"data": [
{
"time": Tue May 01 2018 00:00:00 GMT+0100 (British Summer Time),
"value": 39
},
{
"time": Wed May 02 2018 00:00:00 GMT+0100 (British Summer Time),
"value": 60
},
...
]
}7, {
"title": "Visits",
"data": [
{
"time": Tue May 01 2018 00:00:00 GMT+0100 (British Summer Time),
"value": 39
},
{
"time": Wed May 02 2018 00:00:00 GMT+0100 (British Summer Time),
"value": 60
},
...
]
}8 and {
"title": "Visits",
"data": [
{
"time": Tue May 01 2018 00:00:00 GMT+0100 (British Summer Time),
"value": 39
},
{
"time": Wed May 02 2018 00:00:00 GMT+0100 (British Summer Time),
"value": 60
},
...
]
}9 accordingly:
{
"label": "Apples",
"value": 10
},
{
"label": "Oranges",
"value": 17
},
...
]1
Here's how {
"title": "Visits",
"data": [
{
"time": Tue May 01 2018 00:00:00 GMT+0100 (British Summer Time),
"value": 39
},
{
"time": Wed May 02 2018 00:00:00 GMT+0100 (British Summer Time),
"value": 60
},
...
]
}5 now looks:
{
"label": "Apples",
"value": 10
},
{
"label": "Oranges",
"value": 17
},
...
]2
The dashboard component
We'll now create the main dashboard component. We initialise {
"title": "Categories",
"data": [
{
"label": "A",
"value": 46
},
{
"label": "B",
"value": 87
},
...
]
}1 using function getFeeds() {
let feeds = [];
feeds.push({
title: 'Visits',
data: getRandomDateArray(150)
});
feeds.push({
title: 'Categories',
data: getRandomArray(20)
});
feeds.push({
title: 'Categories',
data: getRandomArray(10)
});
feeds.push({
title: 'Data 4',
data: getRandomArray(6)
});
return feeds;
}5 and render a single {
"title": "Visits",
"data": [
{
"time": Tue May 01 2018 00:00:00 GMT+0100 (British Summer Time),
"value": 39
},
{
"time": Wed May 02 2018 00:00:00 GMT+0100 (British Summer Time),
"value": 60
},
...
]
}2:
{
"label": "Apples",
"value": 10
},
{
"label": "Oranges",
"value": 17
},
...
]3
Render the {
"title": "Categories",
"data": [
{
"label": "A",
"value": 46
},
{
"label": "B",
"value": 87
},
...
]
}4 component and you should see a bar chart:
{
"label": "Apples",
"value": 10
},
{
"label": "Oranges",
"value": 17
},
...
]4
Bar chart configuration
The y-axis in the above bar chart starts at a non-zero value. This is generally considered bad practice so we'll configure the minimum and maximum of the y-axis:
[{
"label": "Apples",
"value": 10
},
{
"label": "Oranges",
"value": 17
},
...
]5
Data updates
We'll now add support for data updates. Add an interval timer to {
"title": "Categories",
"data": [
{
"label": "A",
"value": 46
},
{
"label": "B",
"value": 87
},
...
]
}4 which requests new data and uses {
"title": "Categories",
"data": [
{
"label": "A",
"value": 46
},
{
"label": "B",
"value": 87
},
...
]
}6 to update {
"title": "Categories",
"data": [
{
"label": "A",
"value": 46
},
{
"label": "B",
"value": 87
},
...
]
}7:
{
"label": "Apples",
"value": 10
},
{
"label": "Oranges",
"value": 17
},
...
]6
Every 5 seconds the component state changes triggering a re-render. This in turn causes {
"title": "Categories",
"data": [
{
"label": "A",
"value": 46
},
{
"label": "B",
"value": 87
},
...
]
}8 in the {
"title": "Visits",
"data": [
{
"time": Tue May 01 2018 00:00:00 GMT+0100 (British Summer Time),
"value": 39
},
{
"time": Wed May 02 2018 00:00:00 GMT+0100 (British Summer Time),
"value": 60
},
...
]
}2 component to be called.
A Chart.js chart can be updated by mutating the data arrays (either by supplying a new array or changing the array values) and calling <BarChart
data={myData}
title="My amazing data"
color="#70CAD1"
/>0.
In our case we'll update the <BarChart
data={myData}
title="My amazing data"
color="#70CAD1"
/>1 and <BarChart
data={myData}
title="My amazing data"
color="#70CAD1"
/>2 properties of <BarChart
data={myData}
title="My amazing data"
color="#70CAD1"
/>3 and call <BarChart
data={myData}
title="My amazing data"
color="#70CAD1"
/>0:
{
"label": "Apples",
"value": 10
},
{
"label": "Oranges",
"value": 17
},
...
]7
Line and pie chart components
We'll add components for the line and doughnut charts. They'll follow the same form as the {
"title": "Visits",
"data": [
{
"time": Tue May 01 2018 00:00:00 GMT+0100 (British Summer Time),
"value": 39
},
{
"time": Wed May 02 2018 00:00:00 GMT+0100 (British Summer Time),
"value": 60
},
...
]
}2, the main difference being the <BarChart
data={myData}
title="My amazing data"
color="#70CAD1"
/>6 property. The line chart also needs some configuration to set the x axis type to <BarChart
data={myData}
title="My amazing data"
color="#70CAD1"
/>7 and some additional styling:
{
"label": "Apples",
"value": 10
},
{
"label": "Oranges",
"value": 17
},
...
]8
Here's the <BarChart
data={myData}
title="My amazing data"
color="#70CAD1"
/>8 component:
{
"label": "Apples",
"value": 10
},
{
"label": "Oranges",
"value": 17
},
...
]9
Now update the {
"title": "Categories",
"data": [
{
"label": "A",
"value": 46
},
{
"label": "B",
"value": 87
},
...
]
}4 component's [
{
"name": "A",
"value": 46
},
{
"name": "B",
"value": 87
},
...
]0 method to use all 3 chart types:
let feeds = [];
feeds.push({
title: 'Visits',
data: getRandomDateArray(150)
});
feeds.push({
title: 'Categories',
data: getRandomArray(20)
});
feeds.push({
title: 'Categories',
data: getRandomArray(10)
});
feeds.push({
title: 'Data 4',
data: getRandomArray(6)
});
return feeds;
}0
This results in 4 charts which update every 5 seconds:
Dashboard layout
The charts are rendered one above the other. However if there's sufficient width we'd prefer the line chart to be full width and the remaining charts to occupy the bottom row.
We'll wrap each chart in a [
{
"name": "A",
"value": 46
},
{
"name": "B",
"value": 87
},
...
]1 element and add a [
{
"name": "A",
"value": 46
},
{
"name": "B",
"value": 87
},
...
]2 class to the line chart and a [
{
"name": "A",
"value": 46
},
{
"name": "B",
"value": 87
},
...
]3 class to the remaining charts:
let feeds = [];
feeds.push({
title: 'Visits',
data: getRandomDateArray(150)
});
feeds.push({
title: 'Categories',
data: getRandomArray(20)
});
feeds.push({
title: 'Categories',
data: getRandomArray(10)
});
feeds.push({
title: 'Data 4',
data: getRandomArray(6)
});
return feeds;
}0
We'll add the following CSS to add some padding around each chart, make the main chart full width and the remaining charts a third of the width (so long as the screen width is greater than 700px):
function getFeeds() {let feeds = [];
feeds.push({
title: 'Visits',
data: getRandomDateArray(150)
});
feeds.push({
title: 'Categories',
data: getRandomArray(20)
});
feeds.push({
title: 'Categories',
data: getRandomArray(10)
});
feeds.push({
title: 'Data 4',
data: getRandomArray(6)
});
return feeds;
}2
And finally we set the [
{
"name": "A",
"value": 46
},
{
"name": "B",
"value": 87
},
...
]4 option on all 3 chart components to [
{
"name": "A",
"value": 46
},
{
"name": "B",
"value": 87
},
...
]5. This makes Chart.js fit the chart to its containing element.