Published
Edited
Apr 6, 2021
1 fork
22 stars
Insert cell
Insert cell
Insert cell
chart = {
const nodes = network.nodes,
links = network.links;

const context = DOM.context2d(width, height);

const simulation = d3
.forceSimulation(nodes)
.force("charge", d3.forceManyBody().strength(-2))
.force(
"x",
d3.forceX(width / 2).strength(d => centerStrenghtScale(d.value))
)
.force(
"y",
d3.forceY(height / 2).strength(d => centerStrenghtScale(d.value))
)
.force(
"link",
d3
.forceLink(links)
.id(d => d.id)
.distance(10)
)
.force("boundary", forceBoundary(3, 3, width, height));

function ticked() {
context.clearRect(0, 0, width, height);

context.beginPath();
if (simulation.alpha() < 0.5) {
links.forEach(drawLink);
}
context.strokeStyle = "#aaa";
context.stroke();

context.strokeStyle = "#fff";
for (const node of nodes) {
context.beginPath();
drawNode(node);
// context.fillStyle = color(node);
context.fill();
context.stroke();
}
}

function drawLink(d) {
context.moveTo(d.source.x, d.source.y);
context.lineTo(d.target.x, d.target.y);
}

function drawNode(d) {
context.moveTo(d.x + 3, d.y);
context.arc(d.x, d.y, size(d.value), 0, 2 * Math.PI);
}

simulation.on("tick", ticked);

invalidation.then(() => simulation.stop());

// Call ticked once, so it will render inmediately
ticked();

return d3
.select(context.canvas)
.call(drag(simulation))
.node();
}
Insert cell
centerStrenghtScale = d3
.scaleLinear()
.domain(d3.extent(network.nodes, d => d.value))
.range(
centeringForceType === "Larger Nodes Outside"
? [0.5, 0.0001]
: centeringForceType === "Larger Nodes Inside"
? [0.0001, 0.5]
: [0.1, 0.1]
)
Insert cell
{
// Switch the option after 4.5s
await Promises.delay(4500);
viewof centeringForceType.value = "Larger Nodes Outside";
viewof centeringForceType.dispatchEvent(new CustomEvent("input"));
}
Insert cell
network = {
const dLinks = new Map();

for (let t of movies) {
if (t.type == "Movie") {
const key = getKey(t.director, t.show_id);
if (key == "ignore") continue;
if (!dLinks.has(key)) dLinks.set(key, 0);

dLinks.set(key, dLinks.get(key) + 1);
}
}

const dNodes = new Map();
let links = [];
for (let [l, v] of dLinks) {
const [source, target] = l.split("~");

const s = findOrAdd(dNodes, source);
const t = findOrAdd(dNodes, target);

dNodes.set(source, ((s.value += 1), s));
dNodes.set(target, ((t.value += 1), t));
links.push({ source: s, target: t, value: v });
}

const network = { nodes: Array.from(dNodes.values()), links };
return network;
}
Insert cell
drag = simulation => {
function dragsubject(event) {
return simulation.find(event.x, event.y);
}

function dragstarted(event) {
if (!event.active) simulation.alphaTarget(0.3).restart();
event.subject.fx = event.subject.x;
event.subject.fy = event.subject.y;
}

function dragged(event) {
event.subject.fx = event.x;
event.subject.fy = event.y;
}

function dragended(event) {
if (!event.active) simulation.alphaTarget(0);
event.subject.fx = null;
event.subject.fy = null;
}

return d3
.drag()
.subject(dragsubject)
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended);
}
Insert cell
color = d3.scaleOrdinal(d3.schemeCategory10)
Insert cell
opacity = d3
.scaleLinear()
.domain(d3.extent(network.links, d => d.value))
.range([0.1, 1])
Insert cell
size = d3
.scaleLinear()
.domain(d3.extent(network.nodes, d => d.value))
.range([1, 5])
Insert cell
height = 600
Insert cell
findOrAdd = (dNodes, n) => {
if (!dNodes.has(n)) dNodes.set(n, { id: n, value: 0 });
return dNodes.get(n);
}
Insert cell
getKey = (a, b) => { if (a == '' || b == '') return "ignore";
return `${a}~${b}`}
Insert cell
movies = FileAttachment("netflix_titles.csv").csv()
Insert cell
d3 = require("d3@6")
Insert cell
forceBoundary = require("d3-force-boundary")
Insert cell
import { Radio } from "@observablehq/inputs"
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