Make Your Own Charts in React Without a Charting Library – Part 1

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. This will be a multi-part series of posts, and we’ll try to make something progressively more challenging with each one.

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 seems a bit like overkill to me. 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

The first 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. We’ll add axes to our chart in the next post. Take a look and play with the chart below: