Visx is a library that lets us add graphics to our React app easily.
A Voronoi diagram is a diagram that’s composed of partitions in a plane.
In this article, we’ll look at how to use it to add Voronoi diagrams into our React app.
Install Required Packages
We have to install a few modules.
To get started, we run:
npm i @visx/clip-path @visx/event @visx/gradient @visx/group @visx/responsive @visx/voronoi
to install the packages.
Create the Diagram
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 Voronoi diagram, we write:
import React, { useState, useMemo, useRef } from "react";
import { Group } from "@visx/group";
import { GradientOrangeRed, GradientPinkRed } from "@visx/gradient";
import { RectClipPath } from "@visx/clip-path";
import { voronoi, VoronoiPolygon } from "@visx/voronoi";
import { localPoint } from "@visx/event";
const data = new Array(150).fill(null).map(() => ({
x: Math.random(),
y: Math.random(),
id: Math.random().toString(36).slice(2)
}));
const neighborRadius = 75;
const defaultMargin = {
top: 0,
left: 0,
right: 0,
bottom: 76
};
const Example = ({ width, height, margin = defaultMargin }) => {
const innerWidth = width - margin.left - margin.right;
const innerHeight = height - margin.top - margin.bottom;
const voronoiLayout = useMemo(
() =>
voronoi({
x: (d) => d.x * innerWidth,
y: (d) => d.y * innerHeight,
width: innerWidth,
height: innerHeight
})(data),
[innerWidth, innerHeight]
);
const polygons = voronoiLayout.polygons();
const svgRef = useRef(null);
const [hoveredId, setHoveredId] = useState(null);
const [neighborIds, setNeighborIds] = useState(new Set());
return width < 10 ? null : (
<svg width={width} height={height} ref={svgRef}>
<GradientOrangeRed id="voronoi_orange_red" />
<GradientPinkRed id="voronoi_pink_red" />
<RectClipPath
id="voronoi_clip"
width={innerWidth}
height={innerHeight}
rx={14}
/>
<Group
top={margin.top}
left={margin.left}
clipPath="url(#voronoi_clip)"
onMouseMove={(event) => {
if (!svgRef.current) return;
const point = localPoint(svgRef.current, event);
if (!point) return;
const closest = voronoiLayout.find(point.x, point.y, neighborRadius);
if (closest && closest.data.id !== hoveredId) {
const neighbors = new Set();
const cell = voronoiLayout.cells[closest.index];
if (!cell) return;
cell.halfedges.forEach((index) => {
const edge = voronoiLayout.edges[index];
const { left, right } = edge;
if (left && left !== closest) neighbors.add(left.data.id);
else if (right && right !== closest) neighbors.add(right.data.id);
});
setNeighborIds(neighbors);
setHoveredId(closest.data.id);
}
}}
onMouseLeave={() => {
setHoveredId(null);
setNeighborIds(new Set());
}}
>
{polygons.map((polygon) => (
<VoronoiPolygon
key={`polygon-${polygon.data.id}`}
polygon={polygon}
fill={
hoveredId &&
(polygon.data.id === hoveredId ||
neighborIds.has(polygon.data.id))
? "url(#voronoi_orange_red)"
: "url(#voronoi_pink_red)"
}
stroke="#fff"
strokeWidth={1}
fillOpacity={
hoveredId && neighborIds.has(polygon.data.id) ? 0.5 : 1
}
/>
))}
{data.map(({ x, y, id }) => (
<circle
key={`circle-${id}`}
r={2}
cx={x * innerWidth}
cy={y * innerHeight}
fill={id === hoveredId ? "fuchsia" : "#fff"}
fillOpacity={0.8}
/>
))}
</Group>
</svg>
);
};
export default function App() {
return (
<div className="App">
<Example width={500} height={300} />
</div>
);
}
We create the data for the diagram with the data
array.
The x
and y
properties are for the x and y coordinates of the partition.
And id
is a unique ID for each partition.
neighborhoodRadius
has the radius for the partitions to highlight when we hover over one partition.
defaultMargin
has the default margins for the diagram.
The Example
component has the Voronoi diagram.
We compute the innerWidth
and innerHeight
of the diagram to compute the display area of the diagram.
The voronoiLayout
variable computes the partition layout from the dimensions of the diagram.
The polygons
variable has the polygons for the partitions.
Next, we add the GradientOrangeRed
to highlight the partition that we hovered over.
GradientPinkRed
has the normal color gradient for the partitions.
In the Group
component, we create the partition polygons with the polygons.map
call.
In the onMouseMove
handler, we check if there are any neighboring polygons with the closest
variable, which is generated from the voronoiLayout.find
call.
We use that to generate the cell
variable, which we use to add polygons to the neighbord
object to highlight the neighboring partitions.
It returns the VoronoiPolygon
component with the ID with the fill color.
And we use the x
and y
coordinates to display a dot in the center of each polygon.
Conclusion
We can create a Voronoi diagram easily in our React app with the Visx library.