Public
Edited
Jan 8, 2023
2 stars
Insert cell
Insert cell
<div style='background: darkgray; height: ${h}px; width: ${w}px' id='container'></div>
Insert cell
viewof radiusSizeFilter = rangeSlider({
title: "Filter nodes by their radius size",
// description: "Filter to a range",
min: 0,
max: 21,
step: 1
})
Insert cell
Insert cell
GRAPH_DATA = {
const nodes = [
{ id: "foo", radius: 10 },
{ id: "bar", radius: 20 },
{ id: "bat", radius: 5 },
{ id: "lonely", radius: 2 }
];

const links = [
{ source: "foo", target: "bar" },
{ source: "foo", target: "bat" }
];
return { nodes, links };
}
Insert cell
Insert cell
graphInstance = {
d3.select("#container").selectAll(".graph-parent").remove();
const graphParentDiv = d3
.select("#container")
.append("div")
.attr("class", "graph-parent");

return DynamicGraph(graphParentDiv, d3, {
width: w,
height: h,
nodeRadius: (node) => node.radius + 10,
tooltipYOffset: -190 // WEIRD... for some Reason in Observable we get an extra ~200px yoffset we have to counteract...
});
}
Insert cell
Insert cell
{
// Observable-specific note, by including graphInstanace in this cell, it will run every time graphInstance gets a (new) value
if (graphInstance) {
// 1. Filter data
const nodes = GRAPH_DATA.nodes.filter(
({ radius }) =>
!radius ||
(radius < radiusSizeFilter[1] && radius > radiusSizeFilter[0])
);
const validNodeIds = nodes.map(({ id }) => id);
const links = GRAPH_DATA.links.filter(({ source, target }) => {
// Note, observable doesn't read the mutated data, so source/target are
console.log(
validNodeIds,
source,
target,
validNodeIds.includes(source.id),
validNodeIds.includes(target.id)
);
return (
(validNodeIds.includes(source.id) &&
validNodeIds.includes(target.id)) ||
// Edge case: before the first updateVis, the ids are strings, not pointers to node objects
(validNodeIds.includes(source) && validNodeIds.includes(target))
);
});
// 2. update the chart
// NOTE: nodes & links are actually mutated by the underlying D3 force layout code
// As such, it's important that we filter on the same arrays we originally passed in, not copy's of them.
// This shouldn't really be an issue, because most people aren't deep cloning for no reason.
graphInstance.updateVis(nodes, links);
return links;
}
}
Insert cell
Insert cell
w = 500
Insert cell
h = 400
Insert cell
Insert cell
DynamicGraph = (await import("d3-dynamic-graph@1.2.0")).default
Insert cell
d3 = require("d3")
Insert cell
Insert cell
import { rangeSlider } from "@mootari/range-slider"
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