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.