Visx is a library that lets us add graphics to our React app easily.
In this article, we’ll look at how to use it to add stacked bar charts into our React app.
Install Required Packages
We have to install a few modules.
To get started, we run:
npm i @visx/axis @visx/grid @visx/group @visx/legend @visx/mock-data @visx/responsive @visx/scale @visx/shape @visx/tooltip
to install the packages.
Create the Chart
We can create the chart by adding the items provided by the modules.
We use the data from the @visx/mock-data module.
To add the stacked bar chart, we write:
import React from "react";
import { BarStack } from "@visx/shape";
import { Group } from "@visx/group";
import { Grid } from "@visx/grid";
import { AxisBottom } from "@visx/axis";
import cityTemperature from "@visx/mock-data/lib/mocks/cityTemperature";
import { scaleBand, scaleLinear, scaleOrdinal } from "@visx/scale";
import { timeParse, timeFormat } from "d3-time-format";
import { useTooltip, useTooltipInPortal, defaultStyles } from "@visx/tooltip";
import { LegendOrdinal } from "@visx/legend";
const purple1 = "#6c5efb";
const purple2 = "#c998ff";
export const purple3 = "#a44afe";
export const background = "#eaedff";
const defaultMargin = { top: 40, right: 0, bottom: 0, left: 0 };
const tooltipStyles = {
...defaultStyles,
minWidth: 60,
backgroundColor: "rgba(0,0,0,0.9)",
color: "white"
};
const data = cityTemperature.slice(0, 12);
const keys = Object.keys(data[0]).filter((d) => d !== "date");
const temperatureTotals = data.reduce((allTotals, currentDate) => {
const totalTemperature = keys.reduce((dailyTotal, k) => {
dailyTotal += Number(currentDate[k]);
return dailyTotal;
}, 0);
if (Array.isArray(allTotals)) {
allTotals.push(totalTemperature);
return allTotals;
}
return [];
});
const parseDate = timeParse("%Y-%m-%d");
const format = timeFormat("%b %d");
const formatDate = (date) => format(parseDate(date));
const getDate = (d) => d.date;
const dateScale = scaleBand({
domain: data.map(getDate),
padding: 0.2
});
const temperatureScale = scaleLinear({
domain: [0, Math.max(...temperatureTotals)],
nice: true
});
const colorScale = scaleOrdinal({
domain: keys,
range: [purple1, purple2, purple3]
});
let tooltipTimeout;
function Example({ width, height, events = false, margin = defaultMargin }) {
const {
tooltipOpen,
tooltipLeft,
tooltipTop,
tooltipData,
hideTooltip,
showTooltip
} = useTooltip();
const { containerRef, TooltipInPortal } = useTooltipInPortal();
if (width < 10) return null;
const xMax = width;
const yMax = height - margin.top - 100;
dateScale.rangeRound([0, xMax]);
temperatureScale.range([yMax, 0]);
return width < 10 ? null : (
<div style={{ position: "relative" }}>
<svg ref={containerRef} width={width} height={height}>
<rect
x={0}
y={0}
width={width}
height={height}
fill={background}
rx={14}
/>
<Grid
top={margin.top}
left={margin.left}
xScale={dateScale}
yScale={temperatureScale}
width={xMax}
height={yMax}
stroke="black"
strokeOpacity={0.1}
xOffset={dateScale.bandwidth() / 2}
/>
<Group top={margin.top}>
<BarStack
data={data}
keys={keys}
x={getDate}
xScale={dateScale}
yScale={temperatureScale}
color={colorScale}
>
{(barStacks) =>
barStacks.map((barStack) =>
barStack.bars.map((bar) => (
<rect
key={`bar-stack-${barStack.index}-${bar.index}`}
x={bar.x}
y={bar.y}
height={bar.height}
width={bar.width}
fill={bar.color}
onClick={() => {
if (events) alert(`clicked: ${JSON.stringify(bar)}`);
}}
onMouseLeave={() => {
tooltipTimeout = window.setTimeout(() => {
hideTooltip();
}, 300);
}}
onMouseMove={(event) => {
if (tooltipTimeout) clearTimeout(tooltipTimeout);
const top = event.clientY - margin.top - bar.height;
const left = bar.x + bar.width / 2;
showTooltip({
tooltipData: bar,
tooltipTop: top,
tooltipLeft: left
});
}}
/>
))
)
}
</BarStack>
</Group>
<AxisBottom
top={yMax + margin.top}
scale={dateScale}
tickFormat={formatDate}
stroke={purple3}
tickStroke={purple3}
tickLabelProps={() => ({
fill: purple3,
fontSize: 11,
textAnchor: "middle"
})}
/>
</svg>
<div
style={{
position: "absolute",
top: margin.top / 2 - 10,
width: "100%",
display: "flex",
justifyContent: "center",
fontSize: "14px"
}}
>
<LegendOrdinal
scale={colorScale}
direction="row"
labelMargin="0 15px 0 0"
/>
</div>
{tooltipOpen && tooltipData && (
<TooltipInPortal
key={Math.random()} // update tooltip bounds each render
top={tooltipTop}
left={tooltipLeft}
style={tooltipStyles}
>
<div style={{ color: colorScale(tooltipData.key) }}>
<strong>{tooltipData.key}</strong>
</div>
<div>{tooltipData.bar.data[tooltipData.key]}℉</div>
<div>
<small>{formatDate(getDate(tooltipData.bar.data))}</small>
</div>
</TooltipInPortal>
)}
</div>
);
}
export default function App() {
return (
<div className="App">
<Example width={500} height={300} />
</div>
);
}
We create the purple , purple2 , purple3 variables for the colors of the bars.
background is for the chart’s background color.
tooltipStyles have the styles for the tooltips.
The bar data is set as the value of the data variable.
keys have the values for the x-axis ticks.
We computed the temnperatureTotals by adding the temperature values for each day together.
We then create the dateScale for the x-axis scale.
And temperature scale is for the y-axis scale.
We set the max value of temperatureScale to the max value of the temperatureTotals which is the highest value for the y-axis.
colorScale has the color values.
Then we create the Example component to hold the stacked bar chart.
The useTooltip hook returns an object with various methods and states for creating and setting tooltip values.
We then compute the xMax and yMax values to create the x and y-axis scales.
Next, we add the svg element to hold all the chart parts together.
The Grid component has the grid displayed in the background.
The Group component has the BarStack component, which has the stacked bars.
The stacked bars are created by the rect element.
We have the onMouseMove handler to call showTooltip to show the tooltip with the bar values.
The onMouseLeave handler lets us close the tooltip when we navigate away from a bar.
The AxisBottom component renders the x-axis with the styles and the colors.
Conclusion
We can add stacked bar charts into our React app with the modules provided by Visx.