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 radial lines 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/mock-data @visx/responsive @visx/scale @visx/shape
to install the packages.
Create the Radial Line
We can create the chart by adding the items provided by the modules.
We use the data from the @visx/mock-data
module.
Then we can create the radial line chart by writing:
import React, { useRef, useState, useEffect } from "react";
import { Group } from "@visx/group";
import { LineRadial } from "@visx/shape";
import { scaleTime, scaleLog } from "@visx/scale";
import { curveBasisOpen } from "@visx/curve";
import appleStock from "@visx/mock-data/lib/mocks/appleStock";
import { LinearGradient } from "@visx/gradient";
import { animated, useSpring } from "react-spring";
const green = "#e5fd3d";
export const blue = "#aeeef8";
const darkgreen = "#dff84d";
export const background = "#744cca";
const darkbackground = "#603FA8";
const springConfig = {
tension: 20
};
// utils
function extent(data, value) {
const values = data.map(value);
return [Math.min(...values), Math.max(...values)];
}
// accessors
const date = (d) => new Date(d.date).valueOf();
const close = (d) => d.close;
// scales
const xScale = scaleTime({
range: [0, Math.PI * 2],
domain: extent(appleStock, date)
});
const yScale = scaleLog({
domain: extent(appleStock, close)
});
const angle = (d) => xScale(date(d)) ?? 0;
const radius = (d) => yScale(close(d)) ?? 0;
const firstPoint = appleStock[0];
const lastPoint = appleStock[appleStock.length - 1];
const Example = ({ width, height, animate = true }) => {
const lineRef = useRef(null);
const [lineLength, setLineLength] = useState(0);
const [shouldAnimate, setShouldAnimate] = useState(false);
const spring = useSpring({
frame: shouldAnimate ? 0 : 1,
config: springConfig,
onRest: () => setShouldAnimate(false)
});
const effectDependency = lineRef.current;
useEffect(() => {
if (lineRef.current) {
setLineLength(lineRef.current.getTotalLength());
}
}, [effectDependency]);
if (width < 10) return null;
yScale.range([0, height / 2 - 20]);
const yScaleTicks = yScale.ticks();
const handlePress = () => setShouldAnimate(true);
return (
<>
{animate && (
<>
<button
type="button"
onClick={handlePress}
onTouchStart={handlePress}
>
Animate
</button>
<br />
</>
)}
<svg
width={width}
height={height}
onClick={() => setShouldAnimate(!shouldAnimate)}
>
<LinearGradient from={green} to={blue} id="line-gradient" />
<rect width={width} height={height} fill={background} rx={14} />
<Group top={height / 2} left={width / 2}>
{yScaleTicks.map((tick, i) => (
<circle
key={`radial-grid-${i}`}
r={yScale(tick)}
stroke={blue}
strokeWidth={1}
fill={blue}
fillOpacity={1 / (i + 1) - (1 / i) * 0.2}
strokeOpacity={0.2}
/>
))}
{yScaleTicks.map((tick, i) => (
<text
key={`radial-grid-${i}`}
y={-(yScale(tick) ?? 0)}
dy="-.33em"
fontSize={8}
fill={blue}
textAnchor="middle"
>
{tick}
</text>
))}
<LineRadial angle={angle} radius={radius} curve={curveBasisOpen}>
{({ path }) => {
const d = path(appleStock) || "";
return (
<>
<animated.path
d={d}
ref={lineRef}
strokeWidth={2}
strokeOpacity={0.8}
strokeLinecap="round"
fill="none"
stroke={animate ? darkbackground : "url(#line-gradient)"}
/>
{shouldAnimate && (
<animated.path
d={d}
strokeWidth={2}
strokeOpacity={0.8}
strokeLinecap="round"
fill="none"
stroke="url(#line-gradient)"
strokeDashoffset={spring.frame.interpolate(
(v) => v * lineLength
)}
strokeDasharray={lineLength}
/>
)}
</>
);
}}
</LineRadial>
{[firstPoint, lastPoint].map((d, i) => {
const cx = ((xScale(date(d)) ?? 0) * Math.PI) / 180;
const cy = -(yScale(close(d)) ?? 0);
return (
<circle
key={`line-cap-${i}`}
cx={cx}
cy={cy}
fill={darkgreen}
r={3}
/>
);
})}
</Group>
</svg>
</>
);
};
export default function App() {
return (
<div className="App">
<Example width={500} height={300} />
</div>
);
}
We set the color for the lines and background color with the green
, blue
and background
variables.
darkBackground
is used for the dark background.
The extent
function returns an array of number with the min and max values of the data.
date
and close
are data accessors that return the data for the time and value respectively.
The xScale
and yScale
values are created from the domain
and range
.
xScale
has both the domain and range so that the radial line can be created.
We also need the angle
and radius
functions to compute the points for the radial line.
We have the shouldAnimate
state to set when to animate the filling of the line.
In the Group
component, we have the circle
element to create the concentric circles.
The text
elements have the text with the values for each ring.
The LineRadial
component renders the line from the values returned from the angle
and radius
function which we pass in as props.
The render prop returns the animated.path
component, which is created from the d
path string.
The 2nd animated.path
component has the filled line which is generated by animating the stroke
and strokeDashOffset
.
The circle
below that is added to the start and end of the line respectively as markers for the ends of the line.
Conclusion
We can add a radial line easily into our React app with the Visx library.