Categories
Visx

Add Box Plots into Our React App with the Visx Library

Spread the love

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.

By John Au-Yeung

Web developer specializing in React, Vue, and front end development.

Leave a Reply

Your email address will not be published. Required fields are marked *