Public
Edited
Aug 1, 2023
Insert cell
Insert cell
{
const width = 500;
const height = 200;
const svg = d3.create("svg").attr("width", width).attr("height", height),
link = svg
.selectAll(".link")
.data(graph.links)
.join("line")
.classed("link", true),
node = svg
.selectAll(".node")
.data(graph.nodes)
.join("circle")
.attr("r", (d) => (d.type === "origin" ? 1 : 5))
.attr("fill", (d) => (d.type === "origin" ? "red" : "blue"))
.classed("node", true);
// .classed("fixed", (d) => d.fx !== undefined);

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

node.call(drag);

yield svg.node();

const simulation = d3
.forceSimulation()
.nodes(graph.nodes)
.force(
"charge",
d3.forceManyBody().strength((x) => (x.type === "origin" ? 0 : -30))
)
.force("collide", d3.forceCollide(10))
.force(
"link",
d3
.forceLink(graph.links)
.id((x) => x.id)
.strength(2)
)
.on("tick", tick);

function tick() {
for (let node of graph.nodes) {
node.x = clamp(node.x, 0, width);
node.y = clamp(node.y, 0, height);
}
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 dragstart() {
d3.select(this).classed("fixed", true);
}

function dragged(event, d) {
d.fx = clamp(event.x, 0, width);
d.fy = clamp(event.y, 0, height);
simulation.alpha(1).restart();
}

function dragend(event, d) {
d.x = d.fx;
d.y = d.fy;
d.fx = d.fy = undefined;

if (d.type === "label") {
d.fx = labelX;
}
if (d.type === "origin") {
d.fx = originX;
}
simulation.alpha(1).restart();
}
}
Insert cell
html`<style>

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

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

</style>`
Insert cell
labels = [50, 70, 71, 150, 155].map((y, i) => {
return {
id: `label-${i}`,
x: labelX,
fx: labelX,
y: y,
type: "label"
};
})
Insert cell
originX = 100
Insert cell
labelX = originX + 100
Insert cell
origins = labels.map((l, i) => {
return {
id: `origin-${i}`,
x: originX,
y: l.y,
fx: originX,
fy: l.y,
type: "origin"
};
})
Insert cell
graph = {
const nodes = [...origins, ...labels];
const links = labels.map((l, i) => ({
source: `label-${i}`,
target: `origin-${i}`
}));
console.log({ nodes, links });
return { nodes, links };
}
Insert cell
function clamp(x, lo, hi) {
return x < lo ? lo : x > hi ? hi : x;
}
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