Categories
Visx

Add Lines with Multiple Styles into a 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 curves with segments that have their own styles 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/responsive @visx/scale @visx/shape

to install the packages.

Create the Multi-Styled Line

We can create the chart by adding the items provided by the modules.

To add the line, we write:

import React, { useMemo } from "react";
import { scaleLinear } from "@visx/scale";
import { curveCardinal } from "@visx/curve";
import { LinePath, SplitLinePath } from "@visx/shape";
import { LinearGradient } from "@visx/gradient";

const getX = (d) => d.x;
const getY = (d) => d.y;
const background = "#045275";
const backgroundLight = "#089099";
const foreground = "#b7e6a5";

function generateSinPoints({
  width,
  height,
  numberOfWaves = 10,
  pointsPerWave = 10
}) {
  const waveLength = width / numberOfWaves;
  const distanceBetweenPoints = waveLength / pointsPerWave;
  const sinPoints = [];

  for (let waveIndex = 0; waveIndex <= numberOfWaves; waveIndex += 1) {
    const waveDistFromStart = waveIndex * waveLength;

    for (let pointIndex = 0; pointIndex <= pointsPerWave; pointIndex += 1) {
      const waveXFraction = pointIndex / pointsPerWave;
      const waveX = pointIndex * distanceBetweenPoints;
      const globalX = waveDistFromStart + waveX;
      const globalXFraction = (width - globalX) / width;
      const waveHeight =
        Math.min(globalXFraction, 1 - globalXFraction) * height;
      sinPoints.push({
        x: globalX,
        y: waveHeight * Math.sin(waveXFraction * (2 * Math.PI))
      });
    }
  }

  return sinPoints;
}

function Example({
  width,
  height,
  numberOfWaves = 10,
  pointsPerWave = 100,
  numberOfSegments = 8
}) {
  const data = useMemo(
    () => generateSinPoints({ width, height, numberOfWaves, pointsPerWave }),
    [width, height, numberOfWaves, pointsPerWave]
  );

  const dividedData = useMemo(() => {
    const segmentLength = Math.floor(data.length / numberOfSegments);
    return new Array(numberOfSegments)
      .fill(null)
      .map((_, i) => data.slice(i * segmentLength, (i + 1) * segmentLength));
  }, [numberOfSegments, data]);

  const getScaledX = useMemo(() => {
    const xScale = scaleLinear({ range: [0, width], domain: [0, width] });
    return (d) => xScale(getX(d)) ?? 0;
  }, [width]);

  const getScaledY = useMemo(() => {
    const yScale = scaleLinear({ range: [0, height], domain: [height, 0] });
    return (d) => yScale(getY(d)) ?? 0;
  }, [height]);

  return width < 10 ? null : (
    <div>
      <svg width={width} height={height}>
        <LinearGradient
          id="visx-shape-splitlinepath-gradient"
          from={background}
          to={backgroundLight}
          fromOpacity={0.8}
          toOpacity={0.8}
        />
        <rect
          x={0}
          y={0}
          width={width}
          height={height}
          fill="url(#visx-shape-splitlinepath-gradient)"
          rx={14}
        />

        <g transform={`rotate(${0})translate(${-0}, ${-height * 0.5})`}>
          <LinePath
            data={data}
            x={getScaledX}
            y={getScaledY}
            strokeWidth={8}
            stroke="#fff"
            strokeOpacity={0.15}
            curve={curveCardinal}
          />

          <SplitLinePath
            segments={dividedData}
            x={getScaledX}
            y={getScaledY}
            curve={curveCardinal}
            styles={[
              { stroke: foreground, strokeWidth: 3 },
              { stroke: "#fff", strokeWidth: 2, strokeDasharray: "9,5" },
              { stroke: background, strokeWidth: 2 }
            ]}
          >
            {({ segment, styles, index }) =>
              index === numberOfSegments - 1 || index === 2 ? (
                segment.map(({ x, y }, i) =>
                  i % 8 === 0 ? (
                    <circle
                      key={i}
                      cx={x}
                      cy={y}
                      r={10 * (i / segment.length)}
                      stroke={styles?.stroke}
                      fill="transparent"
                      strokeWidth={1}
                    />
                  ) : null
                )
              ) : (
                <LinePath
                  data={segment}
                  x={(d) => d.x || 0}
                  y={(d) => d.y || 0}
                  {...styles}
                />
              )
            }
          </SplitLinePath>
        </g>
      </svg>
    </div>
  );
}

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

We add the getX and getY functions to get the data for the x and y axes.

background is one of the colors of the background gradient.

backgroundLight has the lighter color of the background gradient.

foreground has one of the line colors.

We generate the points for the line with the generateSinPoints function.

globalX has the x coordinates for the sine curve.

And we use the Math.sin method to create the y axis value.

In the Example component, we render the line with various styles.

We divide the line into segments with the dividedData variable.

This is done by creating an array of values and slicing the values created from generateSinPoints with slice .

Then we create the scales for the graph with the getScaledX and getScaledY variables.

We create them with the scaleLinear function since everything is in linear scale.

Then we render the left line segment with the LinePath component.

And we use the SplitLinePath component to render the remaining segments.

We get the styles from the styles prop and render them styles with the LineSegment component we return in the render prop of SplitLinePath .

Conclusion

We can create a line with multiple segments with their own styles in 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 *