Build Bar-Chart with @vx and React
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 graphcolorArray
to say which color we want to display the required barskeysToInclude
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 alignmentsbackground
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-GraphGroup
— as we might be grouping our graph components togetherAxisBottom
andAxisLeft
— as we would want left and bottom scales to be displayed in the graphscaleBand
for X-Axis scaling,scaleLinear
for Y-Axis scaling andscaleOrdinal
for color scaling for our graph componentwithTooltip
is a HOC component to provide tooltip properties to our component,Tooltip
— as we would require tooltips for our grapLegendOrdinal
— 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,
} = propsconst {
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 .