Public
Edited
Jun 29, 2023
Insert cell
Insert cell
Insert cell
chart = {
replay;

const quadtree = d3.quadtree().extent([
[0, 0],
[width, height]
]);
const newCircle = circleGenerator(maxRadius, quadtree);

const svg = d3.create("svg").attr("viewBox", [0, 0, width, height]);

for (let i = 0; i < N; ++i) {
const circle = newCircle(Math.min(100, Math.pow(1.01, i)));
if (circle === null) continue;

svg
.append("circle")
.attr("cx", circle[0])
.attr("cy", circle[1])
.attr("r", 0)
.attr("fill", color(circle))
.transition()
.attr("r", circle[2]);

const materialized = materialize(quadtree);
console.log(materialized);
const quad = svg
.selectAll(".node")
.data(materialized, (x, i) => x)
.enter()
.append("rect")
.attr("class", "node")
.attr("x", (d) => d.x0)
.attr("y", (d) => d.y0)
.attr("width", (d) => d.y1 - d.y0)
.attr("height", (d) => d.x1 - d.x0);

quad.classed("visited", (d) => d.node.visited);
// quad.classed("ignored", (d) => d.node.ignored);

await sleep(2000);
yield svg.node();
}
}
Insert cell
function circleGenerator(maxRadius, quadtree) {
return k => {
let bestX, bestY, bestDistance = 0;

for (var i = 0; i < k; ++i) {

// Make a random point
const x = Math.random() * width;
const y = Math.random() * height;

// make a maxRadius box
const rx1 = x - maxRadius * 2;
const rx2 = x + maxRadius * 2;
const ry1 = y - maxRadius * 2;
const ry2 = y + maxRadius * 2;
let distance = maxRadius;

// By iterating on the quadtree, we can compare the random point to each circles
// ignoring some parts

quadtree.visit((node, x1, y1, x2, y2) => {
// If the node has no circles, we can ignore it
if (!node.length) {
node.visited = true
do {
// Extract circle center and radius
const [px, py, pr] = node.data;

// Distance of random point to circle
const dx = x - px;
const dy = y - py;
const d2 = dx * dx + dy * dy;
const r2 = pr * pr;

// If we are within a circle, we ignore all remaining points from this quad node
node.ignored = true
if (d2 < r2) return distance = 0, true; // within a circle
const d = Math.sqrt(d2) - pr;
if (d < distance) distance = d;
} while (node = node.next); // Go to next datum
}
return !distance || x1 > rx2 || x2 < rx1 || y1 > ry2 || y2 < ry1; // or outside search radius
});

if (distance > bestDistance) bestX = x, bestY = y, bestDistance = distance;
}

if (bestDistance <= padding) return null;
const best = [bestX, bestY, bestDistance - padding];
quadtree.add(best);
return best;
};
}
Insert cell
N = 2500 // number of circles
Insert cell
maxRadius = 32 // maximum radius of circle
Insert cell
padding = 1 // padding between circles; also minimum radius
Insert cell
height = 500
Insert cell
color = () => d3.interpolateGreys(Math.random() * 0.6 + 0.2)
Insert cell
// Materialize the quadtree into an array of rectangles.
function materialize(quadtree) {
const rects = [];
quadtree.visit((node, x0, y0, x1, y1) => {
rects.push({ x0, y0, x1, y1, node });
});
return rects;
}
Insert cell
html`
<style>
rect {
fill: none;
stroke: red;
stroke-width: 1px;
}

rect.visited {
stroke: blue;
}

rect.ignored {
stroke: yellow;
}
</style>
`
Insert cell
sleep = dur => new Promise(resolve => setTimeout(resolve, dur))
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