Higashi Kota
4 min readMay 19, 2022

How to accomplish “Voronoi/Delaunay” transition animation using d3 and react-spring

Voronoi
Delaunay

Hi,there.I’m Kota.

This is my first post.

The “Voronoi/Delaunay” is one of the most well-known figures in the following link.

I created a transition animation of two figures using d3-voronoi, d3-delaunay and react-spring. In this issue, I would like to show you the implementation.

First, voronoi.

Using d3-voronoi and react-spring, I created.

This is the code that calculates the coordinates of the “Voronoi” figure.

Width and height is box stage size.

The makePath function converts the path command to a string that can be applied to the value of “clip-path”, a css property.

To achieve random transitions, we use “chance” library to recalculate the points of the Voronoi figure each time the vertex coordinates are changed.

const makePath = (d) => {
return `path("M ${d.join(' L ')} Z")`;
};
const data = useMemo(() => {
const v = voronoi().extent([
[0, 0],
[width, height],
]);
return v.polygons(verticles).map((cell, i) => {
const path = makePath(cell);
return {
id: i,
path,
x: chance().integer({min: -width, max: width}),
y: chance().integer({min: -height, max: height}),
};
});
}, [verticles]);

demo site

demo code

demo video

Second, delaunay.

Using d3-delaunay and react-spring, I created.

This is the code that calculates the coordinates of the “Delaunay” figure.

Width and height is box stage size.

It is almost the same as the Voronoi diagram, recalculating the path commands, etc. of the Delaunay diagram every time the vertex coordinates are changed.

const data = useMemo(() => {
const d = [];
let id = 0;
const delaunay = Delaunay.from(verticles);
const {points, triangles} = delaunay;
for (let i = 0; i < triangles.length; i++) {
const t0 = triangles[i * 3 + 0];
const t1 = triangles[i * 3 + 1];
const t2 = triangles[i * 3 + 2];
let path = `path("`;
if (
points[t0 * 2] &&
points[t0 * 2 + 1] &&
points[t1 * 2] &&
points[t1 * 2 + 1] &&
points[t2 * 2] &&
points[t2 * 2 + 1]
) {
path = path + `M ${points[t0 * 2]},${points[t0 * 2 + 1]}`;
path = path + ` L ${points[t1 * 2]},${points[t1 * 2 + 1]}`;
path = path + ` L ${points[t2 * 2]},${points[t2 * 2 + 1]}`;
path = path + ` Z")`;
d.push({
id,
x: chance().integer({min: -width, max: width}),
y: chance().integer({min: -height, max: height}),
path,
});
id = id + 1;
}
}
return d;
}, [verticles]);

If the number of triangles is fixed and the value is randomized, the number of triangles may be different from the number of triangles in the previous transition, which may affect the behavior of the transition. This may be due to the fact that transition keys are animated in react-spring, and the id may be out of alignment.

So this time we have two buttons.

One is a shuffle button and the other is a button to change the number of divisions.

const handleShuffle = (e) => {
setVerticles(chance().shuffle(verticles));
};
const handleChangeData = (e) => {
const verticles = d3.range(10).map((d) => {
return [Math.random() * width, Math.random() * height];
});
setVerticles(verticles);
};

When the button that changes the number of divisions is pressed continuously, as mentioned earlier, the effect of the shifting of the id causes unintended transitions.

As long as the shuffle button is pressed continuously with the initial coordinate data, the intended transition was confirmed.

If there was an option to fix the number of dronay divisions like a seed, it would be possible to workaround with one button instead of two buttons.

We have not been able to do the handling here this time.

demo site

demo code

demo video

That’s all.

Recently, we have been posting mock-up videos on Twitter, so if you’d like to take a peek, here’s one as well.

See you then, Have fun.