Published
Edited
May 29, 2019
10 stars
Insert cell
Insert cell
Insert cell
Insert cell
canvas = {
const n = 120
const height = Math.ceil(width * screen.height / screen.width);
const margin = 60;
const context = DOM.context2d(width, height);
const particles = Array.from({length: n}, () => [Math.random() * width, Math.random() * height, 0, 0]);
const colors = d3.schemeSet3;
const particleColors = particles.map((c,i) => i % colors.length);
context.canvas.style.background = "#fff";
context.strokeStyle = "black";
let lastMutation = 0;
let counts = Array.from({length: colors.length}, () => 0);
let alive = colors.length;
while (true) {
const delaunay = new d3.Delaunay.from(particles);
const voronoi = delaunay.voronoi([0, 0, width, height]);
context.save();
context.clearRect(0, 0, width, height);
// context.beginPath();
// delaunay.renderPoints(context);
// context.fill();
// context.beginPath();
// voronoi.render(context);
for (let i=0; i<n; i++) {
context.beginPath();
// if (particleColors[i]> 0 && Math.random()<0.001) particleColors[i]--;
// else if (particleColors[i] < colors.length-1 && Math.random()<0.001) particleColors[i]++;
const js = Array.from(delaunay.neighbors(i));
const n_counts = Array.from({length: colors.length}, () => 0);
js.forEach(j=> { n_counts[particleColors[j]]++; });
n_counts.forEach( (v,k)=> {
if (v > js.length * (alive > 7 ? 0.40 :0.65) && Math.random()<0.005 && counts[k] > 3) {
particleColors[i] = k;
}
})
if (js.length > 3 && js.every(j => j == js[0])) {
particleColors[i] = particleColors[js[0]];
}
if (js.length > 0 && Math.random()<0.0020) {
const k = Math.floor(Math.random() * js.length);
particleColors[i] = particleColors[js[k]];
}
if (Math.random()< (alive < 5 ? 0.001 : 0.0001)) {
particleColors[i] = Math.floor(Math.random() * colors.length);
// lastMutation = i;
}
if (Math.random()<0.0001) {
let k = i;
while (k==i) {
k = Math.floor(Math.random() * n)
}
if (counts[particleColors[k]] / counts[particleColors[i]] < 0.5) {
particleColors[i] = particleColors[k];
}
else {
particleColors[k] = particleColors[i];
}
}
counts = Array.from({length: colors.length}, () => 0);
particleColors.forEach(v => { counts[v]++; lastMutation = v; });
alive = counts.filter(v => v > 0).length;
counts.forEach((v,k) => {
if (v > 0 && v < counts[lastMutation]) {
lastMutation = k;
}
})
// yield lastMutation;
// await Promises.delay(100);
context.fillStyle = colors[particleColors[i]];
context.lineWidth = particleColors[i] == lastMutation && counts[lastMutation] <= 3 ? 3 : 1;
context.strokeStyle = particleColors[i] == lastMutation && counts[lastMutation] <= 3 ? "black" : "#777";
voronoi.renderCell(i, context);
context.fill();
context.stroke();
context.closePath();
// yield context.canvas;
// await Promises.delay(200);
}
// context.stroke();
yield context.canvas;
for (const p of particles) {
p[0] += p[2];
p[1] += p[3];
if (p[0] < -margin) p[0] += width + margin * 2;
else if (p[0] > width + margin) p[0] -= width + margin * 2;
if (p[1] < -margin) p[1] += height + margin * 2;
else if (p[1] > height + margin) p[1] -= height + margin * 2;
p[2] += 0.2 * (Math.random() - 0.5) - 0.01 * p[2];
p[3] += 0.2 * (Math.random() - 0.5) - 0.01 * p[3];
}
}
}
Insert cell
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more