This commit is contained in:
Felix Klement committed 2026-09-18 18:28:28 +02:00
1 parent d97f541738
commit 0c2b4e7df2
16 files changed
+696 -179

No files matched your search

+47 -45
View File
@@ -9,10 +9,10 @@ type Groups = HowGroups;
type Pt = [number, number];
type Label = string | string[];
export interface Node { x: number; y: number; label?: Label; dot?: 'main' | 'sub' | 'none'; ring?: number; anchor?: 'start' | 'end' | 'middle'; dx?: number; dy?: number }
export interface Edge { points: Pt[]; tick?: boolean; faint?: boolean; dashed?: boolean }
export interface Node { x: number; y: number; label?: Label; dot?: 'main' | 'sub' | 'none'; ring?: number; anchor?: 'start' | 'end' | 'middle'; dx?: number; dy?: number; group?: string }
export interface Edge { points: Pt[]; tick?: boolean; faint?: boolean; dashed?: boolean; group?: string }
export interface Box { x: number; y: number; w: number; h: number; dashed?: boolean }
export interface Text { x: number; y: number; text: Label; anchor?: 'start' | 'end' | 'middle'; strong?: boolean }
export interface Text { x: number; y: number; text: Label; anchor?: 'start' | 'end' | 'middle'; strong?: boolean; group?: string }
export interface Layout { w: number; h: number; nodes: Node[]; edges: Edge[]; boxes?: Box[]; texts?: Text[]; fontMain?: number; fontSub?: number }
export interface Pair { wide: Layout; tall: Layout }
@@ -20,20 +20,20 @@ const RING = 36;
const LINE = 21; // one line of a 17px label, in viewBox units
/* A ringed main node with its label centred below it and an optional muted line under that. */
function main(x: number, y: number, label: Label, sub?: Label, ring = RING): { nodes: Node[]; texts: Text[] } {
function main(x: number, y: number, label: Label, sub?: Label, ring = RING, group?: string): { nodes: Node[]; texts: Text[] } {
const lines = Array.isArray(label) ? label.length : 1;
const nodes: Node[] = [{ x, y, label, dot: 'main', ring, anchor: 'middle', dx: 0, dy: ring + 22 }];
const texts: Text[] = sub ? [{ x, y: y + ring + 22 + lines * LINE + 2, text: sub, anchor: 'middle' }] : [];
const nodes: Node[] = [{ x, y, label, dot: 'main', ring, anchor: 'middle', dx: 0, dy: ring + 22, group }];
const texts: Text[] = sub ? [{ x, y: y + ring + 22 + lines * LINE + 2, text: sub, anchor: 'middle', group }] : [];
return { nodes, texts };
}
/* A plain dot with its label centred below. */
function dot(x: number, y: number, label: Label): Node {
return { x, y, label, dot: 'main', anchor: 'middle', dx: 0, dy: 28 };
function dot(x: number, y: number, label: Label, group?: string): Node {
return { x, y, label, dot: 'main', anchor: 'middle', dx: 0, dy: 28, group };
}
/* Satellites fanning from a node to a column of dots. */
function fan(from: Pt, xs: number, ys: number[], labels: string[], anchor: 'start' | 'end'): { nodes: Node[]; edges: Edge[] } {
const nodes: Node[] = ys.map((y, i) => ({ x: xs, y, label: labels[i], dot: 'sub', anchor, dx: anchor === 'end' ? -12 : 12 }));
const edges: Edge[] = ys.map((y) => ({ points: [from, [xs, y]], faint: true }));
function fan(from: Pt, xs: number, ys: number[], labels: string[], anchor: 'start' | 'end', group?: string): { nodes: Node[]; edges: Edge[] } {
const nodes: Node[] = ys.map((y, i) => ({ x: xs, y, label: labels[i], dot: 'sub', anchor, dx: anchor === 'end' ? -12 : 12, group }));
const edges: Edge[] = ys.map((y) => ({ points: [from, [xs, y]], faint: true, group }));
return { nodes, edges };
}
const chain = (pts: Pt[], tick = true): Edge[] => pts.slice(1).map((p, i) => ({ points: [pts[i], p], tick }));
@@ -67,12 +67,12 @@ function spine(groups: { title: Label; items?: string[]; note?: string }[]): Lay
export function overview(g: Groups[0]): Pair {
const L = g.labels;
const y1 = 110, y2 = 330, ym = 220, fork = 190, join = 1050;
const sbom = main(340, y1, L.sbom, L.sbomSub), dt = main(640, y1, L.dt, L.dtSub);
const sast = main(340, y2, L.sast, L.sastSub), poly = main(640, y2, L.poly, L.polySub), prism = main(900, y2, L.prism, L.prismSub);
const y1 = 90, y2 = 300, ym = 195, fork = 190, join = 1050;
const sbom = main(340, y1, L.sbom, L.sbomSub, RING, 'sbom'), dt = main(640, y1, L.dt, L.dtSub, RING, 'dt');
const sast = main(340, y2, L.sast, L.sastSub, RING, 'sast'), poly = main(640, y2, L.poly, L.polySub, RING, 'poly'), prism = main(900, y2, L.prism, L.prismSub, RING, 'prism');
const wide: Layout = {
w: 1200, h: 470,
nodes: [dot(80, ym, L.repo), ...sbom.nodes, ...dt.nodes, ...sast.nodes, ...poly.nodes, ...prism.nodes, dot(1120, ym, L.report)],
w: 1200, h: 430,
nodes: [dot(80, ym, L.repo, 'repo'), ...sbom.nodes, ...dt.nodes, ...sast.nodes, ...poly.nodes, ...prism.nodes, dot(1120, ym, L.report, 'report')],
edges: [
{ points: [[80, ym], [fork, ym], [fork, y1], [340, y1]], tick: true },
{ points: [[fork, ym], [fork, y2], [340, y2]], tick: true },
@@ -94,14 +94,15 @@ export function overview(g: Groups[0]): Pair {
export function scanners(g: Groups[1]): Pair {
const L = g.labels;
const focal: Pt = [500, 280];
const ys = g.tools.map((_, i) => 60 + i * 27.5);
const tools = fan(focal, 210, ys, g.tools, 'end');
const one = main(500, 280, L.one, L.oneSub), cwe = main(700, 280, L.cwe, L.cweSub), merge = main(900, 280, L.merge, L.mergeSub), resolve = main(1090, 280, L.resolve, L.resolveSub);
const cy = 232;
const focal: Pt = [500, cy];
const ys = g.tools.map((_, i) => 40 + i * 24);
const tools = fan(focal, 210, ys, g.tools, 'end', 'tools');
const one = main(500, cy, L.one, L.oneSub, RING, 'one'), cwe = main(700, cy, L.cwe, L.cweSub, RING, 'cwe'), merge = main(900, cy, L.merge, L.mergeSub, RING, 'merge'), resolve = main(1090, cy, L.resolve, L.resolveSub, RING, 'resolve');
const wide: Layout = {
w: 1200, h: 560,
w: 1200, h: 450,
nodes: [...tools.nodes, ...one.nodes, ...cwe.nodes, ...merge.nodes, ...resolve.nodes],
edges: [...tools.edges, ...chain([[500, 280], [700, 280], [900, 280], [1090, 280]])],
edges: [...tools.edges, ...chain([[500, cy], [700, cy], [900, cy], [1090, cy]])],
texts: [...one.texts, ...cwe.texts, ...merge.texts, ...resolve.texts],
};
const tall = spine([
@@ -113,14 +114,14 @@ export function scanners(g: Groups[1]): Pair {
export function verdict(g: Groups[2]): Pair {
const L = g.labels;
const y = 300, top = 120;
const ctx = fan([380, y], 230, [100, 140, 180, 220], L.ctx, 'end');
const tools = fan([920, y], 1040, [150, 190, 230, 270], L.tools, 'start');
const outcomes = fan([1220, y], 1330, [240, 280, 320], L.outcomes, 'start');
const context = main(380, y, L.context), s1 = main(640, y, L.stage1), s2 = main(920, y, L.stage2), v = main(1220, y, L.verdict, L.carries);
const y = 260, top = 80;
const ctx = fan([380, y], 230, [60, 100, 140, 180], L.ctx, 'end', 'context');
const tools = fan([920, y], 1040, [110, 150, 190, 230], L.tools, 'start', 'escalation');
const outcomes = fan([1220, y], 1330, [200, 240, 280], L.outcomes, 'start', 'verdict');
const context = main(380, y, L.context, undefined, RING, 'context'), s1 = main(640, y, L.stage1, undefined, RING, 'static'), s2 = main(920, y, L.stage2, undefined, RING, 'escalation'), v = main(1220, y, L.verdict, L.carries, RING, 'verdict');
const wide: Layout = {
w: 1460, h: 420,
nodes: [dot(140, y, L.finding), ...context.nodes, ...ctx.nodes, ...s1.nodes, ...s2.nodes, ...tools.nodes, ...v.nodes, ...outcomes.nodes],
w: 1460, h: 380,
nodes: [dot(140, y, L.finding, 'finding'), ...context.nodes, ...ctx.nodes, ...s1.nodes, ...s2.nodes, ...tools.nodes, ...v.nodes, ...outcomes.nodes],
edges: [
...ctx.edges, ...tools.edges, ...outcomes.edges,
...chain([[140, y], [380, y], [640, y], [920, y], [1220, y]]),
@@ -141,14 +142,14 @@ export function verdict(g: Groups[2]): Pair {
export function dependencies(g: Groups[3]): Pair {
const L = g.labels;
const y = 220, ya = 130, yb = 310, mergeX = 300;
const sbom = main(430, y, L.sbom, L.sbomSub), dt = main(700, y, L.dt, L.dtSub), vdr = main(960, y, L.vdr, L.vdrSub);
const y = 170, ya = 80, yb = 260, mergeX = 300;
const sbom = main(430, y, L.sbom, L.sbomSub, RING, 'sbom'), dt = main(700, y, L.dt, L.dtSub, RING, 'dt'), vdr = main(960, y, L.vdr, L.vdrSub, RING, 'vdr');
const wide: Layout = {
w: 1200, h: 400,
w: 1200, h: 320,
nodes: [
{ x: 150, y: ya, label: L.syft, dot: 'main', anchor: 'end', dx: -16, dy: 6 },
{ x: 150, y: yb, label: L.cdxgen, dot: 'main', anchor: 'end', dx: -16, dy: 6 },
...sbom.nodes, ...dt.nodes, ...vdr.nodes, dot(1130, y, L.report),
{ x: 150, y: ya, label: L.syft, dot: 'main', anchor: 'end', dx: -16, dy: 6, group: 'generators' },
{ x: 150, y: yb, label: L.cdxgen, dot: 'main', anchor: 'end', dx: -16, dy: 6, group: 'generators' },
...sbom.nodes, ...dt.nodes, ...vdr.nodes, dot(1130, y, L.report, 'report'),
],
edges: [
{ points: [[150, ya], [mergeX, ya], [mergeX, y]] },
@@ -168,23 +169,24 @@ export function dependencies(g: Groups[3]): Pair {
export function operate(g: Groups[4]): Pair {
const L = g.labels;
const hub: Pt = [560, 220];
const ys = [100, 160, 220, 280, 340];
const sources = fan(hub, 200, ys, L.sources, 'end');
const outputs = fan(hub, 920, ys, L.outputs, 'start');
const hub: Pt = [560, 200];
const ys = [100, 150, 200, 250, 300];
const sources = fan(hub, 200, ys, L.sources, 'end', 'sources');
const outputs = fan(hub, 920, ys, L.outputs, 'start', 'outputs');
const sy = 410;
const wide: Layout = {
w: 1200, h: 540,
w: 1200, h: 470,
nodes: [
{ x: hub[0], y: hub[1], label: L.hub, dot: 'main', ring: 44, anchor: 'middle', dx: 0, dy: -58 },
{ x: hub[0], y: hub[1], label: L.hub, dot: 'main', ring: 44, anchor: 'middle', dx: 0, dy: -58, group: 'hub' },
...sources.nodes, ...outputs.nodes,
dot(420, 460, L.scopes[0]), dot(560, 460, L.scopes[1]), dot(700, 460, L.scopes[2]),
dot(420, sy, L.scopes[0], 'scopes'), dot(560, sy, L.scopes[1], 'scopes'), dot(700, sy, L.scopes[2], 'scopes'),
],
edges: [
...sources.edges, ...outputs.edges,
{ points: [[560, 264], [560, 460]], faint: true },
...chain([[420, 460], [560, 460], [700, 460]]),
{ points: [[560, 244], [560, sy]], faint: true, group: 'scopes' },
...chain([[420, sy], [560, sy], [700, sy]]).map((e) => ({ ...e, group: 'scopes' })),
],
texts: [{ x: 540, y: 425, text: L.scopesTitle, anchor: 'end' }],
texts: [{ x: 540, y: sy - 35, text: L.scopesTitle, anchor: 'end', group: 'scopes' }],
};
const tall = spine([
{ title: L.hub, items: L.sources },