function ForceGraph(
{
nodes,
links
},
{
nodeId = (d) => d.id,
nodeGroup,
nodeGroups,
nodeTitle,
nodeWidth,
nodeStatus = (d) => d.status,
nodeInfo,
nodeLang,
nodeFill = "currentColor",
nodeStroke = "#fff",
nodeStrokeWidth = 1.5,
nodeStrokeOpacity = 1,
nodeRadius = (d) => d.width,
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,
invalidation
} = {}
) {
// Compute values.
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 Info = nodeInfo == null ? null : d3.map(nodes, nodeInfo);
const Lang = nodeLang == null ? null : d3.map(nodes, nodeLang).map(intern);
const R = nodeWidth == null ? null : d3.map(nodes, nodeWidth);
const Stat = d3.map(nodes, nodeStatus);
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 : function (G) {
// var c = 'gray';
// if(G == "Media") c = "#20a5ba";
// if(G == "Person") c = "#e6b744";
// return c;
// }
const widthN = nodeWidth == null ? null : function (nodeWidth) {
var w;
w = Math.ceil(Math.sqrt((nodeWidth/10000)/Math.PI)); // R=√S/π
return w < 5 ? 5 : w;
}
const color = nodeLang == null ? null : function (Lang) {
var c = 'gray';
if(Lang == "RUS") c = "#4da2f1ff";
if(Lang == "ROM") c = "#8ad554ff";
return c;
}
const stat_color = function (Stat, Lang) {
var c = '#fff';
if(Stat == "inf"){
if(Lang == "RUS") c = "#4da2f1ff";
if(Lang == "ROM") c = "#8ad554ff";
}
return c;
}
//d3.scaleOrdinal(nodeGroups, colors);
// Construct the forces.
const forceNode = d3.forceManyBody();
const forceLink = d3.forceLink(links).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", 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("fill", nodeFill)
.attr("stroke", nodeStroke)
.attr("stroke-opacity", nodeStrokeOpacity)
.attr("stroke-width", nodeStrokeWidth)
// SM: change
// .selectAll("circle")
.selectAll("g")
.data(nodes)
// SM: change
// .join("circle")
.join("g")
// SM: change
// .attr("r", nodeRadius)
.call(drag(simulation));
// SM: change
// append circle and text to node <g> (selection of all <g> elements corresponding to each node)
// node.append("circle").attr("r", nodeRadius);
// node.append("circle").attr("r", ({ index: i }) => R[i]);
node.append("circle").attr("r", ({ index: i }) => widthN(R[i]));
node
.append("text")
.text(({ index: i }) => T[i])
.attr("fill", "black")
.attr("stroke", "none")
.attr("font-family", "Arial, Helvetica, sans-serif")
.attr("font-family", "Arial, Helvetica, sans-serif")
.attr("font-size", "0.4em");
if (W) link.attr("stroke-width", ({ index: i }) => W[i]);
if (L) link.attr("stroke", ({ index: i }) => L[i]);
if (Stat) node.attr("fill", ({ index: i }) => stat_color(Stat[i], Lang[i])); //({ index: i }) => color(Lang[i]));
if (Lang) node.attr("stroke", ({ index: i }) => color(Lang[i]));
if (Info) node.append("title").text(({ index: i }) => Info[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("transform", (d) => `translate(${d.x} ${d.y})`);
// SM: change
// instead of moving the circle centers we transform the whole <g>
// .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;
simulation.restart(); //
}
function dragged(event) {
event.subject.fx = event.x;
event.subject.fy = event.y;
simulation.restart();
}
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 } });
}