trace-viz 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (110) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/LICENSE +21 -0
  3. package/README.md +385 -0
  4. package/dist/adapters/index.d.ts +32 -0
  5. package/dist/adapters/index.d.ts.map +1 -0
  6. package/dist/adapters/index.js +83 -0
  7. package/dist/adapters/index.js.map +1 -0
  8. package/dist/agent.d.ts +66 -0
  9. package/dist/agent.d.ts.map +1 -0
  10. package/dist/agent.js +329 -0
  11. package/dist/agent.js.map +1 -0
  12. package/dist/atlas/atlas.d.ts +203 -0
  13. package/dist/atlas/atlas.d.ts.map +1 -0
  14. package/dist/atlas/atlas.js +1466 -0
  15. package/dist/atlas/atlas.js.map +1 -0
  16. package/dist/atlas/index.d.ts +7 -0
  17. package/dist/atlas/index.d.ts.map +1 -0
  18. package/dist/atlas/index.js +4 -0
  19. package/dist/atlas/index.js.map +1 -0
  20. package/dist/atlas/layout.d.ts +44 -0
  21. package/dist/atlas/layout.d.ts.map +1 -0
  22. package/dist/atlas/layout.js +103 -0
  23. package/dist/atlas/layout.js.map +1 -0
  24. package/dist/atlas/model.d.ts +164 -0
  25. package/dist/atlas/model.d.ts.map +1 -0
  26. package/dist/atlas/model.js +489 -0
  27. package/dist/atlas/model.js.map +1 -0
  28. package/dist/channels.d.ts +105 -0
  29. package/dist/channels.d.ts.map +1 -0
  30. package/dist/channels.js +411 -0
  31. package/dist/channels.js.map +1 -0
  32. package/dist/components.d.ts +76 -0
  33. package/dist/components.d.ts.map +1 -0
  34. package/dist/components.js +432 -0
  35. package/dist/components.js.map +1 -0
  36. package/dist/estate.d.ts +25 -0
  37. package/dist/estate.d.ts.map +1 -0
  38. package/dist/estate.js +285 -0
  39. package/dist/estate.js.map +1 -0
  40. package/dist/failure.d.ts +36 -0
  41. package/dist/failure.d.ts.map +1 -0
  42. package/dist/failure.js +82 -0
  43. package/dist/failure.js.map +1 -0
  44. package/dist/geometry.d.ts +24 -0
  45. package/dist/geometry.d.ts.map +1 -0
  46. package/dist/geometry.js +92 -0
  47. package/dist/geometry.js.map +1 -0
  48. package/dist/index.d.ts +51 -0
  49. package/dist/index.d.ts.map +1 -0
  50. package/dist/index.js +34 -0
  51. package/dist/index.js.map +1 -0
  52. package/dist/layout.d.ts +66 -0
  53. package/dist/layout.d.ts.map +1 -0
  54. package/dist/layout.js +152 -0
  55. package/dist/layout.js.map +1 -0
  56. package/dist/layout3d.d.ts +98 -0
  57. package/dist/layout3d.d.ts.map +1 -0
  58. package/dist/layout3d.js +350 -0
  59. package/dist/layout3d.js.map +1 -0
  60. package/dist/mesh.d.ts +23 -0
  61. package/dist/mesh.d.ts.map +1 -0
  62. package/dist/mesh.js +263 -0
  63. package/dist/mesh.js.map +1 -0
  64. package/dist/model.d.ts +94 -0
  65. package/dist/model.d.ts.map +1 -0
  66. package/dist/model.js +84 -0
  67. package/dist/model.js.map +1 -0
  68. package/dist/particles.d.ts +80 -0
  69. package/dist/particles.d.ts.map +1 -0
  70. package/dist/particles.js +118 -0
  71. package/dist/particles.js.map +1 -0
  72. package/dist/project.d.ts +113 -0
  73. package/dist/project.d.ts.map +1 -0
  74. package/dist/project.js +288 -0
  75. package/dist/project.js.map +1 -0
  76. package/dist/react/index.d.ts +22 -0
  77. package/dist/react/index.d.ts.map +1 -0
  78. package/dist/react/index.js +69 -0
  79. package/dist/react/index.js.map +1 -0
  80. package/dist/renderer.d.ts +252 -0
  81. package/dist/renderer.d.ts.map +1 -0
  82. package/dist/renderer.js +1710 -0
  83. package/dist/renderer.js.map +1 -0
  84. package/dist/scales.d.ts +99 -0
  85. package/dist/scales.d.ts.map +1 -0
  86. package/dist/scales.js +126 -0
  87. package/dist/scales.js.map +1 -0
  88. package/dist/scene.d.ts +53 -0
  89. package/dist/scene.d.ts.map +1 -0
  90. package/dist/scene.js +44 -0
  91. package/dist/scene.js.map +1 -0
  92. package/dist/schema.d.ts +24 -0
  93. package/dist/schema.d.ts.map +1 -0
  94. package/dist/schema.js +131 -0
  95. package/dist/schema.js.map +1 -0
  96. package/dist/seed.d.ts +33 -0
  97. package/dist/seed.d.ts.map +1 -0
  98. package/dist/seed.js +173 -0
  99. package/dist/seed.js.map +1 -0
  100. package/dist/theme.d.ts +35 -0
  101. package/dist/theme.d.ts.map +1 -0
  102. package/dist/theme.js +83 -0
  103. package/dist/theme.js.map +1 -0
  104. package/dist/trace-viz.esm.js +15 -0
  105. package/dist/trace-viz.min.js +15 -0
  106. package/dist/types.d.ts +92 -0
  107. package/dist/types.d.ts.map +1 -0
  108. package/dist/types.js +9 -0
  109. package/dist/types.js.map +1 -0
  110. package/package.json +105 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js ADDED
@@ -0,0 +1,34 @@
1
+ /**
2
+ * tracelight - visualize service topologies as animated particle flows.
3
+ *
4
+ * @example
5
+ * ```ts
6
+ * import { TraceLight, authChallengeScenario } from 'tracelight';
7
+ *
8
+ * const tl = new TraceLight(document.querySelector('#map')!, { theme: 'auto' });
9
+ * tl.setGraph(authChallengeScenario(5000));
10
+ * ```
11
+ */
12
+ export { TraceLight } from './renderer.js';
13
+ // --- the dimensional framework --------------------------------------------
14
+ export { describeTable, endpointDims, fieldValue } from './model.js';
15
+ export { project, nodeIdFor, isMeasure } from './project.js';
16
+ export { sceneFromGraph } from './scene.js';
17
+ export { CHANNELS, styleScene, couplingFor, CATEGORICAL, ERROR_FLOOR, ERROR_CEIL } from './channels.js';
18
+ export { COMPONENTS, pickComponent, defaultTier, TIER_LABELS } from './components.js';
19
+ export { layoutWorld, fitCamera, project3, worldExtent, defaultWorldLayout } from './layout3d.js';
20
+ export { propagateFailure, emptyFailure } from './failure.js';
21
+ export { proposeSpec, validateSpec, heuristicSpec, buildPrompt, extractJson } from './agent.js';
22
+ export { generateMesh } from './mesh.js';
23
+ export { generateEstate } from './estate.js';
24
+ export { resolveGraph, safeResolveGraph, GraphValidationError } from './schema.js';
25
+ export { layoutGraph, defaultLayout } from './layout.js';
26
+ export { EdgeStream, mulberry32 } from './particles.js';
27
+ export { edgePath, borderPoint, distanceToPath } from './geometry.js';
28
+ export { defaultEncoding, emissionRate, particleSpeed, particleRadius, edgeWidth, logNorm, rpsDomain, clamp, formatRps, formatLatency, formatBytes, } from './scales.js';
29
+ export { themes, lightTheme, darkTheme, resolveTheme } from './theme.js';
30
+ export { generateSeedGraph, authChallengeScenario, tokenAuthScenario } from './seed.js';
31
+ export { fromObject, fromUrl, fromEventSource, pollSource } from './adapters/index.js';
32
+ // Atlas: the map-style navigator for large estates.
33
+ export * from './atlas/index.js';
34
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAG3C,6EAA6E;AAC7E,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAErE,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAE7D,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAE5C,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,WAAW,EAAE,WAAW,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAExG,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAEtF,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,QAAQ,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAElG,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAE9D,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,aAAa,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAEhG,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAI7C,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AACnF,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAEzD,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAExD,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAGtE,OAAO,EACL,eAAe,EACf,YAAY,EACZ,aAAa,EACb,cAAc,EACd,SAAS,EACT,OAAO,EACP,SAAS,EACT,KAAK,EACL,SAAS,EACT,aAAa,EACb,WAAW,GACZ,MAAM,aAAa,CAAC;AAGrB,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAGzE,OAAO,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAGxF,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAgBvF,oDAAoD;AACpD,cAAc,kBAAkB,CAAC"}
@@ -0,0 +1,66 @@
1
+ /** The minimum a graph must expose to be laid out. Both a flat graph and a projected scene satisfy it. */
2
+ export interface LayoutInput {
3
+ nodes: {
4
+ id: string;
5
+ layer?: number;
6
+ x?: number;
7
+ y?: number;
8
+ }[];
9
+ edges: {
10
+ from: string;
11
+ to: string;
12
+ kind?: string;
13
+ id?: string;
14
+ }[];
15
+ }
16
+ export interface Point {
17
+ x: number;
18
+ y: number;
19
+ }
20
+ export interface LayoutNode extends Point {
21
+ id: string;
22
+ layer: number;
23
+ width: number;
24
+ height: number;
25
+ }
26
+ export interface LayoutOptions {
27
+ width: number;
28
+ height: number;
29
+ padding: number;
30
+ nodeWidth: number;
31
+ nodeHeight: number;
32
+ /** Extra separation between layers, on top of what fitting requires. */
33
+ layerGap: number;
34
+ rowGap: number;
35
+ /**
36
+ * 0..1 pull per edge, from the distance channel. Ordering within a column
37
+ * weights neighbours by it, so a service ends up beside the thing it sends
38
+ * most of its traffic to, not merely beside something it calls.
39
+ */
40
+ weightOf?: (edgeId: string) => number;
41
+ /**
42
+ * Override the crossing-minimising row order inside each column with an
43
+ * explicit key - by name, or by traffic - when a stable, scannable order
44
+ * matters more than fewer crossings.
45
+ */
46
+ rowKey?: (nodeId: string) => string | number;
47
+ }
48
+ export declare const defaultLayout: LayoutOptions;
49
+ /**
50
+ * Layered left-to-right layout ("Sugiyama-lite").
51
+ *
52
+ * Deliberately simple and fully deterministic: same graph in, same picture
53
+ * out, every reload. A force layout would look organic and make it impossible
54
+ * to compare two screenshots, which is exactly what this tool is for.
55
+ *
56
+ * 1. Assign layers by longest path from a source, ignoring back-edges so that
57
+ * a response or retry edge does not push its target rightwards.
58
+ * 2. Order within each layer by the barycentre of already-placed neighbours,
59
+ * a couple of sweeps. Good enough to remove most crossings.
60
+ * 3. Distribute evenly in the available box.
61
+ *
62
+ * Nodes with explicit `x`/`y` are honoured verbatim; nodes with an explicit
63
+ * `layer` skip step 1.
64
+ */
65
+ export declare function layoutGraph(graph: LayoutInput, options?: Partial<LayoutOptions>): Map<string, LayoutNode>;
66
+ //# sourceMappingURL=layout.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"layout.d.ts","sourceRoot":"","sources":["../src/layout.ts"],"names":[],"mappings":"AAAA,0GAA0G;AAC1G,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,CAAC,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAChE,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,EAAE,CAAC,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CACnE;AAED,MAAM,WAAW,KAAK;IACpB,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;CACX;AAED,MAAM,WAAW,UAAW,SAAQ,KAAK;IACvC,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,wEAAwE;IACxE,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf;;;;OAIG;IACH,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,MAAM,CAAC;IACtC;;;;OAIG;IACH,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,MAAM,GAAG,MAAM,CAAC;CAC9C;AAED,eAAO,MAAM,aAAa,EAAE,aAQ3B,CAAC;AAEF;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,WAAW,CACzB,KAAK,EAAE,WAAW,EAClB,OAAO,GAAE,OAAO,CAAC,aAAa,CAAM,GACnC,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,CAiJzB"}
package/dist/layout.js ADDED
@@ -0,0 +1,152 @@
1
+ export const defaultLayout = {
2
+ width: 960,
3
+ height: 540,
4
+ padding: 48,
5
+ nodeWidth: 132,
6
+ nodeHeight: 46,
7
+ layerGap: 64,
8
+ rowGap: 26,
9
+ };
10
+ /**
11
+ * Layered left-to-right layout ("Sugiyama-lite").
12
+ *
13
+ * Deliberately simple and fully deterministic: same graph in, same picture
14
+ * out, every reload. A force layout would look organic and make it impossible
15
+ * to compare two screenshots, which is exactly what this tool is for.
16
+ *
17
+ * 1. Assign layers by longest path from a source, ignoring back-edges so that
18
+ * a response or retry edge does not push its target rightwards.
19
+ * 2. Order within each layer by the barycentre of already-placed neighbours,
20
+ * a couple of sweeps. Good enough to remove most crossings.
21
+ * 3. Distribute evenly in the available box.
22
+ *
23
+ * Nodes with explicit `x`/`y` are honoured verbatim; nodes with an explicit
24
+ * `layer` skip step 1.
25
+ */
26
+ export function layoutGraph(graph, options = {}) {
27
+ const opt = { ...defaultLayout, ...options };
28
+ const ids = graph.nodes.map((n) => n.id);
29
+ const index = new Map(ids.map((id, i) => [id, i]));
30
+ const forward = new Map(ids.map((id) => [id, []]));
31
+ const reverse = new Map(ids.map((id) => [id, []]));
32
+ const structural = graph.edges.filter((e) => e.from !== e.to && e.kind !== 'response' && e.kind !== 'challenge');
33
+ const edgeSource = structural.length ? structural : graph.edges.filter((e) => e.from !== e.to);
34
+ for (const e of edgeSource) {
35
+ const w = 0.15 + (opt.weightOf && e.id ? opt.weightOf(e.id) : 0.5);
36
+ forward.get(e.from).push({ id: e.to, w });
37
+ reverse.get(e.to).push({ id: e.from, w });
38
+ }
39
+ // --- 1. layers -----------------------------------------------------------
40
+ const layer = new Map();
41
+ for (const n of graph.nodes)
42
+ if (typeof n.layer === 'number')
43
+ layer.set(n.id, n.layer);
44
+ const state = new Map(); // 0 unvisited, 1 in-stack, 2 done
45
+ const depth = (id) => {
46
+ const pinned = layer.get(id);
47
+ if (pinned !== undefined && state.get(id) === 2)
48
+ return pinned;
49
+ if (state.get(id) === 1)
50
+ return pinned ?? 0; // cycle: stop descending
51
+ state.set(id, 1);
52
+ let d = pinned ?? 0;
53
+ if (pinned === undefined) {
54
+ for (const p of reverse.get(id) ?? [])
55
+ d = Math.max(d, depth(p.id) + 1);
56
+ }
57
+ layer.set(id, d);
58
+ state.set(id, 2);
59
+ return d;
60
+ };
61
+ for (const id of ids)
62
+ depth(id);
63
+ const maxLayer = Math.max(0, ...layer.values());
64
+ const byLayer = Array.from({ length: maxLayer + 1 }, () => []);
65
+ for (const id of ids)
66
+ byLayer[layer.get(id)].push(id);
67
+ // --- 2. ordering ---------------------------------------------------------
68
+ const order = new Map();
69
+ for (const column of byLayer)
70
+ column.forEach((id, i) => order.set(id, i));
71
+ const barycentre = (id, neighbours) => {
72
+ const ns = neighbours.get(id) ?? [];
73
+ if (!ns.length)
74
+ return order.get(id) ?? 0;
75
+ let sum = 0;
76
+ let weight = 0;
77
+ for (const n of ns) {
78
+ sum += (order.get(n.id) ?? 0) * n.w;
79
+ weight += n.w;
80
+ }
81
+ return sum / weight;
82
+ };
83
+ for (let sweep = 0; sweep < 4; sweep++) {
84
+ const neighbours = sweep % 2 === 0 ? reverse : forward;
85
+ const columns = sweep % 2 === 0 ? byLayer : [...byLayer].reverse();
86
+ for (const column of columns) {
87
+ const keyed = column.map((id) => ({
88
+ id,
89
+ key: barycentre(id, neighbours),
90
+ tie: index.get(id) ?? 0,
91
+ }));
92
+ keyed.sort((a, b) => a.key - b.key || a.tie - b.tie);
93
+ keyed.forEach((k, i) => order.set(k.id, i));
94
+ column.sort((a, b) => order.get(a) - order.get(b));
95
+ }
96
+ }
97
+ // An explicit row key replaces the crossing-minimised order outright.
98
+ if (opt.rowKey) {
99
+ for (const column of byLayer) {
100
+ column.sort((a, b) => {
101
+ const ka = opt.rowKey(a);
102
+ const kb = opt.rowKey(b);
103
+ return typeof ka === 'number' && typeof kb === 'number' ? kb - ka : String(ka).localeCompare(String(kb));
104
+ });
105
+ }
106
+ }
107
+ // --- 3. coordinates ------------------------------------------------------
108
+ const columns = byLayer.length;
109
+ const availW = Math.max(opt.width - opt.padding * 2, 120);
110
+ // Shrink the boxes when there are more layers than the canvas comfortably
111
+ // fits, rather than letting adjacent columns touch and read as one blob.
112
+ const MIN_COL_GAP = 36;
113
+ const nodeWidth = Math.max(64, Math.min(opt.nodeWidth, (availW - (columns - 1) * MIN_COL_GAP) / Math.max(columns, 1)));
114
+ // Inset by half a node so boxes at the extremes are not clipped by the edge
115
+ // of the canvas.
116
+ // The same for rows: a column of eleven services must not stack its boxes
117
+ // on top of each other. Shrink the box before overlapping it.
118
+ const MIN_ROW_GAP = 8;
119
+ const deepest = Math.max(1, ...byLayer.map((c) => c.length));
120
+ const availH = Math.max(opt.height - opt.padding * 2, 80);
121
+ const nodeHeight = Math.max(22, Math.min(opt.nodeHeight, (availH - (deepest - 1) * MIN_ROW_GAP) / deepest));
122
+ const usableW = Math.max(availW - nodeWidth, 1);
123
+ const usableH = Math.max(availH - nodeHeight, 1);
124
+ const originX = opt.padding + nodeWidth / 2;
125
+ const originY = opt.padding + nodeHeight / 2;
126
+ const colStep = columns > 1 ? usableW / (columns - 1) : 0;
127
+ const positions = new Map();
128
+ for (const [li, column] of byLayer.entries()) {
129
+ const rows = column.length;
130
+ // Fill the height when a column is deep, but do not fling two nodes to
131
+ // opposite corners just because there is room.
132
+ const step = rows > 1
133
+ ? Math.min(usableH / (rows - 1), Math.max(nodeHeight + opt.rowGap, usableH * 0.55))
134
+ : 0;
135
+ const blockH = step * (rows - 1);
136
+ const top = originY + (usableH - blockH) / 2;
137
+ for (const [ri, id] of column.entries()) {
138
+ const node = graph.nodes[index.get(id)];
139
+ positions.set(id, {
140
+ id,
141
+ layer: li,
142
+ x: node.x ?? (columns > 1 ? originX + li * colStep : opt.width / 2),
143
+ y: node.y ?? (rows > 1 ? top + ri * step : opt.height / 2),
144
+ width: nodeWidth,
145
+ height: nodeHeight,
146
+ });
147
+ }
148
+ }
149
+ void opt.layerGap;
150
+ return positions;
151
+ }
152
+ //# sourceMappingURL=layout.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"layout.js","sourceRoot":"","sources":["../src/layout.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,98 @@
1
+ import type { Scene, SceneNode } from './scene.js';
2
+ /**
3
+ * World layout: a real three-dimensional arrangement, not a stack of flat rows.
4
+ *
5
+ * Tiers are planes on the vertical axis - services on one, datastores below -
6
+ * and each plane is a two-dimensional field the nodes are free to move around
7
+ * in. That second in-plane axis is what makes clustering legible: services that
8
+ * call each other constantly end up genuinely adjacent, and you can rotate the
9
+ * camera to see round a cluster instead of squinting at crossing lines.
10
+ *
11
+ * The projection is **orthographic**, deliberately. Perspective would make far
12
+ * things smaller, and size already means something here - it is the instance
13
+ * count. Two encodings cannot share one channel.
14
+ */
15
+ export interface WorldNode {
16
+ id: string;
17
+ /** World coordinates. y is up; tier 0 sits highest. */
18
+ x: number;
19
+ y: number;
20
+ z: number;
21
+ tier: number;
22
+ w: number;
23
+ h: number;
24
+ }
25
+ export interface Camera {
26
+ yaw: number;
27
+ tilt: number;
28
+ /** Horizontal world-to-pixel scale. */
29
+ scale: number;
30
+ /**
31
+ * Vertical scale, allowed to differ from the horizontal one.
32
+ *
33
+ * Bodies are drawn at a fixed pixel size, so stretching the *arrangement* to
34
+ * fill a wide canvas distorts nothing that carries meaning - and a uniform
35
+ * scale would otherwise leave a third of the width empty whenever the tier
36
+ * stack is the binding constraint. Bounded, so clustering stays readable.
37
+ */
38
+ scaleY: number;
39
+ cx: number;
40
+ cy: number;
41
+ }
42
+ export interface Projected {
43
+ x: number;
44
+ y: number;
45
+ depth: number;
46
+ }
47
+ export declare function project3(p: {
48
+ x: number;
49
+ y: number;
50
+ z: number;
51
+ }, cam: Camera): Projected;
52
+ export interface WorldLayoutOptions {
53
+ /** Which plane each node sits on. */
54
+ tierOf: (node: SceneNode) => number;
55
+ /** 0..1 pull between two connected nodes. */
56
+ attractionOf: (edgeId: string) => number;
57
+ /** Optional categorical grouping: nodes sharing a value are pulled together. */
58
+ groupOf?: (node: SceneNode) => string | undefined;
59
+ /** Vertical separation between planes, in world units. */
60
+ tierGap: number;
61
+ nodeWidth: number;
62
+ nodeHeight: number;
63
+ iterations: number;
64
+ seed: number;
65
+ /** Must match the camera's tilt: separation is measured in projected space. */
66
+ tilt: number;
67
+ /** Minimum projected gap between two bodies on the same plane, in world units. */
68
+ gap: number;
69
+ /** Screen size of each body, so separation can be done in pixels where it matters. */
70
+ sizeOf?: (node: SceneNode) => {
71
+ w: number;
72
+ h: number;
73
+ };
74
+ /** Canvas the world has to fit. Given it, separation runs against the real camera. */
75
+ viewport?: {
76
+ width: number;
77
+ height: number;
78
+ yaw: number;
79
+ };
80
+ /** Pixels to keep clear around a body, including its label block. */
81
+ labelSpace: number;
82
+ }
83
+ export declare const defaultWorldLayout: WorldLayoutOptions;
84
+ /**
85
+ * Place nodes in the world.
86
+ *
87
+ * A short deterministic relaxation, not a live force simulation: same scene in,
88
+ * same world out, so two screenshots are comparable and a re-render does not
89
+ * shuffle everything the user had just learned to read.
90
+ */
91
+ export declare function layoutWorld(scene: Scene, options?: Partial<WorldLayoutOptions>): Map<string, WorldNode>;
92
+ /** How far the planes reach, so the ground can be drawn under everything on it. */
93
+ export declare function worldExtent(world: Map<string, WorldNode>, margin?: number): number;
94
+ /** Fit the projected world into a box, leaving room for labels under each body. */
95
+ export declare function fitCamera(world: Map<string, WorldNode>, width: number, height: number, yaw: number, tilt?: number, padding?: number,
96
+ /** Include the tier planes in the fit, so the ground is not clipped. */
97
+ extent?: number): Camera;
98
+ //# sourceMappingURL=layout3d.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"layout3d.d.ts","sourceRoot":"","sources":["../src/layout3d.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAEnD;;;;;;;;;;;;GAYG;AAEH,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,uDAAuD;IACvD,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,IAAI,EAAE,MAAM,CAAC;IACb,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;CACX;AAED,MAAM,WAAW,MAAM;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,uCAAuC;IACvC,KAAK,EAAE,MAAM,CAAC;IACd;;;;;;;OAOG;IACH,MAAM,EAAE,MAAM,CAAC;IACf,EAAE,EAAE,MAAM,CAAC;IACX,EAAE,EAAE,MAAM,CAAC;CACZ;AAED,MAAM,WAAW,SAAS;IACxB,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,KAAK,EAAE,MAAM,CAAC;CACf;AAED,wBAAgB,QAAQ,CAAC,CAAC,EAAE;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,EAAE,GAAG,EAAE,MAAM,GAAG,SAAS,CAUvF;AAED,MAAM,WAAW,kBAAkB;IACjC,qCAAqC;IACrC,MAAM,EAAE,CAAC,IAAI,EAAE,SAAS,KAAK,MAAM,CAAC;IACpC,6CAA6C;IAC7C,YAAY,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,MAAM,CAAC;IACzC,gFAAgF;IAChF,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,SAAS,KAAK,MAAM,GAAG,SAAS,CAAC;IAClD,0DAA0D;IAC1D,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,+EAA+E;IAC/E,IAAI,EAAE,MAAM,CAAC;IACb,kFAAkF;IAClF,GAAG,EAAE,MAAM,CAAC;IACZ,sFAAsF;IACtF,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,SAAS,KAAK;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACvD,sFAAsF;IACtF,QAAQ,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC;IAC1D,qEAAqE;IACrE,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,eAAO,MAAM,kBAAkB,EAAE,kBAWhC,CAAC;AAEF;;;;;;GAMG;AACH,wBAAgB,WAAW,CACzB,KAAK,EAAE,KAAK,EACZ,OAAO,GAAE,OAAO,CAAC,kBAAkB,CAAM,GACxC,GAAG,CAAC,MAAM,EAAE,SAAS,CAAC,CAkLxB;AA2ED,mFAAmF;AACnF,wBAAgB,WAAW,CAAC,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,SAAO,GAAG,MAAM,CAIhF;AAoDD,mFAAmF;AACnF,wBAAgB,SAAS,CACvB,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,SAAS,CAAC,EAC7B,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,MAAM,EACX,IAAI,SAAM,EACV,OAAO,SAAK;AACZ,wEAAwE;AACxE,MAAM,CAAC,EAAE,MAAM,GACd,MAAM,CAmCR"}