merge parallel edges in graph visualization; update link representation to include combined types and max count

This commit is contained in:
ImBenji 2026-04-23 16:58:46 +01:00
parent 0bc62b0a9b
commit 8588f4904f

View file

@ -1347,12 +1347,13 @@ function renderIntelGraph() {
// deep-copy so d3 doesnt mutate our stored arrays on re-render
const nodesCopy = nodes.map(n => ({ ...n }));
// merge parallel edges (same src+tgt pair) into one — concat labels, take max count
// merge parallel + reciprocal edges into one — sort the pair so A→B and B→A share a key
const edgeMap = new Map();
for (const l of links) {
const src = typeof l.source === 'object' ? l.source.key : l.source;
const tgt = typeof l.target === 'object' ? l.target.key : l.target;
const key = `${src}||${tgt}`;
const [a, b] = src < tgt ? [src, tgt] : [tgt, src];
const key = `${a}||${b}`;
if (edgeMap.has(key)) {
const existing = edgeMap.get(key);
if (!existing.types.includes(l.type)) existing.types.push(l.type);
@ -1402,18 +1403,10 @@ function renderIntelGraph() {
const maxDegree = Math.max(...Object.values(degree), 1);
const simulation = d3.forceSimulation(nodesCopy)
.force('link', d3.forceLink(linksCopy).id(d => d.key).distance(d => {
const src = typeof d.source === 'object' ? d.source.key : d.source;
const tgt = typeof d.target === 'object' ? d.target.key : d.target;
const deg = Math.max(degree[src] || 1, degree[tgt] || 1);
return 80 + deg * 8;
}))
.force('charge', d3.forceManyBody().strength(d => {
const deg = degree[d.key] || 1;
return -300 - deg * 40;
}))
.force('link', d3.forceLink(linksCopy).id(d => d.key).distance(60))
.force('charge', d3.forceManyBody().strength(-120))
.force('center', d3.forceCenter(width / 2, height / 2))
.force('collide', d3.forceCollide(d => d.tracked ? 50 : 36))
.force('collide', d3.forceCollide(d => d.tracked ? 48 : 34).strength(1))
.alphaDecay(0.015);