Categories
Visx

Create Curves 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 curves into our React app

Install Required Packages

We have to install a few modules.

To get started, we run:

npm i @visx/curve @visx/gradient @visx/group @visx/marker @visx/mock-data @visx/responsive @visx/scale @visx/shape

to install the packages.

Create the Curves

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 curves, we write:

import React, { useState } from "react";
import { extent, max } from "d3-array";
import * as allCurves from "@visx/curve";
import { Group } from "@visx/group";
import { LinePath } from "@visx/shape";
import { scaleTime, scaleLinear } from "@visx/scale";
import {
  MarkerArrow,
  MarkerCross,
  MarkerX,
  MarkerCircle,
  MarkerLine
} from "@visx/marker";
import generateDateValue from "@visx/mock-data/lib/generators/genDateValue";

const curveTypes = Object.keys(allCurves);
const lineCount = 3;
const series = new Array(lineCount)
  .fill(null)
  .map((_) =>
    generateDateValue(25).sort((a, b) => a.date.getTime() - b.date.getTime())
  );
const allData = series.reduce((rec, d) => rec.concat(d), []);

const getX = (d) => d.date;
const getY = (d) => d.value;

// scales
const xScale = scaleTime({
  domain: extent(allData, getX)
});
const yScale = scaleLinear({
  domain: [0, max(allData, getY)]
});

function Example({ width, height, showControls = true }) {
  const [curveType, setCurveType] = useState("curveNatural");
  const [showPoints, setShowPoints] = useState(true);
  const svgHeight = showControls ? height - 40 : height;
  const lineHeight = svgHeight / lineCount;

  xScale.range([0, width - 50]);
  yScale.range([lineHeight - 2, 0]);

  return (
    <div className="visx-curves-demo">
      {showControls && (
        <>
          <label>
            Curve type &nbsp;
            <select
              onChange={(e) => setCurveType(e.target.value)}
              value={curveType}
            >
              {curveTypes.map((curve) => (
                <option key={curve} value={curve}>
                  {curve}
                </option>
              ))}
            </select>
          </label>
          &nbsp;
          <label>
            Show points&nbsp;
            <input
              type="checkbox"
              checked={showPoints}
              onChange={() => setShowPoints(!showPoints)}
            />
          </label>
          <br />
        </>
      )}
      <svg width={width} height={svgHeight}>
        <MarkerX
          id="marker-x"
          stroke="#333"
          size={22}
          strokeWidth={4}
          markerUnits="userSpaceOnUse"
        />
        <MarkerCross
          id="marker-cross"
          stroke="#333"
          size={22}
          strokeWidth={4}
          strokeOpacity={0.6}
          markerUnits="userSpaceOnUse"
        />
        <MarkerCircle id="marker-circle" fill="#333" size={2} refX={2} />
        <MarkerArrow
          id="marker-arrow-odd"
          stroke="#333"
          size={8}
          strokeWidth={1}
        />
        <MarkerLine id="marker-line" fill="#333" size={16} strokeWidth={1} />
        <MarkerArrow id="marker-arrow" fill="#333" refX={2} size={6} />
        <rect width={width} height={svgHeight} fill="#efefef" rx={14} ry={14} />
        {width > 8 &&
          series.map((lineData, i) => {
            const even = i % 2 === 0;
            let markerStart = even ? "url(#marker-cross)" : "url(#marker-x)";
            if (i === 1) markerStart = "url(#marker-line)";
            const markerEnd = even
              ? "url(#marker-arrow)"
              : "url(#marker-arrow-odd)";
            return (
              <Group key={`lines-${i}`} top={i * lineHeight} left={13}>
                {showPoints &&
                  lineData.map((d, j) => (
                    <circle
                      key={i + j}
                      r={3}
                      cx={xScale(getX(d))}
                      cy={yScale(getY(d))}
                      stroke="rgba(33,33,33,0.5)"
                      fill="transparent"
                    />
                  ))}
                <LinePath
                  curve={allCurves[curveType]}
                  data={lineData}
                  x={(d) => xScale(getX(d)) ?? 0}
                  y={(d) => yScale(getY(d)) ?? 0}
                  stroke="#333"
                  strokeWidth={even ? 2 : 1}
                  strokeOpacity={even ? 0.6 : 1}
                  shapeRendering="geometricPrecision"
                  markerMid="url(#marker-circle)"
                  markerStart={markerStart}
                  markerEnd={markerEnd}
                />
              </Group>
            );
          })}
      </svg>
      <style jsx>{`
        .visx-curves-demo label {
          font-size: 12px;
        }
      `}</style>
    </div>
  );
}

export default function App() {
  return (
    <div className="App">
      <Example width={500} height={300} />
    </div>
  );
}

We get the curve types from the @visx/curve library.

The curveTypes is generated from the keys of the module object.

The data for the curves are created from the generateDataValue function from the @visx/mock-data module.

The getX and getY functions are the data accessor functions to get the data for the x and y axes respectively.

xScale has the x-axis scale.

yScale has the y-axis scale.

The Example component has the curves.

We have a dropdown to set the curveType state.

And we have a checkbox to set the showPoints state.

We add the markers for the curves with the MarkerX , MarkerCross , MarkerCircle , MarkerLine and MarkerArrow components.

Then to add the curves, we add the LinePath component.

We set the curve prop to the type of curve we want to render.

data has the line data.

x and y have the x and y values to render respectively.

stroke and strokeWidth have the stroke styles.

We set the markers to display in the curve with the markerMid , markerStart and markerEnd props.

markerMid has the markers in the middle of the curve.

And the others are displayed at the ends.

Conclusion

We can add curves easily 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 *