Public
Edited
Feb 19, 2024
Insert cell
Insert cell
Insert cell
force_graph = {
let w = 800;
let h = 700;
let s = 0.05;
let chart = d3
.create("svg")
.attr("viewBox", [0, 0, w, h])
.style("max-width", "800px");

let nodes = connections.nodes;
let links = connections.edges;

let link = chart
.append("g")
.attr("class", "links")
.selectAll("line")
.data(links)
.join("line")
.attr("source", (d) => d.source)
.attr("target", (d) => d.target)
.attr("stroke-width", 1)
.attr("stroke", "black");

let node = chart.append("g").attr("class", "nodes");
node
.selectAll("g")
.data(nodes)
.join(function (enter) {
let g = enter
.append("g")
.attr("class", "node-container")
.attr("transform", `scale(${s})`);
g.append("circle")
.attr("r", 500)
.attr("cx", `${0.5 * w}`)
.attr("cy", `${0.5 * h}`)
.attr("fill", "lightgray")
.attr("stroke", "black")
.attr("fill-opacity", 0.4)
.attr("stroke-opacity", 0.2)
.attr("stroke-width", 40);
g.append((d) => svg`${images.get(icon_to_image.get(d.label))}`);
});

let simulation = d3
.forceSimulation()
.force("charge", d3.forceManyBody().strength(-300))
.force("collide", d3.forceCollide(30))
.force("center", d3.forceCenter(w / 2, h / 2))
.force(
"link",
d3.forceLink().id((d) => d.id)
);

simulation.nodes(nodes).on("tick", tick);
simulation.force("link").links(links);
return chart.node();

function tick() {
link
.attr("x1", function (d) {
return d.source.x + (s * w) / 2;
})
.attr("y1", function (d) {
return d.source.y + (s * h) / 2;
})
.attr("x2", function (d) {
return d.target.x + (s * w) / 2;
})
.attr("y2", function (d) {
return d.target.y + (s * h) / 2;
});
node
.selectAll("g.node-container")
.attr("transform", (d) => `translate(${d.x} ${d.y}) scale(${s})`);
}
}
Insert cell
Insert cell
// Map the node labels to SVG names
icon_to_image = new Map([
["attack-pattern", "rect-attack-pattern.svg"],
["campaign", "rect-campaign.svg"],
["course-of-action", "rect-course-of-action.svg"],
["identity-organization", "rect-identity-organization.svg"],
["indicator", "rect-indicator.svg"],
["malware-family", "rect-malware-family.svg"],
["malware", "rect-malware.svg"],
["report", "rect-report.svg"],
["vulnerability", "rect-vulnerability.svg"]
])
Insert cell
images = {
let zipped_images = await FileAttachment("icons.zip").zip();

// Select just the SVG files
let filenames = zipped_images.filenames.filter((s) => s.slice(-4) == ".svg");
// Extract the text of the SVG files
let images = await Promise.all(
filenames.map((f) => zipped_images.file(f).text())
);
// Slice off the XML header
images = images.map((s) => s.slice(39));
// Grab the filenames with directory header sliced off.
let names = filenames.map((s) => s.slice(6));
// Return Map for easy retrieval
return new Map(d3.zip(names, images));
}
Insert cell
connections = {
reload;
let connections = await FileAttachment("generic_n_and_e.json").json();
return connections;
}
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