Public
Edited
Oct 21, 2023
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
${ctx.canvas}
Insert cell
Insert cell
ctx = DOM.context2d(width, height, 1)
Insert cell
draw(circles, data)
Insert cell
data = mkData(circles, 100000)
Insert cell
draw = (circles, data) => {
ctx.fillStyle = "black"; //"hsl(216deg 100% 13%)";
ctx.fillRect(0, 0, width, height);

circles.map((c) => {
const { x, y, r, i } = c,
color = d3.color(scheme[i % scheme.length]);

color.opacity = circleOpacity;

ctx.strokeStyle = color;
ctx.lineWidth = 3;

ctx.beginPath();
ctx.arc(scaleX(x), scaleY(y), scaleR(r * beta), 0, Math.PI * 2);
ctx.stroke();
});

data.map((d) => {
const { x, y, i } = d,
color = d3.color(scheme[i % scheme.length]);

color.opacity = 0.2;

ctx.fillStyle = color;
ctx.fillRect(scaleX(x), scaleY(y), 1, 1);
ctx.beginPath();
});
}
Insert cell
Insert cell
mkData = (circles, numPoints = 10000) => {
const copyCircles = circles.map((d) => Object.assign({}, d)),
rndInt = d3.randomInt(numCircles),
rndPnt = () => {
return {
x: d3.randomUniform(-100, 100)(),
y: d3.randomUniform(-100, 100)()
};
};

copyCircles.map((d) => (d.r *= beta));

const data = [];

var pnt, circle;

for (let i = 0; i < numPoints; ++i) {
pnt = rndPnt();

for (let j = 0; j < loops; ++j) {
circle = copyCircles[rndInt()];
if (dist(pnt, circle) > circle.r) pnt = invert(pnt, circle);
}

data.push(Object.assign(pnt, { i: circle.i }));
}

return data;
}
Insert cell
circles = {
refresh;

// const defaultCircles = [
// [0.5, 0.5, 0.5],
// [-0.5, 0.5, 0.5],
// [0.5, -0.5, 0.5],
// [-0.5, -0.5, 0.5],
// [0, 0, 0.75 / 2]
// ];

// return defaultCircles.map((d, i) => {
// return { i, x: d[0], y: d[1], r: d[2] };
// });

const circles = [],
rndR = d3.randomUniform(0.1, 0.3),
rndP = d3.randomUniform(-1.0, 1.0);

for (let i = 0; i < numCircles; ++i) {
circles.push({ i, x: rndP(), y: rndP(), r: rndR() });
}

if (numCircles > 10) {
const graph = {
nodes: circles.map((d) => Object.assign({}, d)), //Array.from({ length: numCircles }, () => ({})),
links: []
};

circles.map((c, src) => {
const others = circles.filter((d) => d.i !== c.i),
{ r } = c;

others.map((d, tar) => {
graph.links.push({ source: src, target: tar, r });
});
});

circles.graph = graph;

const tick = () => {
const scaleX = d3
.scaleLinear()
.domain(d3.extent(graph.nodes, (d) => d.x))
.range([-1, 1]),
scaleY = d3
.scaleLinear()
.domain(d3.extent(graph.nodes, (d) => d.y))
.range([-1, 1]);

circles.map((c, i) => {
c.x = scaleX(graph.nodes[i].x);
c.y = scaleY(graph.nodes[i].y);
});

// const data = mkData(circles);
// draw(circles, data);
};

const simulation = d3
.forceSimulation()
.nodes(graph.nodes)
.force("charge", d3.forceManyBody())
.force("center", d3.forceCenter(0, 0))
.force(
"collide",
d3.forceCollide((d) => d.r)
)
.on("tick", tick);
}

// // simulation.restart();
// for (let i = 0; i < 100; ++i) {
// simulation.tick();
// }

return circles;
}
Insert cell
Insert cell
dist = (a, b) => {
return Math.hypot(a.x - b.x, a.y - b.y);
}
Insert cell
invert = (pnt, circle) => {
const { x: cx, y: cy, r } = circle,
{ x: px, y: py } = pnt,
[vx, vy] = [px - cx, py - cy],
t = (r * r) / (vx * vx + vy * vy);

return { x: cx + t * vx, y: cy + t * vy };
}
Insert cell
invert1 = function ([px, py], [cx, cy], r) {
let r2 = r * r;
let [vx, vy] = [px - cx, py - cy];
let t = r2 / (vx * vx + vy * vy);
return [cx + t * vx, cy + t * vy];
}
Insert cell
scaleR = d3
.scaleLinear()
.domain([0, 1])
.range([0, height / 2])
Insert cell
scaleY = d3.scaleLinear().domain([-1, 1]).range([0, height])
Insert cell
scaleX = d3
.scaleLinear()
.domain([0, 1])
.range([width / 2, width / 2 + height / 2])
Insert cell
aspect = 4 / 3
Insert cell
width = 800
Insert cell
height = width / aspect
Insert cell
d3 = require("d3")
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