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 box plots into our React app.
Install Required Packages
We have to install a few modules.
To get started, we run:
npm i @visx/gradient @visx/group @visx/mock-data @visx/pattern @visx/responsive @visx/scale @visx/stats @visx/tooltip
to install the packages.
Add Box Plots
Now we can add the box plots with:
import React from "react";
import { Group } from "@visx/group";
import { ViolinPlot, BoxPlot } from "@visx/stats";
import { LinearGradient } from "@visx/gradient";
import { scaleBand, scaleLinear } from "@visx/scale";
import genStats from "@visx/mock-data/lib/generators/genStats";
import {
Tooltip,
defaultStyles as defaultTooltipStyles,
useTooltip
} from "@visx/tooltip";
import { PatternLines } from "@visx/pattern";
const data = genStats(5);
const x = (d) => d.boxPlot.x;
const min = (d) => d.boxPlot.min;
const max = (d) => d.boxPlot.max;
const median = (d) => d.boxPlot.median;
const firstQuartile = (d) => d.boxPlot.firstQuartile;
const thirdQuartile = (d) => d.boxPlot.thirdQuartile;
const outliers = (d) => d.boxPlot.outliers;
const Example = ({ width, height }) => {
const {
tooltipOpen,
tooltipLeft,
tooltipTop,
tooltipData,
hideTooltip,
showTooltip
} = useTooltip();
const xMax = width;
const yMax = height - 120;
const xScale = scaleBand({
range: [0, xMax],
round: true,
domain: data.map(x),
padding: 0.4
});
const values = data.reduce((allValues, { boxPlot }) => {
allValues.push(boxPlot.min, boxPlot.max);
return allValues;
}, []);
const minYValue = Math.min(...values);
const maxYValue = Math.max(...values);
const yScale = scaleLinear({
range: [yMax, 0],
round: true,
domain: [minYValue, maxYValue]
});
const boxWidth = xScale.bandwidth();
const constrainedWidth = Math.min(40, boxWidth);
return width < 10 ? null : (
<div style={{ position: "relative" }}>
<svg width={width} height={height}>
<LinearGradient id="statsplot" to="#8b6ce7" from="#87f2d4" />
<rect
x={0}
y={0}
width={width}
height={height}
fill="url(#statsplot)"
rx={14}
/>
<PatternLines
id="hViolinLines"
height={3}
width={3}
stroke="#ced4da"
strokeWidth={1}
orientation={["horizontal"]}
/>
<Group top={40}>
{data.map((d, i) => (
<g key={i}>
<ViolinPlot
data={d.binData}
stroke="#dee2e6"
left={xScale(x(d))}
width={constrainedWidth}
valueScale={yScale}
fill="url(#hViolinLines)"
/>
<BoxPlot
min={min(d)}
max={max(d)}
left={xScale(x(d)) + 0.3 * constrainedWidth}
firstQuartile={firstQuartile(d)}
thirdQuartile={thirdQuartile(d)}
median={median(d)}
boxWidth={constrainedWidth * 0.4}
fill="#FFFFFF"
fillOpacity={0.3}
stroke="#FFFFFF"
strokeWidth={2}
valueScale={yScale}
outliers={outliers(d)}
minProps={{
onMouseOver: () => {
showTooltip({
tooltipTop: yScale(min(d)) ?? 0 + 40,
tooltipLeft: xScale(x(d)) + constrainedWidth + 5,
tooltipData: {
min: min(d),
name: x(d)
}
});
},
onMouseLeave: () => {
hideTooltip();
}
}}
maxProps={{
onMouseOver: () => {
showTooltip({
tooltipTop: yScale(max(d)) ?? 0 + 40,
tooltipLeft: xScale(x(d)) + constrainedWidth + 5,
tooltipData: {
max: max(d),
name: x(d)
}
});
},
onMouseLeave: () => {
hideTooltip();
}
}}
boxProps={{
onMouseOver: () => {
showTooltip({
tooltipTop: yScale(median(d)) ?? 0 + 40,
tooltipLeft: xScale(x(d)) + constrainedWidth + 5,
tooltipData: {
...d.boxPlot,
name: x(d)
}
});
},
onMouseLeave: () => {
hideTooltip();
}
}}
medianProps={{
style: {
stroke: "white"
},
onMouseOver: () => {
showTooltip({
tooltipTop: yScale(median(d)) ?? 0 + 40,
tooltipLeft: xScale(x(d)) + constrainedWidth + 5,
tooltipData: {
median: median(d),
name: x(d)
}
});
},
onMouseLeave: () => {
hideTooltip();
}
}}
/>
</g>
))}
</Group>
</svg>
{tooltipOpen && tooltipData && (
<Tooltip
top={tooltipTop}
left={tooltipLeft}
style={{
...defaultTooltipStyles,
backgroundColor: "#283238",
color: "white"
}}
>
<div>
<strong>{tooltipData.name}</strong>
</div>
<div style={{ marginTop: "5px", fontSize: "12px" }}>
{tooltipData.max && <div>max: {tooltipData.max}</div>}
{tooltipData.thirdQuartile && (
<div>third quartile: {tooltipData.thirdQuartile}</div>
)}
{tooltipData.median && <div>median: {tooltipData.median}</div>}
{tooltipData.firstQuartile && (
<div>first quartile: {tooltipData.firstQuartile}</div>
)}
{tooltipData.min && <div>min: {tooltipData.min}</div>}
</div>
</Tooltip>
)}
</div>
);
};
export default function App() {
return (
<div className="App">
<Example width={500} height={300} />
</div>
);
}
The data
variable has the data for the boxplot.
Then we add the getter methods below it.
In the Example
component, we call the useTooltip
hook to show tooltips when we hover over the boxplots.
showTooltip
shows the tooltip. hideTooltip
hides the tooltip.
We add the values
variable to gather the values needed for the boxplots.
yScale
has the scale for the y-axis.
To add the boxplots, we call data.map
to map the data values box plot components.
We use the ViolinPlot
component to render the violin plot surrounding the boxplot.
Then we use the BoxPlot
component to render the boxplot.
We pass in all the getter methods as props into ViolinPlot
and BoxPlot
.
We call showTooltip
in the onMouseOver
method to show a tooltip when we hover over the boxplot.
And in the onMouseLeave
handler, we call hideTooltip
to hide the tooltips when we move away from the boxplot.
minProps
takes handlers that runs when we hover over the bottom of the boxplot.
maxProps
takes handlers that runs when we hover over the top of the boxplot.
boxProps
takes handlers that runs when we hover over the boxes of the boxplot.
medianProps
takes handlers that runs when we hover over the median point of the boxplot.
Finally, we add the tooltip content with the Tooltip
component.
Now we should see a box and violin plot. And when we hover over the whiskers or the boxes, we see a tooltip with the values.
Conclusion
We can add box and violin plots into our React app with the Visx library.