A basic responsive bar chart in reactjs can be hand coded easily.

When developers are faced with any sort of problem that involves data visualization, most google d3 chart examples and find some d3 code example that sort of satisfies their criteria, but they still need to do some reverse engineering to get it exactly right. What seems to be a simple bit of refactoring can turn into a nightmare. This is because d3 can be a little bit tricky to understand, and there is a learning curve. A slight variation in the visual requirement can sometimes involve rewriting the whole of your chart code.

When I am faced with problems that involve the creation of simple bar charts I always turn to code it from the ground up using SVG.

This article will take you through the steps in building a basic infographic type bar chart, that will visualize data from a set of monthly expenses.

Here is the video that shows the step by step guide to creating a basic responsive bar char

The structure of the app

Before we draw our chart we need to specify some measurement values :

  • Maximum expense value (This is also the maximum height fo a bar)= 200
  • Number of bars = 6
  • Chart height = maximum expense value +20 pixels top margin = 220
  • each bar would have 30 pixels right margin
  • each bars width will be 50 pixels
  • Chart width = (bar wdth(50) + bar mergin(30) ) * number of bars(6)

I have chosen these numbers to make our demo simple, in real life, you would not have any such restrictions.

To draw our chart in SVG, we need to structure the SVG container using the values we have specified as shown above. The three yellow boxes in the diagram are important attributes that make the SVG responsive and display correctly. In the first yellow box we have the SVG drawing area, we will give this a dimension of 400 wide by 220 height. The second yellow box is the viewport, we have given this width of 100% and 70% high. Note the discrepancy between the viewport and view box values. The Viewport percentage numbers allow us to scale it to the window size. But it will only scale properly if we have the attribute given in the third yellow box, which is:

The whole subject area around SVG and scaling is a bit of a complex area, you can read more about it in this article on csstricks.

In the diagram above, we also have a tag that renders a bar for each of the expenses with x,y, width and fill color as attributes.

The diagram shows a sample of the markup that we are aiming for in our demo app, the challenge is to create the same dynamically with our data in react. So that’s what we are going to do.

Our app structure — start with a skeleton code

//App.js
import React , {useState,useEffect} from 'react';
import './App.css';

//Sample data for expenses for the month
const data = [
{ name: "Phone", expense: 151 },
{ name: "Electricity", expense: 100 },
{ name: "Car", expense: 5 },
{ name: "House", expense: 43 },
{ name: "Food", expense: 56 },
{ name: "Leisure", expense: 182 }
];

function App() {

// Initialization values that include chart and bar dimensions
const [expensesData, setExpensesData] = useState(data); // State value for expenses
const maxExpense = 200;
const chartHeight = maxExpense + 20;
const barWidth = 50;
const barMargin = 30;
const numberofBars = expensesData.length;
let width = numberofBars * (barWidth + barMargin);

// Calculate highest expense for the month
const calculateHighestExpense = (data) => {}

//Button click handler that refresh's expenses data
let refreshChart = ()=> { }

//Render chart
return (
<>
<p className="legend">
<span className="expense">Expense</span>
<span className="highest-expense">Highest expense</span>
</p>

<Chart height={chartHeight} width={width}>
{/* To Do: Render children , these will be our bars*/}
</Chart>

<button onClick={refreshChart}>Refresh Chart</button>
</>
);
}

//Component to render SVG chart
const Chart = ({ children, width, height }) => (
<svg
viewBox={`0 0 ${width} ${height}`}
width="100%"
height="70%"
preserveAspectRatio="xMidYMax meet"
>
{children}
</svg>
);

export default App

Next lets complete the function “calculateHighestExpense” :

// Calculate highest expense for the month
const calculateHighestExpense = (data) => data.reduce((acc, cur) => {
const { expense } = cur;
return expense > acc ? expense : acc;
}, 0);

in this function, all we are doing is applying the javascript function reduce on our data array. It will go through one by one comparing values and keeps track of the highest value and then return the highest value at the end. We will use the value returned to set a state for highestExpense :

const [highestExpense, setHighestExpense] = useState(calculateHighestExpense(data));

The Chart component

<Chart height={chartHeight} width={width}>
{/* To Do: Render children , these will be our bars*/}
</Chart>

The chart bar component will be rendered In the commented area that currently says “To Do”. We are going to render a bar for each expense in our data, so we will need to apply the map function to the data array. Each bar will render a element with our bar dimensions. So for the above code will look like this now :

In the above block of code, I have declared and assigned barHeight to equal data.expense. The barheight is then used to calculate the y value. The x value of the bar will be calculated by the formulae (index * (barWidth + barMargin)). The rest of the attributes for our Bar component is self-explanatory.

<Chart height={chartHeight} width={width}>
{expensesData.map((data, index) => {
const barHeight = data.expense;
return (
<Bar
key={data.name}
x={index * (barWidth + barMargin)}
y={chartHeight - barHeight}
width={barWidth}
height={barHeight}
expenseName={data.name}
highestExpense={highestExpense}
/>
);
})}
</Chart>

The Bar component

const Bar = ({ x, y, width, height, expenseName,highestExpense }) => (
<>
<rect x={x} y={y} width={width} height={height} fill={ highestExpense===height ?`purple`:`black`} />
<text x={x + width / 3} y={y - 5}>
{highestExpense===height ? `${expenseName}: ${height}` : `${height}`}
</text>
</>
);

We are returning an element with the calculated values of x,y, width and height. Additionally, if the current bar has the highest expense value then we want to display the bar in purple, otherwise, display the bar with black color. In the code above, we are alternating the colors with the following ternary expression in the fill attribute:

fill={ highestExpense===height ?`purple`:`black`}

Here we are using strict equality ‘===’ to test if highestExpense is equal to height, height is also the expense value in this block of code.

In the bar component function we also return an element that also ahs has another ternary expression to alternate the text:

{highestExpense===height ? `${expenseName}: ${height}` : `${height}`}

again we test if the highestExpense is equal to height, if it is then we render a label with the name and expense, otherwise, just render the value. Note that we are using backticks to output strings ie ${expenseName}: ${height}. We use backticks because this allows us to combine variables in our strings, this is a ES6 feature called string templates.

…. and finally, the button to refresh

<button onClick={refreshChart}>Refresh Chart</button>

So the final piece of code we need to write is the callback function “refreshChart” that handles the click. This function will do two things, one, recalculate the array items by setting the expense item to a new expense for each array item. Secondly, it will set new states. We can write the function to generate a new set of random values as a separate function first:

const createRandomData = (data) => data.map((exp) => ({
name: exp.name,
expense: Math.floor(Math.random() * maxExpense)
}))

then we can use this in our refreshChart function that handles the click:

let refreshChart = ()=> {
const newData = createRandomData(expensesData);
const newHighestexpense = calculateHighestExpense(newData);
setExpensesData(newData);
setHighestExpense(newHighestexpense);
}

The final code and conclusion

There is so much more you could do with SVG, this is just scratching the surface. The hardest part of working with SVG is understanding the coordinate system and general drawing of shapes and paths. You can read more about SVG co-ordinate system here https://www.sarasoueidan.com/blog/svg-coordinate-systems/.

Originally published at https://dev.to on October 30, 2020.

UI Designer/Developer

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store