Public
Edited
Apr 5, 2024
Insert cell
Insert cell
chart = ForceGraph(formattedData, {
nodeId: d => d.id,
nodeTitle: d => `${d.id}\n${d.topic}`,
linkStrokeWidth: l => Math.sqrt(l.value),
width: 2000,
height: 2000,
invalidation // a promise to stop the simulation when the cell is re-run
});

Insert cell
viewof selectedDate = {
const dates = Array.from(new Set(data.nodes.map(d => d.date))).sort();
const slider = html`<input type="range" min="0" max="${dates.length - 1}" value="0">`;
yield slider;

return dates[+slider.value];
}
Insert cell
function ForceGraph({
nodes,
links
}, {
nodeId = d => d.id,
nodeGroup,
nodeGroups,
nodeTitle,
nodeFill = "currentColor",
nodeStroke = "#fff",
nodeStrokeWidth = 1.5,
nodeStrokeOpacity = 1,
nodeRadius = 5,
nodeStrength,
linkSource = ({source}) => source,
linkTarget = ({target}) => target,
linkStroke = "#999",
linkStrokeOpacity = 0.6,
linkStrokeWidth = 1.5,
linkStrokeLinecap = "round",
linkStrength,
colors = d3.schemeTableau10,
width = 640,
height = 400,
enableZoom = true,
invalidation,
nodeColor // New parameter for node coloring function
} = {}) {
const N = nodes.map(d => d.id);
const LS = links.map(d => d.source);
const LT = links.map(d => d.target);
if (nodeTitle === undefined) nodeTitle = (_, i) => N[i];
const T = nodeTitle == null ? null : nodes.reduce((acc, d, i) => {
acc[d.id] = nodeTitle(d, i);
return acc;
}, {});
const G = nodeGroup == null ? null : nodes.reduce((acc, d) => {
acc[d.id] = nodeGroup(d);
return acc;
}, {});
nodes = nodes.map(d => ({id: d.id}));
links = links.map(d => ({source: d.source, target: d.target}));
const color = nodeGroup == null ? null : d3.scaleOrdinal(nodeGroups, colors);
const forceNode = d3.forceManyBody();
const forceLink = d3.forceLink(links).id(d => d.id);
if (nodeStrength !== undefined) forceNode.strength(nodeStrength);
if (linkStrength !== undefined) forceLink.strength(linkStrength);
const simulation = d3.forceSimulation(nodes)
.force("link", forceLink)
.force("charge", forceNode)
.force("center", d3.forceCenter())
.on("tick", ticked);

const svg = d3.create("svg")
.attr("width", width)
.attr("height", height)
.attr("viewBox", [-width / 2, -height / 2, width, height])
.attr("style", "max-width: 100%; height: auto; height: intrinsic;");

const link = svg.append("g")
.attr("stroke", typeof linkStroke !== "function" ? linkStroke : null)
.attr("stroke-opacity", linkStrokeOpacity)
.attr("stroke-width", typeof linkStrokeWidth !== "function" ? linkStrokeWidth : null)
.attr("stroke-linecap", linkStrokeLinecap)
.selectAll("line")
.data(links)
.join("line");

const node = svg.append("g")
.attr("stroke", nodeStroke)
.attr("stroke-opacity", nodeStrokeOpacity)
.attr("stroke-width", nodeStrokeWidth)
.selectAll("circle")
.data(nodes)
.join("circle")
.attr("r", nodeRadius)
.style("fill", nodeColor) // Set node color based on nodeColor function
.call(drag(simulation));

if (T) node.append("title").text(d => T[d.id]);
if (invalidation != null) invalidation.then(() => simulation.stop());

function ticked() {
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 drag(simulation) {
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()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended);
}

return Object.assign(svg.node(), {scales: {color}});
}

Insert cell
formattedData = transformData(Words_data);
Insert cell
// Define the transform function
function transformData(data) {
const nodesMap = new Map();
const links = data.Links.map(link => ({
source: link.Term_orig,
target: link.Tem_dest,
value: link.Edge_val
}));
data.Nodes.forEach(node => {
if (!nodesMap.has(node.Term)) {
nodesMap.set(node.Term, {
date: node.Date,
topic: node.Topic
});
}
});
const nodes = Array.from(nodesMap, ([term, info]) => ({
id: term,
date: info.date,
topic: info.topic
}));
return { nodes, links };
}

// // Apply the transform function to the JSON data
// const { nodes, links } = transformData(data);

// Output the transformed data
// console.log(nodes);
// console.log(links);
Insert cell
Words_data = FileAttachment("Project_data_10.json").json()
Insert cell
import {howto} from "@d3/example-components"
Insert cell
import {Swatches} from "@d3/color-legend"
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