function chart(d3Contour, title, grid, first, invalidation) {
const width = 464,
height = 400,
context = DOM.context2d(width, height),
path = d3.geoPath().context(context);
const Vmax = 0.15;
context.scale(4, 4);
context.font = `${14 / 4}px Arial`;
function drawsystem() {
context.strokeStyle = "#777";
context.lineWidth = 0.05 / 4
context.beginPath();
for (let i = 0; i < 101; i += 4) {
context.moveTo(i, 12);
context.lineTo(i, 100);
if (i > 8) {
context.moveTo(0, i);
context.lineTo(100, i);
}
}
context.stroke();
context.strokeStyle = "black";
context.lineWidth = .25 / 4;
context.beginPath();
for (const c of d3Contour
.contourDensity()
.size([100, 100])
.bandwidth(8)
.thresholds(10)(particles)
) path(c);
context.stroke();
if (grid) {
context.lineWidth = .75 / 4;
context.globalAlpha = 0.5;
for (const p of grid(particles)) {
context.beginPath();
path.pointRadius(Math.sqrt(p.weight) * 1.3);
path(p);
context.fillStyle = "red";
context.fill();
}
context.globalAlpha = 1;
}
context.beginPath();
path.pointRadius(1.3);
path({ type: "MultiPoint", coordinates: particles });
context.strokeStyle = context.fillStyle = "red";
context.stroke();
context.fillStyle = "black";
context.fillText(title, 10 / 4, 24 / 4);
}
function draw() {
if (first) {
for (const p of particles) {
p.v = (p.v || [0, 0]).map(d => d + (Math.random() - .5) ** 5);
const V = Math.hypot(...p.v);
if (V > Vmax) p.v = p.v.map(d => d / (V / Vmax));
p[0] += p.v[0];
p[1] += p.v[1];
if (p[0] < 10) p.v[0] = Math.abs(p.v[0]);
if (p[0] > 90) p.v[0] = -Math.abs(p.v[0]);
if (p[1] < 10) p.v[1] = Math.abs(p.v[1]);
if (p[1] > 90) p.v[1] = -Math.abs(p.v[1]);
}
}
context.fillStyle = "white";
context.fillRect(0, 0, width, height);
drawsystem();
}
if (!pause) {
const timer = d3.timer(draw);
invalidation.then(() => timer.stop());
}
draw();
return context.canvas;
}