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.