Public
Edited
Apr 25
1 star
Insert cell
Insert cell
Insert cell
{
const size = 640;
const x = d3.randomUniform(0, size);
const r = d3.randomUniform(12, 12);
const nodes = Array.from({ length: n }, (d) => ({ x: x(), y: x(), r: r() }));
const state = cm.state({ nodes, selected: null });
const ticker = cm.ticker().on("animate", () => simulation.tick());

const simulation = manyBodyForce(nodes, {
ontick: (I, positions) => {
for (const i of I) {
const { x, y } = positions[i];
state.nodes[i].x = x;
state.nodes[i].y = y;
}
state.nodes = [...state.nodes];
}
});

// This is for Observable Notebook. Dispose scope when cell reruns.
invalidation.then(() => ticker.dispose());

function ondrag(event, index) {
const { x, y } = event;
simulation.set(index, x, y);
}

function ondragstart(event, index) {
state.selected = index;
}

function ondragend(event, index) {
state.selected = null;
}

return cm.svg`<svg ${{ width: size, height: size }}>
${() =>
state.nodes.map((d, i) =>
cm.draggable(
{
ondrag: (e) => ondrag(e, i),
onstart: (e) => ondragstart(e, i),
onend: (e) => ondragend(e, i)
},
[
cm.svg`<circle
${{ cx: d.x, cy: d.y, r: d.r, style: "cursor:pointer" }}
${{ fill: state.selected === i ? "red" : "black" }}
/>`
]
)
)}
</svg>`;
}
Insert cell
function manyBodyForce(nodes, { ontick } = {}) {
// Compute a rough radius for the shape of body.
const radius = 20 * Math.sqrt(nodes.length);

// Use arrays to store attributes for body.
const I = Array.from({ length: nodes.length }, (_, i) => i);
const accelerations = I.map(() => cm.vec());
const positions = I.map((i) => cm.vec(nodes[i].x, nodes[i].y));
const velocitys = I.map(() => cm.vec());
const masses = I.map((i) => nodes[i].r / 50);

// Apply attractions from each other, then update positions and call callback.
function tick() {
for (const i of I) for (const j of I) if (i !== j) attract(i, j);
for (const i of I) update(i);
ontick(I, positions);
}

function set(i, x, y) {
positions[i].set(x, y);
}

function attract(i, j) {
const force = cm.vecSub(positions[j], positions[i]);
const d = Math.max(1, force.mag());
const t = (d - radius) / d;
const strength = masses[i] * masses[j] * t;
force.mag(strength);
applyForce(i, force);
}

function applyForce(i, force) {
const f = cm.vecDiv(force, masses[i]);
accelerations[i].set(f.x, f.y);
velocitys[i]
.mult(0.99) // Reduce velocity each time a force is applied
.add(accelerations[i]);
}

function update(i) {
positions[i].add(velocitys[i]);
}

return { tick, set };
}
Insert cell
cm = ({
...(await require("charmingjs@0.0.10")),
...extension
})
Insert cell
import { extension } from "@charming-art/charming-extension"
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