Skip to content

Instantly share code, notes, and snippets.

@redblobgames
Forked from HarryStevens/.block
Last active September 30, 2022 16:42
Show Gist options
  • Save redblobgames/76308dd1a7e89bd08aeadc4a87675353 to your computer and use it in GitHub Desktop.
Save redblobgames/76308dd1a7e89bd08aeadc4a87675353 to your computer and use it in GitHub Desktop.
Voronoi Jiggle
license: gpl-3.0
<html>
<head>
<style>
body {
margin: 0;
}
.control {
position: absolute;
right: 5px;
top: 5px;
}
.control .label {
font-family: sans-serif;
text-align: center;
}
.control input {
display: inline-block;
vertical-align: middle;
}
.control output {
display: inline-block;
font-family: monospace;
vertical-align: middle;
}
.voronoi {
stroke: black;
}
</style>
</head>
<body>
<div class="control">
<div class="jiggle-control">
<div class="label">Jiggle</div>
<div class="range">
<input type="range" min="0.0" step="0.1" max="9.9" value="0.5">
<output></output>
</div>
</div>
<div class="flow-control">
<div class="label">Flow</div>
<div class="range">
<input type="range" min="-20" step="0.1" max="20" value="5">
<output></output>
</div>
</div>
</div>
<div class="jiggle"></div>
<script src="https://unpkg.com/flubber@0.3.0"></script>
<script type="module">
import { Delaunay } from "https://cdn.skypack.dev/d3-delaunay@6";
import { interpolateLab } from "https://cdn.skypack.dev/d3-interpolate@3";
import { polygonArea } from "https://cdn.skypack.dev/d3-polygon@3";
import { randomUniform } from "https://cdn.skypack.dev/d3-random@3";
import { scaleLinear, scaleSequential } from "https://cdn.skypack.dev/d3-scale@4";
import { select } from "https://cdn.skypack.dev/d3-selection@3";
import { interval } from "https://cdn.skypack.dev/d3-timer@3";
import { transition } from "https://cdn.skypack.dev/d3-transition@3";
const { abs, max, min } = Math;
const width = window.innerWidth;
const height = window.innerHeight;
const duration = 12; // milliseconds of transition duration
const radius = 5; // circle radius
const x = randomUniform(radius, width - radius);
const y = randomUniform(radius, height - radius);
const data = Array.from({ length: 64 })
.map((_, id) => ({ id, x: x(), y: y() }));
const color = scaleSequential()
.domain([0, width * height / data.length * 2])
.interpolator(interpolatePalette(["#fc8d62", "#ffd92f", "#66c2a5", "#8da0cb"]));
const jiggleInput = document.querySelector(".jiggle-control input");
const jiggleValue = document.querySelector(".jiggle-control output");
const flowInput = document.querySelector(".flow-control input");
const flowValue = document.querySelector(".flow-control output");
const svg = select(".jiggle").append("svg")
.attr("width", width)
.attr("height", height);
function redraw(data){
const voronoi = Array.from(
Delaunay
.from(data.map(({x, y}) => [x, y]))
.voronoi([0, 0, width, height])
.cellPolygons(),
(p, i) => Object.assign(p, { data: data[i] })
);
// transition
const t = transition()
.duration(duration);
// JOIN
const polygon = svg.selectAll(".voronoi")
.data(voronoi, d => d.data.id);
const circle = svg.selectAll(".dot")
.data(data, d => d.id);
// UPDATE
polygon
.transition(t)
.attrTween("d", (d, i, e) => {
const prev = select(e[i]).attr("d");
const curr = `M${d.join("L")}Z`;
return flubber.interpolate(prev, curr);
})
.style("fill", d => color(abs(polygonArea(d))));
circle
.transition(t)
.attr("cx", d => d.x)
.attr("cy", d => d.y);
// ENTER
polygon.enter().append("path")
.attr("class", "voronoi")
.attr("d", d => `M${d.join("L")}Z`)
.style("fill", d => color(abs(polygonArea(d))));
circle.enter().append("circle")
.attr("class", "dot")
.attr("r", 5)
.attr("cx", d => d.x)
.attr("cy", d => d.y);
}
redraw(data);
const wrapX = (v) => (v + width) % width
interval(() => {
const jiggle = jiggleInput.valueAsNumber;
const flow = flowInput.valueAsNumber;
const random = randomUniform(-jiggle, jiggle);
jiggleValue.innerHTML = jiggle.toFixed(1);
flowValue.innerHTML = flow.toFixed(1);
data.forEach(d => {
d.x = wrapX(d.x + random() + flow * (d.y/height - 0.5))
d.y = min(height - radius, max(radius, d.y + random()));
return d;
});
redraw(data);
}, duration * 2);
// https://observablehq.com/@harrystevens/roll-your-own-color-palette-interpolator
function interpolatePalette(palette){
const domain = [0];
for (let i = 1, l = palette.length - 1; i <= l; i++){
domain[i] = i / l;
}
const scale = scaleLinear(domain, palette)
.interpolate(interpolateLab)
.clamp(true);
return t => scale(t);
}
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment