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 horizontal 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 create the horizontal stacked bar chart, we write:
import React from "react";
import { BarStackHorizontal } from "@visx/shape";
import { Group } from "@visx/group";
import { AxisBottom, AxisLeft } 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 { Tooltip, defaultStyles, useTooltip } 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, left: 50, right: 40, bottom: 100 };
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);
allTotals.push(totalTemperature);
return allTotals;
}, []);
const parseDate = timeParse("%Y-%m-%d");
const format = timeFormat("%b %d");
const formatDate = (date) => format(parseDate(date));
const getDate = (d) => d.date;
const temperatureScale = scaleLinear({
domain: [0, Math.max(...temperatureTotals)],
nice: true
});
const dateScale = scaleBand({
domain: data.map(getDate),
padding: 0.2
});
const colorScale = scaleOrdinal({
domain: keys,
range: [purple1, purple2, purple3]
});
let tooltipTimeout;
const Example = ({ width, height, events = false, margin = defaultMargin }) => {
const {
tooltipOpen,
tooltipLeft,
tooltipTop,
tooltipData,
hideTooltip,
showTooltip
} = useTooltip();
const xMax = width - margin.left - margin.right;
const yMax = height - margin.top - margin.bottom;
temperatureScale.rangeRound([0, xMax]);
dateScale.rangeRound([yMax, 0]);
return width < 10 ? null : (
<div>
<svg width={width} height={height}>
<rect width={width} height={height} fill={background} rx={14} />
<Group top={margin.top} left={margin.left}>
<BarStackHorizontal
data={data}
keys={keys}
height={yMax}
y={getDate}
xScale={temperatureScale}
yScale={dateScale}
color={colorScale}
>
{(barStacks) =>
barStacks.map((barStack) =>
barStack.bars.map((bar) => (
<rect
key={`barstack-horizontal-${barStack.index}-${bar.index}`}
x={bar.x}
y={bar.y}
width={bar.width}
height={bar.height}
fill={bar.color}
onClick={() => {
if (events) alert(`clicked: ${JSON.stringify(bar)}`);
}}
onMouseLeave={() => {
tooltipTimeout = window.setTimeout(() => {
hideTooltip();
}, 300);
}}
onMouseMove={() => {
if (tooltipTimeout) clearTimeout(tooltipTimeout);
const top = bar.y + margin.top;
const left = bar.x + bar.width + margin.left;
showTooltip({
tooltipData: bar,
tooltipTop: top,
tooltipLeft: left
});
}}
/>
))
)
}
</BarStackHorizontal>
<AxisLeft
hideAxisLine
hideTicks
scale={dateScale}
tickFormat={formatDate}
stroke={purple3}
tickStroke={purple3}
tickLabelProps={() => ({
fill: purple3,
fontSize: 11,
textAnchor: "end",
dy: "0.33em"
})}
/>
<AxisBottom
top={yMax}
scale={temperatureScale}
stroke={purple3}
tickStroke={purple3}
tickLabelProps={() => ({
fill: purple3,
fontSize: 11,
textAnchor: "middle"
})}
/>
</Group>
</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 && (
<Tooltip 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>
</Tooltip>
)}
</div>
);
};
export default function App() {
return (
<div className="App">
<Example width={500} height={300} />
</div>
);
}
We create the purple
, purple2
and purple3
variables to set the color of the bars.
background
has the background color.
defaultMargin
has the margins for the chart.
tooltipStyles
has the styles for the tooltips.
data
has the data for the bars.
keys
have the data for the y-axis.
We compute the temperatureTotals
so that we can determine the max value for the x-axis.
Next, we have the parseDate
and format
functions to let us parse the dates from the data and format them for display on the y-axis.
Then we create the temperatureScale
for the x-axis.
dateScale
is for the y-axis.
colorScale
is for the stacked bars.
Then we create the Example
component to add the chart.
We call the useTooltip
hook to return an object that lets us render tooltips to display the data values.
The BarStackHorizontal
component has the container for the horizontal stacked bars.
We set the scales as the values of the xScale
and yScale
props.
color
sets the color scales.
In the child of the BarStackHorizontal
component, we render the rectangles for the bars.
This is where we add the onMouseMove
prop to show the tooltip with the bar data when we move the mouse.
And when our mouse leave the bar, we call hideTooltip
to hide the tooltip.
AxisLeft
has the y-axis, which has the date displayed.
And AxisBottom
has the x-axis with the temperature data.
Then we add the legend for the bars with the LegendOrdinal
component.
And the Tooltip
component has the tooltip.
Conclusion
We can add a horizontal stacked bar chart into our React app with the Visx library.