// Traceability Sankey Diagram (artifactGraph)
// ----------------------------------------------
// Pattern: Left-to-right Sankey flow showing the full SE traceability chain.
//
// Layout strategy:
// - 7 columns: Literature → Stakeholders → Needs → Requirements → System Reqs →
// Architecture → Verification
// - Nodes are thin vertical bars (nodeW=14px), height proportional to their value
// (number of items they represent), scaled relative to the tallest column
// - Links are cubic bezier curves (horizontal S-curves) with width proportional
// to the number of traces flowing through that relationship
// - Gradient strokes on links blend the source column color into the target column color
//
// Node positioning:
// - Each column's nodes are vertically centered in the available height
// - Spacing between nodes = scale * 1.5 (proportional gap)
// - colNodes groups nodes by column index for layout calculation
//
// Link routing:
// - srcOff/tgtOff track cumulative vertical offset per node to stack multiple
// links without overlap (each link's width is added to the offset after drawing)
// - Bezier control points use horizontal midpoint: M(sx,sy) C(mx,sy) (mx,ty) (tx,ty)
//
// Label placement:
// - Columns 0-3: labels to the LEFT of the bar (text-anchor: end)
// - Columns 4-6: labels to the RIGHT of the bar (text-anchor: start)
// - Column headers are bold colored text above the diagram
//
// To add a node: append to nodes[] with {id, col, value, label}.
// To add a link: append to links[] with {s: sourceId, t: targetId, v: traceCount}.
// Gradient IDs are auto-generated from source-target names (spaces/parens sanitized).
artifactGraph = {
const w = 1242, h = 932;
const m = {top: 40, right: 30, bottom: 40, left: 30};
const svg = d3.create("svg")
.attr("viewBox", [0, 0, w, h])
.attr("width", w).attr("height", h)
.style("font-family", "system-ui, sans-serif")
.style("margin-top", "20px");
const colColors = ["#E45756", "#B279A2", "#4C78A8", "#4C78A8", "#72B7B2", "#54A24B", "#F58518"];
const colLabels = ["Literature", "Stakeholders", "Needs (SN)", "Requirements (SR)", "System Reqs", "Architecture", "Verification"];
const numCols = 7;
const colW = (w - m.left - m.right) / numCols;
const nodeW = 14;
colLabels.forEach((label, i) => {
svg.append("text")
.attr("x", m.left + colW * i + colW / 2).attr("y", m.top - 12)
.attr("text-anchor", "middle")
.text(label).style("font-size", "14px").style("font-weight", "bold").style("fill", colColors[i]);
});
const nodes = [
{id: "SysMBench", col: 0, value: 3, label: "SysMBench"},
{id: "SysTemp", col: 0, value: 2, label: "SysTemp"},
{id: "Bader", col: 0, value: 3, label: "Bader et al."},
{id: "Ferrari", col: 0, value: 1, label: "Ferrari et al."},
{id: "Darm", col: 0, value: 2, label: "Darm et al."},
{id: "Li", col: 0, value: 2, label: "Li et al."},
{id: "OpenViking", col: 0, value: 2, label: "OpenViking"},
{id: "Manus", col: 0, value: 1, label: "Manus"},
{id: "xc-mcp", col: 0, value: 2, label: "xc-mcp"},
{id: "Advisor", col: 1, value: 2},
{id: "Collaborator", col: 1, value: 1},
{id: "OSS Community", col: 1, value: 2},
{id: "Practitioners", col: 1, value: 5},
{id: "GitLab", col: 1, value: 2},
{id: "Implementers", col: 1, value: 2},
{id: "INCOSE", col: 1, value: 1},
{id: "DevOps", col: 1, value: 1},
{id: "SN-Process", col: 2, value: 4, label: "Process (4)"},
{id: "SN-Integration", col: 2, value: 3, label: "Integration (3)"},
{id: "SN-Usability", col: 2, value: 4, label: "Usability (4)"},
{id: "SN-Parsing", col: 2, value: 4, label: "Parsing (4)"},
{id: "SR-Process", col: 3, value: 3, label: "Process (3)"},
{id: "SR-Repo", col: 3, value: 3, label: "Repo/Deploy (3)"},
{id: "SR-Docs", col: 3, value: 3, label: "Docs (3)"},
{id: "SR-Parser", col: 3, value: 6, label: "Parser (6)"},
{id: "FR-MCP", col: 4, value: 6, label: "FR-MCP (6)"},
{id: "FR-REPO", col: 4, value: 7, label: "FR-REPO (7)"},
{id: "FR-SYS", col: 4, value: 9, label: "FR-SYS (9)"},
{id: "NFR-PERF", col: 4, value: 2, label: "NFR-PERF (2)"},
{id: "NFR-SEC", col: 4, value: 3, label: "NFR-SEC (3)"},
{id: "NFR-DEP", col: 4, value: 4, label: "NFR-DEP (4)"},
{id: "NFR-DOC", col: 4, value: 3, label: "NFR-DOC (3)"},
{id: "mcp-server", col: 5, value: 9, label: "mcp-server"},
{id: "repo-client", col: 5, value: 7, label: "repo-client"},
{id: "sysml-parser", col: 5, value: 7, label: "sysml-parser"},
{id: "Build/Container", col: 5, value: 4, label: "Build/Container"},
{id: "Documentation", col: 5, value: 3, label: "Documentation"},
{id: "Test (26)", col: 6, value: 26, label: "Test (26)"},
{id: "Demo (2)", col: 6, value: 2, label: "Demo (2)"},
{id: "Inspect (6)", col: 6, value: 6, label: "Inspect (6)"}
];
const links = [
{s: "SysMBench", t: "Practitioners", v: 2},
{s: "SysMBench", t: "Implementers", v: 1},
{s: "SysTemp", t: "Practitioners", v: 1},
{s: "SysTemp", t: "Implementers", v: 1},
{s: "Bader", t: "Practitioners", v: 1},
{s: "Bader", t: "OSS Community", v: 1},
{s: "Bader", t: "GitLab", v: 1},
{s: "Ferrari", t: "Practitioners", v: 1},
{s: "Darm", t: "OSS Community", v: 1},
{s: "Darm", t: "Practitioners", v: 1},
{s: "Li", t: "Practitioners", v: 1},
{s: "Li", t: "Implementers", v: 1},
{s: "OpenViking", t: "GitLab", v: 1},
{s: "OpenViking", t: "Practitioners", v: 1},
{s: "Manus", t: "Practitioners", v: 1},
{s: "xc-mcp", t: "OSS Community", v: 1},
{s: "xc-mcp", t: "Practitioners", v: 1},
{s: "Advisor", t: "SN-Process", v: 2},
{s: "Collaborator", t: "SN-Process", v: 1},
{s: "OSS Community", t: "SN-Integration", v: 2},
{s: "Practitioners", t: "SN-Usability", v: 3},
{s: "Practitioners", t: "SN-Parsing", v: 2},
{s: "GitLab", t: "SN-Integration", v: 1},
{s: "GitLab", t: "SN-Process", v: 1},
{s: "Implementers", t: "SN-Parsing", v: 2},
{s: "INCOSE", t: "SN-Process", v: 1},
{s: "DevOps", t: "SN-Usability", v: 1},
{s: "SN-Process", t: "SR-Process", v: 3},
{s: "SN-Integration", t: "SR-Repo", v: 3},
{s: "SN-Usability", t: "SR-Docs", v: 3},
{s: "SN-Usability", t: "SR-Repo", v: 1},
{s: "SN-Parsing", t: "SR-Parser", v: 4},
{s: "SR-Process", t: "FR-MCP", v: 1},
{s: "SR-Repo", t: "FR-REPO", v: 5},
{s: "SR-Repo", t: "NFR-DEP", v: 3},
{s: "SR-Docs", t: "NFR-DOC", v: 3},
{s: "SR-Docs", t: "FR-SYS", v: 1},
{s: "SR-Parser", t: "FR-SYS", v: 5},
{s: "SR-Parser", t: "FR-MCP", v: 1},
{s: "FR-MCP", t: "mcp-server", v: 6},
{s: "FR-REPO", t: "repo-client", v: 7},
{s: "FR-SYS", t: "sysml-parser", v: 7},
{s: "FR-SYS", t: "mcp-server", v: 2},
{s: "NFR-PERF", t: "mcp-server", v: 1},
{s: "NFR-SEC", t: "mcp-server", v: 2},
{s: "NFR-SEC", t: "repo-client", v: 1},
{s: "NFR-DEP", t: "Build/Container", v: 4},
{s: "NFR-DOC", t: "Documentation", v: 3},
{s: "mcp-server", t: "Test (26)", v: 8},
{s: "repo-client", t: "Test (26)", v: 6},
{s: "sysml-parser", t: "Test (26)", v: 10},
{s: "sysml-parser", t: "Inspect (6)", v: 2},
{s: "Build/Container", t: "Test (26)", v: 2},
{s: "Build/Container", t: "Demo (2)", v: 2},
{s: "Documentation", t: "Inspect (6)", v: 3}
];
const colNodes = Array.from({length: numCols}, (_, i) => nodes.filter(n => n.col === i));
const maxColValue = d3.max(colNodes, ns => d3.sum(ns, n => n.value));
const availH = h - m.top - m.bottom - 60;
const scale = availH / (maxColValue + colNodes[0].length * 2);
const nodePos = {};
colNodes.forEach((ns, ci) => {
const totalH = d3.sum(ns, n => n.value * scale) + (ns.length - 1) * scale * 1.5;
let cy = m.top + 30 + (availH - totalH) / 2;
ns.forEach(n => {
const nh = n.value * scale;
nodePos[n.id] = {
x: m.left + ci * colW + (colW - nodeW) / 2,
y: cy, h: nh, col: ci
};
cy += nh + scale * 1.5;
});
});
const srcOff = {}, tgtOff = {};
nodes.forEach(n => { srcOff[n.id] = 0; tgtOff[n.id] = 0; });
links.forEach(l => {
const sp = nodePos[l.s], tp = nodePos[l.t];
const lw = Math.max(l.v * scale * 0.6, 2);
const sx = sp.x + nodeW, sy = sp.y + srcOff[l.s] + lw / 2;
const tx = tp.x, ty = tp.y + tgtOff[l.t] + lw / 2;
const mx = (sx + tx) / 2;
const sc = colColors[sp.col], tc = colColors[tp.col];
const gid = `sg-${l.s}-${l.t}`.replace(/[\s()]/g, "_");
const grad = svg.append("defs").append("linearGradient")
.attr("id", gid).attr("gradientUnits", "userSpaceOnUse")
.attr("x1", sx).attr("y1", 0).attr("x2", tx).attr("y2", 0);
grad.append("stop").attr("offset", "0%").attr("stop-color", sc).attr("stop-opacity", 0.35);
grad.append("stop").attr("offset", "100%").attr("stop-color", tc).attr("stop-opacity", 0.35);
svg.append("path")
.attr("d", `M${sx},${sy} C${mx},${sy} ${mx},${ty} ${tx},${ty}`)
.attr("fill", "none").attr("stroke", `url(#${gid})`)
.attr("stroke-width", lw);
srcOff[l.s] += lw;
tgtOff[l.t] += lw;
});
nodes.forEach(n => {
const p = nodePos[n.id];
const c = colColors[n.col];
svg.append("rect")
.attr("x", p.x).attr("y", p.y)
.attr("width", nodeW).attr("height", p.h).attr("rx", 3)
.attr("fill", c).attr("opacity", 0.7);
const label = n.label || n.id;
const textX = n.col <= 3 ? p.x - 6 : p.x + nodeW + 6;
const anchor = n.col <= 3 ? "end" : "start";
svg.append("text")
.attr("x", textX).attr("y", p.y + p.h / 2 + 4)
.attr("text-anchor", anchor)
.text(label).style("font-size", "13px").style("fill", "#444");
});
svg.append("text").attr("x", w / 2).attr("y", h - 8)
.attr("text-anchor", "middle")
.text("18 Papers → 8 Stakeholders → 15 Needs → 15 Requirements → 34 System Reqs → 7 Components → 34 Verifications")
.style("font-size", "13px").style("fill", "#999").style("font-style", "italic");
return svg.node();
}