function ForceGraph({
nodes,
links
}, {
nodeId = d => d.id,
nodeGroup = [1],
nodeGroups = [1,2,3,4],
nodeTitle,
nodeSize,
nodeColor,
nodeFill = "currentColor",
nodeStroke = "#fff",
nodeStrokeWidth = 1.5,
nodeStrokeOpacity = 1,
nodeRadius = 5,
nodeStrength = -31,
linkSource = ({source}) => source,
linkTarget = ({target}) => target,
linkStroke = "#999",
linkStrokeOpacity = 0.6,
linkStrokeWidth = 1.5,
linkStrokeLinecap = "round",
linkStrength,
width = 640,
height = 400,
invalidation
} = {}) {
let colors = ["#a6cee3","#1f78b4","#b2df8a","#33a02c","#fb9a99","#e31a1c","#fdbf6f","#ff7f00","#cab2d6","#6a3d9a","#34ebcc","#b15928","#34a1eb"];
let trueColors = ["#a6cee3","#1f78b4","#b2df8a","#33a02c","#fb9a99","#e31a1c","#fdbf6f","#ff7f00","#cab2d6","#6a3d9a","#34ebcc","#b15928","#34a1eb"];
const N = d3.map(nodes, nodeId).map(intern);
const LS = d3.map(links, linkSource).map(intern);
const LT = d3.map(links, linkTarget).map(intern);
if (nodeTitle === undefined) nodeTitle = (_, i) => N[i];
const T = nodeTitle == null ? null : d3.map(nodes, nodeTitle);
const G = nodeGroup == null ? null : d3.map(nodes, nodeGroup).map(intern);
const W = typeof linkStrokeWidth !== "function" ? null : d3.map(links, linkStrokeWidth);
const L = typeof linkStroke !== "function" ? null : d3.map(links, linkStroke);
// Replace the input nodes and links with mutable objects for the simulation.
nodes = d3.map(nodes, (_, i) => ({id: N[i]}));
links = d3.map(links, (_, i) => ({source: LS[i], target: LT[i]}));
// Compute default domains.
if (G && nodeGroups === undefined) nodeGroups = d3.sort(G);
// Construct the scales.
const color = nodeGroup == null ? null : d3.scaleOrdinal(nodeGroups, colors);
// Construct the forces.
const forceNode = d3.forceManyBody();
const forceLink = d3.forceLink(links).distance(({index:j}) => distances[j]).id(({index: i}) => N[i]);
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", "100%")
.attr("height", "100%")
.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("fill", "#fff")
.attr("stroke", nodeStroke)
.attr("stroke-opacity", nodeStrokeOpacity)
.attr("stroke-width", nodeStrokeWidth)
.selectAll("circle")
.data(nodes)
.join("circle")
.attr("r", (d,i) => sizes[i])
.call(drag(simulation))
if (W) link.attr("stroke-width", ({index: i}) => W[i]);
if (L) link.attr("stroke", ({index: i}) => L[i]);
if (G) node.attr("fill", (d,i) => colors[c[i]]);
if (T) node.append("title").text(({index: i}) => T[i]);
if (invalidation != null) invalidation.then(() => simulation.stop());
function intern(value) {
return value !== null && typeof value === "object" ? value.valueOf() : value;
}
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);
}
svg.on("click", function(e){
return Object.assign(svg.node(), {scales: {color}});
})
node.on("mouseover", function(e) {
let g = d3.select(this).attr("fill");
for(let i = 0; i < colors.length; i++){
if(colors[i] != g)
colors[i] = "#808080";
}
node.attr("fill", (d,i) => colors[c[i]]);
})
node.on("mouseout", function(e) {
node.attr("fill", (d,i) => trueColors[c[i]]);
for(let i = 0; i < colors.length; i++){
colors[i] = trueColors[i];
}
})
let x = 700
let x2 = x + 35
let r = 22
svg.append("circle").attr("cx",x).attr("cy",-900).attr("r", r).style("fill", colors[10])
svg.append("circle").attr("cx",x).attr("cy",-840).attr("r", r).style("fill", colors[2])
svg.append("text").attr("x", x2).attr("y", -900).text("Social Engineering").style("font-size", "25px").attr("alignment-baseline","middle")
svg.append("text").attr("x", x2).attr("y", -840).text("Accidentally Published").style("font-size", "25px").attr("alignment-baseline","middle")
svg.append("circle").attr("cx",x).attr("cy",-780).attr("r", r).style("fill", colors[3])
svg.append("circle").attr("cx",x).attr("cy",-720).attr("r", r).style("fill", colors[11])
svg.append("text").attr("x", x2).attr("y", -780).text("Hacked").style("font-size", "25px").attr("alignment-baseline","middle")
svg.append("text").attr("x", x2).attr("y", -720).text("Unknown").style("font-size", "25px").attr("alignment-baseline","middle")
svg.append("circle").attr("cx",x -325).attr("cy",-900).attr("r", r).style("fill", colors[6])
svg.append("circle").attr("cx",x-325).attr("cy",-840).attr("r", r).style("fill", colors[7])
svg.append("text").attr("x", x2-325).attr("y", -900).text("Lost/Stolen Computer").style("font-size", "25px").attr("alignment-baseline","middle")
svg.append("text").attr("x", x2-325).attr("y", -840).text("Lost/Stolen Media").style("font-size", "25px").attr("alignment-baseline","middle")
svg.append("circle").attr("cx",x-325).attr("cy",-780).attr("r", r).style("fill", colors[4])
svg.append("circle").attr("cx",x-325).attr("cy",-720).attr("r", r).style("fill", colors[8])
svg.append("text").attr("x", x2-325).attr("y", -780).text("Inside Job").style("font-size", "25px").attr("alignment-baseline","middle")
svg.append("text").attr("x", x2-325).attr("y", -720).text("Poor Security").style("font-size", "25px").attr("alignment-baseline","middle")
svg.append("circle").attr("cx",x -650).attr("cy",-900).attr("r", r).style("fill", colors[12])
svg.append("circle").attr("cx",x-650).attr("cy",-840).attr("r", r).style("fill", colors[9])
svg.append("text").attr("x", x2-650).attr("y", -900).text("Aggregate Bubble").style("font-size", "25px").attr("alignment-baseline","middle")
svg.append("text").attr("x", x2-650).attr("y", -840).text("Rogue Contractor").style("font-size", "25px").attr("alignment-baseline","middle")
svg.append("circle").attr("cx",x -650).attr("cy",-780).attr("r", r).style("fill", colors[0])
svg.append("circle").attr("cx",x-650).attr("cy",-720).attr("r", r).style("fill", colors[5])
svg.append("text").attr("x", x2-650).attr("y", -780).text("Accidentally Exposed").style("font-size", "25px").attr("alignment-baseline","middle")
svg.append("text").attr("x", x2-650).attr("y", -720).text("Intentionally Lost").style("font-size", "25px").attr("alignment-baseline","middle")
svg.append("text").attr("x", -900).attr("y", -900).text("Is data ever safe?").style("font-size", "60px").attr("alignment-baseline","middle")
svg.append("text").attr("x", -900).attr("y", -840).text("A record of all breached consumer data").style("font-size", "40px").attr("alignment-baseline","middle")
return Object.assign(svg.node(), {scales: {color}});
}