Build Bar-Chart with @vx and React

Karthick Shanmugam
8 min readMay 3, 2019

--

Hi Fellow UI-Devs,

This time with full javascript module explanation with React.

Being a UI-Dev, obviously at-least once we would have come across a requirement where we have to build a graph (say bar-chart) to show maybe a summary as a part of dashboard. Some might have used D3.js — raw js plugin dealing with svg which is obviously open source but the tireful part is you have to build everything. Some might have used some wow plugins where everything is right there for you to build with an easy-to-do approach but never mind it is costly.

Right there we have @vx javascript plugin built in ReactJS which comes to rescue being easily customisable and of-course open source. Believe me, it is one of the most used and most promisable chart plugin used in web industry

One feature what I like in @vx is, what you want is what you import. No need to import the whole package but just install and import what you want.

Okay. No more explanations and we will jump into the explanation of how I used @vx for a use-case which I took over here.

Use Case:

Show total sales earned by three employees Joe, Chandler and Ross in three years

Required npm modules:

npm i @vx/axis ## set axis information for our chart
npm i @vx/group ## set grouping our chart
npm i @vx/legend ## set legend-info for our chart
npm i @vx/scale ## set scaling-info for our chart
npm i @vx/shape ## set bar-chart info
npm i @vx/tooltip ## set tooltip-info for our chart

We can see what we have done. We just installed what we want instead of the whole package. What you want is what you install

Let’s start with the master component from where we will call our bar chart.

import React from 'react'
import SummaryGraph from '../SummaryGraph'
const MyChartComponent = () => {

const mockData = [
{
year: '2017',
joe: 1000,
chandler: 2000,
ross: 800,
},
{
year: '2018',
joe: 1500,
chandler: 1800,
ross: 1000,
},
{
year: '2019',
joe: 2000,
chandler: 1750,
ross: 950,
}]
const colorArray = ['#191919', '#FFCF02', '#FF5F00'] // Object.keys(mockData).slice(1) -> ['joe','chandler','ross']
// Object.keys(mockData).shift() -> 'year'
return (
<>
<SummaryGraph
graphData={mockData}
categoryAPI={Object.keys(mockData).shift()}
colorArray={colorArray}
keysToInclude={Object.keys(mockData).slice(1)}
/>
</>
)
}

Now our parent component is all set where we call the SummaryGraph component with required props which we will see in a minute. The required props are

  • graphData where we pass the required data in array format to create the graph.
  • categoryAPI where we say the base variable to be considered for the graph
  • colorArray to say which color we want to display the required bars
  • keysToInclude to say which keys we need to consider to display the graph

We might need some props which may not be required as we see below

  • measurements where we mention the height, width and rest all measurement properties so as to display our graph as we need with perfect alignments
  • background where we specify the background color for our graph

Actually enough about intro. Now we will dive deep into SummaryGraph component

SummaryGraph.js

import statements

import React from 'react'
import {BarStack} from '@vx/shape'
import {Group} from '@vx/group'
import {AxisBottom, AxisLeft} from '@vx/axis'
import {scaleBand, scaleLinear, scaleOrdinal} from '@vx/scale'
import {withTooltip, Tooltip} from '@vx/tooltip'
import {LegendOrdinal} from '@vx/legend'
  • BarStack — as we are going to implement Bar-Graph
  • Group — as we might be grouping our graph components together
  • AxisBottom and AxisLeft — as we would want left and bottom scales to be displayed in the graph
  • scaleBand for X-Axis scaling, scaleLinear for Y-Axis scaling and scaleOrdinal for color scaling for our graph component
  • withTooltip is a HOC component to provide tooltip properties to our component, Tooltip — as we would require tooltips for our grap
  • LegendOrdinal — as we want legend information to be shown for our graph

As you can see What is you want is what you import

As we use withTooltip HOC, by default our component will be having the following props

tooltipOpen  // is tooltip open
tooltipLeft // left alignment of tooltip
tooltipTop // top alignment of tooltip
tooltipData // tooltip data
hideTooltip // function provided to hide tooltip
showTooltip // function provided to show tooltip

I feel with above props we can easily handle the tooltip for our graph. Cool isn’t it!?!

Let’s destructure our props for SummaryGraph component

const {
colorArray,
graphData,
keysToInclude,
measurements = {},
background = '#FFFFFF',
categoryAPI,
tooltipOpen,
tooltipLeft,
tooltipTop,
tooltipData,
hideTooltip,
showTooltip,
} = props
const {
width = 500,
graphWidth = '100%',
height = 400,
margin = {
left: 50,
right: 40,
top: 40,
},
} = measurements

As you can see the props provided for tooltip as said above doesn’t require to be provided with default value since it is provided by withTooltip HOC. measurements prop is again optional one, instead of which, it takes some default values. background prop is by default #FFFFFF.

Now let’s see how we can define our graph scales and graph data

// returns a number array with totals for each and every year
const totals = graphData.reduce((ret, cur) => {
const t = keysToInclude.reduce((dailyTotal, k) => {
dailyTotal += +cur[k]
return dailyTotal
}, 0)
ret.push(t)
return ret
}, [])
// set maximum width and height for X-Axis and Y-Axis
const xMax = width
const yMax = height - margin.top - 100
// getter function to get the category value for each year - categoryAPI is obtained from props
const getCategory = (d) => d[categoryAPI]
// scales for X-Axis - each year
const xScale = scaleBand({
domain: graphData.map(getCategory),
padding: 0.2,
})
// scales for Y-Axis - 0 to maximum of totals in each years
const yScale = scaleLinear({
domain: [0, Math.max(...totals)],
nice: true,
})
// scales for colorings for each key in bar chart - one to one mapping
const color = scaleOrdinal({
domain: keysToInclude,
range: colorArray,
})
// scales for coloring for each key in legend - one to one mapping - sometimes we can use same color variable for legend as well
const legendColor = scaleOrdinal({
domain: keysToInclude.map((key) => {
return getCapitalizedValue(key)
}),
range: colorArray,
})
//getCapitalizedValue - a util function to return capitalized value for the string passed - e.g chandlerMBing becomes Chandler M Bing :P
const getCapitalizedValue = (inputString) => {
const alteredInputString = inputString.replace(/\.?([A-Z])/g, (x, y) => ' ' + y).replace(/^_/, '')
return alteredInputString.charAt(0).toUpperCase() + alteredInputString.slice(1))
}
xScale.rangeRound([0, xMax])
yScale.range([yMax, 0])

Still now we have set the required variables to draw the graph. We will see below each and every part of the graph on how to set it up for our use-case

Tooltip

{tooltipOpen && tooltipData && (
<Tooltip top={tooltipTop} left={tooltipLeft} className="tool-tip">
<strong>
{'Sales made by ' + getCapitalizedValue(tooltipData.key) + ' in ' + tooltipData.bar.data[categoryAPI]}
</strong>
<div>
{tooltipData.bar.data[tooltipData.key]}
</div>
</Tooltip>
)}

So when we hover-over bar-chart for Joey for 2017, it would look like

Legend

<div className="legend">
<LegendOrdinal
scale={legendColor}
direction={'column'}
labelMargin="0 15px 0 0"
/>
</div>

So our legend looks like

Axis

<AxisLeft
hideTicks={true}
scale={yScale}
tickLabelProps={(value, index) => ({
fontSize: 11,
textAnchor: 'end',
})}
/>
<AxisBottom
top={yMax}
hideTicks={true}
scale={xScale}
tickLabelProps={(value, index) => ({
fontSize: 11,
textAnchor: 'middle',
})}
/>

The above code will be used to display the X-Axis and Y-Axis for our Bar-Chart, which will look like

Y-Axis

X-Axis

Bar-Chart

<BarStack
data={graphData}
keys={keysToInclude}
x={getCategory}
xScale={xScale}
yScale={yScale}
color={color}
>
{(barStacks) => {
return barStacks.map((barStack) => {
return barStack.bars.map((bar) => {
return (
<rect
key={`bar-stack-${barStack.index}-${bar.index}`}
x={bar.x}
y={bar.y}
height={bar.height}
width={bar.width}
fill={bar.color}
onMouseLeave={event => {
tooltipTimeout = setTimeout(() => {
hideTooltip()
}, 300)
}}
onMouseMove={event => {
if (tooltipTimeout) {
clearTimeout(tooltipTimeout)
}
const top = bar.y + 10
const offset =(xScale.paddingInner() * xScale.step())/2
const left = bar.x + bar.width + offset
showTooltip({
tooltipData: bar,
tooltipLeft: left,
tooltipTop: top,
})
}}
>
<animate
attributeName="height"
from={0}
to={bar.height}
dur="0.5s"
fill="freeze"
/>
</rect>
)
})
})
}}
</BarStack>

As we can see over here, we feed in the graphData and the keys along with the getter function to get the year value with which our graph will be formed with xScale and yScale and colorings as well.

On each rect element (which is obviously each and every bar in our graph filled with respective colors), we have mouseLeave and mouseMove events to hide or show the tooltip at desired positions as provided by withTooltip HOC.

And a dashboard chart without animate is obviously boring. So we have a animate element as well bounded within rect component.

With above snippet our component with mock data would look like

Almost done.

Now since we saw each and every step in a detailed view. Oops we forgot to group them.

<svg width={graphWidth} height={height} overflow="auto">
<rect
x={0}
y={0}
width={graphWidth}
height={height}
fill={background}
rx={14}
/>
<Group top={margin.top} left={margin.left} right={margin.right}>
/* Our components above */
</Group>
</svg>

So our final implementation looks like

So to checkout our final code-base for SummaryGraph component it will be like as below

Tadaa….!!!! We are done with SummaryGraph component.

As usual your likes and comments are much appreciated 😃

Please don’t forget to checkout below links to learn more about @vx .

--

--