Public
Edited
Sep 1, 2023
Insert cell
Insert cell
{
const svg = d3.create("svg").attr("viewBox", [0, 0, width, height]),
link = svg
.selectAll(".link")
.data(graph.links, (x) => `${x.source.index}|${x.target.index}`)
.join("line")
.classed("link", true),
node = svg
.selectAll(".node")
.data(graph.nodes)
.join("circle")
.attr("r", 12)
.classed("node", true)
.classed("fixed", (d) => d.fx !== undefined);

yield svg.node();

const simulation = d3
.forceSimulation()
.nodes(graph.nodes)
.force("charge", d3.forceManyBody())
.force("center", d3.forceCenter(width / 2, height / 2))
.force("link", d3.forceLink(graph.links))
.on("tick", tick);

const drag = d3
.drag()
.on("start", dragstart)
.on("drag", dragged)
.on("end", draggend);

node.call(drag).on("click", click);

function tick() {
link
.attr("x1", (d) => d.source.x)
.attr("y1", (d) => d.source.y)
.attr("x2", (d) => d.target.x)
.attr("y2", (d) => d.target.y);
node.attr("cx", (d) => d.x).attr("cy", (d) => d.y);
}

function click(event, d) {
delete d.fx;
delete d.fy;
d3.select(this).classed("fixed", false);
simulation.alpha(1).restart();
}

function dragstart(event, d) {
console.log({ d });
simulation.stop();
d3.select(this).classed("fixed", true);
}

function dragged(event, d) {
const xx = clamp(event.x, 0, width);
const yy = clamp(event.y, 0, height);
d.x = xx;
d.y = yy;
d3.select(this).attr("cx", xx).attr("cy", yy);
svg
.selectAll(".link")
.data(
graph.links.filter((x) => x.source.index === d.index),
(x) => `${x.source.index}|${x.target.index}`
)
.attr("x1", xx)
.attr("y1", yy);
svg
.selectAll(".link")
.data(
graph.links.filter((x) => x.target.index === d.index),
(x) => `${x.source.index}|${x.target.index}`
)
.attr("x2", xx)
.attr("y2", yy);
}
function draggend(event, d) {
const xx = clamp(event.x, 0, width);
const yy = clamp(event.y, 0, height);
d.fx = xx;
d.fy = yy;
//simulation.alpha(1).restart();

d3.select(this).classed("fixed", false);
}
}
Insert cell
c = JSON.parse(
`{"nodes":[{"index":0,"x":643.7635780473545,"y":326.11409230900307,"vy":0.0009665554475077971,"vx":0.0004471053717546039},{"index":1,"x":609.5132169043713,"y":317.1181430586332,"vy":0.0005811239352358275,"vx":0.0005105059544350517},{"index":2,"x":631.669825805727,"y":293.34616359104314,"vy":0.0007713356454594171,"vx":0.0007887999665437334},{"index":3,"x":593.9995206323449,"y":277.1344520014064,"vy":0.00046376785943506014,"vx":0.000744157037988027},{"index":4,"x":564.05791236956,"y":237.6512133767844,"vy":0.00037483669542700973,"vx":0.00039028860355886963},{"index":5,"x":597.6546452016144,"y":189.18158017082135,"vy":-0.00023265081878198106,"vx":0.00006278089396455359},{"index":6,"x":638.1563214602123,"y":172.39305944308464,"vy":-0.0007249979462094941,"vx":-0.00017935039649596586},{"index":7,"x":618.2410987825522,"y":148.0000934839298,"vy":-0.00045885245934598415,"vx":-0.0005226776293748948},{"index":8,"x":653.9204177479494,"y":140.99473299650583,"vy":-0.0010206082022237014,"vx":-0.0005534226823498593},{"index":9,"x":514.8986326668804,"y":267.85251896081127,"vy":0.0002979649298166875,"vx":0.00017381253535886992},{"index":10,"x":491.4624210416062,"y":290.0553006652277,"vy":-0.0014363521080467548,"vx":-0.0015762671009302283},{"index":11,"x":475.4553554373047,"y":307.19821470353423,"vy":0.000236998100194172,"vx":-0.00002163210555269607},{"index":12,"x":455.2058421969482,"y":282.96077725720943,"vy":0.0005228969165396461,"vx":-0.0014758060237977137}],"links":[{"source":{"index":0,"x":643.7635780473545,"y":326.11409230900307,"vy":0.0009665554475077971,"vx":0.0004471053717546039},"target":{"index":1,"x":609.5132169043713,"y":317.1181430586332,"vy":0.0005811239352358275,"vx":0.0005105059544350517},"index":0},{"source":{"index":1,"x":609.5132169043713,"y":317.1181430586332,"vy":0.0005811239352358275,"vx":0.0005105059544350517},"target":{"index":2,"x":631.669825805727,"y":293.34616359104314,"vy":0.0007713356454594171,"vx":0.0007887999665437334},"index":1},{"source":{"index":2,"x":631.669825805727,"y":293.34616359104314,"vy":0.0007713356454594171,"vx":0.0007887999665437334},"target":{"index":0,"x":643.7635780473545,"y":326.11409230900307,"vy":0.0009665554475077971,"vx":0.0004471053717546039},"index":2},{"source":{"index":1,"x":609.5132169043713,"y":317.1181430586332,"vy":0.0005811239352358275,"vx":0.0005105059544350517},"target":{"index":3,"x":593.9995206323449,"y":277.1344520014064,"vy":0.00046376785943506014,"vx":0.000744157037988027},"index":3},{"source":{"index":3,"x":593.9995206323449,"y":277.1344520014064,"vy":0.00046376785943506014,"vx":0.000744157037988027},"target":{"index":2,"x":631.669825805727,"y":293.34616359104314,"vy":0.0007713356454594171,"vx":0.0007887999665437334},"index":4},{"source":{"index":3,"x":593.9995206323449,"y":277.1344520014064,"vy":0.00046376785943506014,"vx":0.000744157037988027},"target":{"index":4,"x":564.05791236956,"y":237.6512133767844,"vy":0.00037483669542700973,"vx":0.00039028860355886963},"index":5},{"source":{"index":4,"x":564.05791236956,"y":237.6512133767844,"vy":0.00037483669542700973,"vx":0.00039028860355886963},"target":{"index":5,"x":597.6546452016144,"y":189.18158017082135,"vy":-0.00023265081878198106,"vx":0.00006278089396455359},"index":6},{"source":{"index":5,"x":597.6546452016144,"y":189.18158017082135,"vy":-0.00023265081878198106,"vx":0.00006278089396455359},"target":{"index":6,"x":638.1563214602123,"y":172.39305944308464,"vy":-0.0007249979462094941,"vx":-0.00017935039649596586},"index":7},{"source":{"index":5,"x":597.6546452016144,"y":189.18158017082135,"vy":-0.00023265081878198106,"vx":0.00006278089396455359},"target":{"index":7,"x":618.2410987825522,"y":148.0000934839298,"vy":-0.00045885245934598415,"vx":-0.0005226776293748948},"index":8},{"source":{"index":6,"x":638.1563214602123,"y":172.39305944308464,"vy":-0.0007249979462094941,"vx":-0.00017935039649596586},"target":{"index":7,"x":618.2410987825522,"y":148.0000934839298,"vy":-0.00045885245934598415,"vx":-0.0005226776293748948},"index":9},{"source":{"index":6,"x":638.1563214602123,"y":172.39305944308464,"vy":-0.0007249979462094941,"vx":-0.00017935039649596586},"target":{"index":8,"x":653.9204177479494,"y":140.99473299650583,"vy":-0.0010206082022237014,"vx":-0.0005534226823498593},"index":10},{"source":{"index":7,"x":618.2410987825522,"y":148.0000934839298,"vy":-0.00045885245934598415,"vx":-0.0005226776293748948},"target":{"index":8,"x":653.9204177479494,"y":140.99473299650583,"vy":-0.0010206082022237014,"vx":-0.0005534226823498593},"index":11},{"source":{"index":9,"x":514.8986326668804,"y":267.85251896081127,"vy":0.0002979649298166875,"vx":0.00017381253535886992},"target":{"index":4,"x":564.05791236956,"y":237.6512133767844,"vy":0.00037483669542700973,"vx":0.00039028860355886963},"index":12},{"source":{"index":9,"x":514.8986326668804,"y":267.85251896081127,"vy":0.0002979649298166875,"vx":0.00017381253535886992},"target":{"index":11,"x":475.4553554373047,"y":307.19821470353423,"vy":0.000236998100194172,"vx":-0.00002163210555269607},"index":13},{"source":{"index":9,"x":514.8986326668804,"y":267.85251896081127,"vy":0.0002979649298166875,"vx":0.00017381253535886992},"target":{"index":10,"x":491.4624210416062,"y":290.0553006652277,"vy":-0.0014363521080467548,"vx":-0.0015762671009302283},"index":14},{"source":{"index":10,"x":491.4624210416062,"y":290.0553006652277,"vy":-0.0014363521080467548,"vx":-0.0015762671009302283},"target":{"index":11,"x":475.4553554373047,"y":307.19821470353423,"vy":0.000236998100194172,"vx":-0.00002163210555269607},"index":15},{"source":{"index":11,"x":475.4553554373047,"y":307.19821470353423,"vy":0.000236998100194172,"vx":-0.00002163210555269607},"target":{"index":12,"x":455.2058421969482,"y":282.96077725720943,"vy":0.0005228969165396461,"vx":-0.0014758060237977137},"index":16},{"source":{"index":12,"x":455.2058421969482,"y":282.96077725720943,"vy":0.0005228969165396461,"vx":-0.0014758060237977137},"target":{"index":10,"x":491.4624210416062,"y":290.0553006652277,"vy":-0.0014363521080467548,"vx":-0.0015762671009302283},"index":17}]}`
)
Insert cell
d3 = require("d3@6")
Insert cell
height = Math.min(500, width * 0.6)
Insert cell
function clamp(x, lo, hi) {
return x < lo ? lo : x > hi ? hi : x;
}
Insert cell
html`<style>

.link {
stroke: #000;
stroke-width: 1.5px;
}

.node {
cursor: move;
fill: #ccc;
stroke: #000;
stroke-width: 1.5px;
}

.node.fixed {
fill: #f00;
}

</style>`
Insert cell
graph = c
Insert cell

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more