chart = {
const width = 2688;
const height = 1680;
const strength = -100;
const node_size = 10;
const alpha_target = 0.3;
const edge_opacity = 0.6;
const weight1 = 0.31;
const weight2 = 0.33;
const weight3 = 0.357;
const weight4 = 0.39;
const light = "lightgray";
const dark = "black";
const thin = 1;
const thick = 1;
const dot_length = 2;
const dot_spacing = 2;
const dash_length = 6;
const dash_spacing = 6;
// Text distance from node
const x = 8;
const y = -8;
/*
Color scale
Denim: systems programming
Pumpkin: object-oriented programming
Slimy green: computing
Middle purple: functional programming
Acid gold: dynamic
Cerulean: scripting
*/
const color = d3.scaleOrdinal(d3.schemeCategory10);
color.domain(Array.from({ length: 10 }, (_, i) => i));
// Read nodes
const nodes = data.nodes.map(node => ({...node}));
// Edges contain weak connections greater than weight4 and all strong connections
const edges = data.edges.map(edge => ({...edge}));
// edges.push(...data.weak_connections.filter(edge => edge.weight > weight4));
// Create a simulation with several forces
const simulation = d3.forceSimulation(nodes)
.force("edge", d3.forceLink(edges).id(node => node.id))
.force("charge", d3.forceManyBody().strength(strength))
.force("center", d3.forceCenter(width / 2, height / 2))
.on("tick", ticked);
// Create the SVG container
const svg = d3.create("svg")
.attr("width", width)
.attr("height", height)
.attr("viewBox", [0, 0, width, height])
.attr("style", "max-width: 100%; height: auto;");
// Draw a line for each edge
const edge = svg.append("g")
.attr("stroke-opacity", edge_opacity)
.selectAll()
.data(edges)
.join("line")
.each(function(edge) {
d3.select(this)
// Lexically similar pairs have darker edges
.attr("stroke", edge.weight < weight2 ? light : dark)
.attr("x1", edge.source.x)
.attr("y1", edge.source.y)
.attr("x2", edge.target.x)
.attr("y2", edge.target.y)
.attr("stroke-width", edge.weight < weight4 ? thin : thick)
.attr("stroke-dasharray", () => {
if (edge.weight < weight1) return `${dot_length},${dot_spacing}`;
else if (edge.weight < weight3) return `${dash_length},${dash_spacing}`;
});
});
// Hash function to convert a string to a numeric value
function hashCode(str) {
let hash = 0;
for (let i = 0; i < str.length; i++) {
const char = str.charCodeAt(i);
hash = (hash << 5) - hash + char;
}
return hash;
}
// Draw a circle for each node
const node = svg.append("g")
.selectAll()
.data(nodes)
.join("g")
.call(d3.drag().on("start", dragstarted).on("drag", dragged).on("end", dragended));
node.append("circle")
.attr("r", node => node_size)
.attr("fill", node => color(hashCode(node.id) % 10));
// Append title for tooltip
node.append("title")
.text(node => node.id);
// Append text for label
const textLabels = node.append("text")
.text(node => node.id)
.attr("x", x)
.attr("y", y)
.style("visibility", "hidden"); // Initially hide text labels
// Show text label on mouseover
node.on("mouseover", function() {
d3.select(this).select("text").style("visibility", "visible");
});
// Hide text label on mouseout and delay hiding for 10 seconds
node.on("mouseout", function() {
const currentNode = d3.select(this);
const textLabel = currentNode.select("text");
// Delay hiding for 10 seconds
setTimeout(function() {
textLabel.style("visibility", "hidden");
}, 10000); // 10000 milliseconds = 10 seconds
});
node.call(d3.drag().on("start", dragstarted).on("drag", dragged).on("end", dragended));
// Set the positions of the nodes each time the simulation moves
function ticked() {
edge
.attr("x1", edge => edge.source.x)
.attr("y1", edge => edge.source.y)
.attr("x2", edge => edge.target.x)
.attr("y2", edge => edge.target.y);
node
.attr("transform", edge => `translate(${edge.x},${edge.y})`);
}
// Fix the node position to the mouse when dragged
function dragstarted(event) {
if (!event.active) simulation.alphaTarget(alpha_target).restart();
if (edges.some(edge => edge.source === event.subject || edge.target === event.subject)) {
event.subject.fx = event.subject.x;
event.subject.fy = event.subject.y;
} else {
simulation.force("center", null);
simulation.force("charge", null);
}
}
// Update the position of the node being dragged
function dragged(event) {
event.subject.fx = event.x;
event.subject.fy = event.y;
}
// Unfix the node position to the mouse when drag ends
function dragended(event) {
if (!event.active) simulation.alphaTarget(0);
if (edges.some(edge => edge.source === event.subject || edge.target === event.subject)) {
event.subject.fx = null;
event.subject.fy = null;
} else {
simulation.force("center", d3.forceCenter(width / 2, height / 2));
simulation.force("charge", d3.forceManyBody().strength(strength));
}
}
// Stop the previous simulation
invalidation.then(() => simulation.stop());
return svg.node();
}