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.