D3's general update pattern with a Voronoi diagram & rapidly updating data.
Last active
September 30, 2022 16:15
-
-
Save HarryStevens/6c4ee7e57df79fbd3137aad5de289cf6 to your computer and use it in GitHub Desktop.
Voronoi Jiggle
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
license: gpl-3.0 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<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 .value { | |
display: inline-block; | |
font-family: monospace; | |
vertical-align: middle; | |
} | |
.voronoi { | |
stroke: black; | |
} | |
</style> | |
</head> | |
<body> | |
<div class="control"> | |
<div class="label">Jiggle</div> | |
<div class="range"> | |
<input type="range" min="0.1" step="0.1" max="9.9" value="1.5"> | |
<div class="value"></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 input = document.querySelector(".control input"); | |
const value = document.querySelector(".control .value"); | |
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); | |
interval(() => { | |
const jiggle = +input.value; | |
const random = randomUniform(-jiggle, jiggle); | |
value.innerHTML = jiggle.toFixed(1); | |
data.forEach(d => { | |
d.x = min(width - radius, max(radius, d.x + random())); | |
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