Make Your Own Charts in React Without a Charting Library
Occasionally I see someone ask, “What’s the best way to make bar charts with React? Are there any great libraries?” I often respond with, “Why not build it with React yourself?”
React’s one-way data binding model is perfect for creating simple data visualizations from scratch and I want to show you how.
Why shouldn’t I use a charting library?
Charting libraries are great! I’m not going to stop you. But bringing in a whole library for a simple data viz might be a bit of overkill in some situations. If you go the route of adding a charting library to your application, you now have to understand two APIs versus one. React’s API is well suited to creating these visualizations, so I’m encouraging you to use what you already know.
A Simple Bar Chart
We are going to build is a bar chart. These are really easy to build with React. Once you understand the concepts, I’m sure you’ll be jumping ahead and making even more complex visualizations in no time. Let’s get started.
To make our bar chart, we’re going to first need some data. Because I lack creativity, I’m going to create a data set based on the number of repos a few of my favorite Github users own (and mine for good measure):
const data = [
{
name: 'kentcdodds',
repos: 371,
},
{
name: 'sindresorhus',
repos: 909,
},
{
name: 'developit',
repos: 222,
},
{
name: 'getify',
repos: 43,
},
{
name: 'btholt',
repos: 56,
},
{
name: 'kyleshevlin',
repos: 82,
},
]
Next, we need to build a few basic components to represent this data. We’ll start with a Chart
component and a Bar
component.
const Chart = ({ children, width, height }) => (
<svg viewBox={`0 0 ${width} ${height}`} width={width} height={height}>
{children}
</svg>
)
const Bar = ({ x, y, width, height }) => (
<rect x={x} y={y} width={width} height={height} />
)
These are really simple components. Our Chart
component creates an svg
based upon the width and height we pass in as props. Then, the Bar
component creates a rect
element that we will pass as a child of the Chart
component.
Putting this together (with some math to handle the discrepancy in repo totals), we can make our bar chart like so:
const BarChart = ({ data }) => {
// Width of each bar
const itemWidth = 20
// Distance between each bar
const itemMargin = 5
const dataLength = data.length
// Normalize data, we'll reduce all sizes to 25% of their original value
const massagedData = data.map(datum =>
Object.assign({}, datum, { repos: datum.repos * 0.25 }),
)
const mostRepos = massagedData.reduce((acc, cur) => {
const { repos } = cur
return repos > acc ? repos : acc
}, 0)
const chartHeight = mostRepos
return (
<Chart width={dataLength * (itemWidth + itemMargin)} height={chartHeight}>
{massagedData.map((datum, index) => (
<Bar
key={datum.name}
x={index * (itemWidth + itemMargin)}
y={0}
width={itemWidth}
height={datum.repos}
/>
))}
</Chart>
)
}
ReactDOM.render(<BarChart data={data} />, document.getElementById('barchart'))
If you’ve followed along, you should now have a bar chart of 6 bars. Their heights should correspond with how many repos the user has. There is one small problem, though. The bars are upside down.
This is a typical problem with bar charts and is easily solved. I just wanted to show you how.
In our BarChart
component, we need to change the y
prop to place the rect
such that they all line up on the bottom. To do this, we can set y
equal to chartHeight
minus that rect
’s height. Let’s make some small changes to the component:
const BarChart = ({ data }) => {
// all the same ...
return (
<Chart width={dataLength * (itemWidth + itemMargin)} height={chartHeight}>
{massagedData.map((datum, index) => {
const itemHeight = datum.repos
return (
<Bar
x={index * (itemWidth + itemMargin)}
y={chartHeight - itemHeight}
width={itemWidth}
height={itemHeight}
/>
)
})}
</Chart>
)
}
And there you have it, a very simple to make bar chart. Check out a rendering of our bar chart below: