Public
Edited
Jan 11, 2024
1 fork
Insert cell
# I've been playing a lot of Dyson Sphere program
Insert cell
Insert cell
graph = parseGraphViz(graphRaw)
Insert cell
graph.nodes.sort((a,b) => b.incomingEdges - a.incomingEdges)
Insert cell
node = {
const colorFromHighlights = ({ isRoot, isLeaf, onlyHasOneOutput }) => {
const r = highlights.includes("Leaves") && isLeaf ? 255 : 0;
const g = highlights.includes("Roots") && isRoot ? 255 : 0;
const b = highlights.includes("Only one output") && onlyHasOneOutput ? 255 : 0;
return `rgb(${r}, ${g}, ${b})`;
};
const highlight_color = "gold"
return selection => {
const size = 100;
selection.attr("y", d => d.depth * 400);
selection.append("circle")
.attr("r", d => d.outgoingEdges * 4 + 3)
.attr("fill", d => colorFromHighlights({...d}))
selection.append("text")
.attr("fill", "slategrey")
.attr("x", 24)
.text(d => d.id)
};
}
Insert cell
edge = {
return selection => {
selection.append("line")
.attr("stroke-width", 1)
selection.append("circle")
.attr("r", 1)
};
}
Insert cell
viewof highlights = Inputs.checkbox(["Leaves","Roots", "Only one output"], {label: "Highlights"})
Insert cell
chart.svg.node()
Insert cell
range = 80
Insert cell
Insert cell
chart = {
const height =400;
const svg = d3.create("svg").attr("viewBox", [-width / 2, -height / 2, width, height]);
const edges = graph.edges
.map(e => ({...e}))
;

const depthToY = d => d.depth * range + (d.randId * range / 2) - height/3;

function getDepth(node) {
if (isRoot(node)) { return 0; }
const incomingEdge = graph.edges.find(e => e.target === node.id);
const parent = graph.nodes.find(n => n.id === incomingEdge.source);
return getDepth(parent) + 1;
}

function isRoot(node) {
return graph.edges.every(e => e.target !== node.id);
}

const randoms = Array.from({length: graph.nodes.length}, () => Math.random());
const nodes = graph.nodes
.map(n => ({...n}))
.map((n,i) => ({
...n,
incomingEdges: graph.edges.filter(e => e.target === n.id).length,
outgoingEdges: graph.edges.filter(e => e.source === n.id).length,
isRoot: isRoot(n),
isLeaf: graph.edges.every(e => e.source !== n.id),
onlyHasOneOutput: graph.edges.filter(e => e.source === n.id).length === 1,
depth: getDepth(n),
randId: randoms[i]
}));

const linkSelection = svg
.append("g")
.attr("stroke", "#999")
.selectAll("line")
.data(edges)
.join("g")
.call(edge)
;
const nodeSelection = svg
.append("g")
.selectAll("g")
.data(nodes)
.join("g")
.call(node)
;

const simulation = d3.forceSimulation(nodes)
.force("link", d3.forceLink(edges).id(d => d.id).distance(d => (d.source.outgoingEdges + 1)*50))
//.force("center", d3.forceCenter(0,0))
.force("collide", d3.forceCollide().radius(80))
.force("x", d3.forceX(-100).strength(.01))
//.force("forceY", d3.forceY(d => 400 * d.depth - 800))
.alphaDecay(0)
;


let time = 0;
simulation.on("tick", () => {
time++;


nodeSelection
.attr("transform", d => `translate(${d.x}, ${depthToY(d)})`)
;
linkSelection.selectAll("line")
.attr("x1", d => d.source.x)
.attr("y1", d => depthToY(d.source))
.attr("x2", d => d.target.x)
.attr("y2", d => depthToY(d.target))
const prog = 1 - (time/47 % 1);
linkSelection.selectAll("circle")
.attr("r", 2)
.attr("cx", d => d.target.x + (d.source.x - d.target.x) * prog)
.attr("cy", d => depthToY(d.target) + (depthToY(d.source) - depthToY(d.target)) * prog)
.attr("fill", d => "black");
if (time === 100) {
simulation.force("collide", null);
simulation.force("x", null);
}
});
invalidation.then(() => simulation.stop());
return { simulation, svg, edges, nodes, time };
}
Insert cell
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