@zenfg/inspector 0.1.0-beta.1

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 (123) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +74 -0
  3. package/dist/FrameGraphInspector.d.ts +153 -0
  4. package/dist/FrameGraphInspector.d.ts.map +1 -0
  5. package/dist/FrameGraphInspector.js +580 -0
  6. package/dist/FrameGraphInspector.js.map +1 -0
  7. package/dist/debugCaptureModel.d.ts +166 -0
  8. package/dist/debugCaptureModel.d.ts.map +1 -0
  9. package/dist/debugCaptureModel.js +364 -0
  10. package/dist/debugCaptureModel.js.map +1 -0
  11. package/dist/index.d.ts +17 -0
  12. package/dist/index.d.ts.map +1 -0
  13. package/dist/index.js +15 -0
  14. package/dist/index.js.map +1 -0
  15. package/dist/panelAliasAnalysis.d.ts +20 -0
  16. package/dist/panelAliasAnalysis.d.ts.map +1 -0
  17. package/dist/panelAliasAnalysis.js +113 -0
  18. package/dist/panelAliasAnalysis.js.map +1 -0
  19. package/dist/panelCytoscapeGraphRenderer.d.ts +71 -0
  20. package/dist/panelCytoscapeGraphRenderer.d.ts.map +1 -0
  21. package/dist/panelCytoscapeGraphRenderer.js +559 -0
  22. package/dist/panelCytoscapeGraphRenderer.js.map +1 -0
  23. package/dist/panelDiagnosticsView.d.ts +22 -0
  24. package/dist/panelDiagnosticsView.d.ts.map +1 -0
  25. package/dist/panelDiagnosticsView.js +123 -0
  26. package/dist/panelDiagnosticsView.js.map +1 -0
  27. package/dist/panelDomHelpers.d.ts +26 -0
  28. package/dist/panelDomHelpers.d.ts.map +1 -0
  29. package/dist/panelDomHelpers.js +129 -0
  30. package/dist/panelDomHelpers.js.map +1 -0
  31. package/dist/panelGraphLayout.d.ts +28 -0
  32. package/dist/panelGraphLayout.d.ts.map +1 -0
  33. package/dist/panelGraphLayout.js +223 -0
  34. package/dist/panelGraphLayout.js.map +1 -0
  35. package/dist/panelGraphRenderer.d.ts +20 -0
  36. package/dist/panelGraphRenderer.d.ts.map +1 -0
  37. package/dist/panelGraphRenderer.js +2 -0
  38. package/dist/panelGraphRenderer.js.map +1 -0
  39. package/dist/panelGraphScene.d.ts +98 -0
  40. package/dist/panelGraphScene.d.ts.map +1 -0
  41. package/dist/panelGraphScene.js +559 -0
  42. package/dist/panelGraphScene.js.map +1 -0
  43. package/dist/panelGraphView.d.ts +9 -0
  44. package/dist/panelGraphView.d.ts.map +1 -0
  45. package/dist/panelGraphView.js +88 -0
  46. package/dist/panelGraphView.js.map +1 -0
  47. package/dist/panelGraphVisuals.d.ts +38 -0
  48. package/dist/panelGraphVisuals.d.ts.map +1 -0
  49. package/dist/panelGraphVisuals.js +330 -0
  50. package/dist/panelGraphVisuals.js.map +1 -0
  51. package/dist/panelIcons.d.ts +4 -0
  52. package/dist/panelIcons.d.ts.map +1 -0
  53. package/dist/panelIcons.js +43 -0
  54. package/dist/panelIcons.js.map +1 -0
  55. package/dist/panelInspectorView.d.ts +33 -0
  56. package/dist/panelInspectorView.d.ts.map +1 -0
  57. package/dist/panelInspectorView.js +357 -0
  58. package/dist/panelInspectorView.js.map +1 -0
  59. package/dist/panelMemoryView.d.ts +21 -0
  60. package/dist/panelMemoryView.d.ts.map +1 -0
  61. package/dist/panelMemoryView.js +134 -0
  62. package/dist/panelMemoryView.js.map +1 -0
  63. package/dist/panelPassesView.d.ts +31 -0
  64. package/dist/panelPassesView.d.ts.map +1 -0
  65. package/dist/panelPassesView.js +183 -0
  66. package/dist/panelPassesView.js.map +1 -0
  67. package/dist/panelResourcesView.d.ts +19 -0
  68. package/dist/panelResourcesView.d.ts.map +1 -0
  69. package/dist/panelResourcesView.js +89 -0
  70. package/dist/panelResourcesView.js.map +1 -0
  71. package/dist/panelSelection.d.ts +5 -0
  72. package/dist/panelSelection.d.ts.map +1 -0
  73. package/dist/panelSelection.js +88 -0
  74. package/dist/panelSelection.js.map +1 -0
  75. package/dist/panelTypes.d.ts +40 -0
  76. package/dist/panelTypes.d.ts.map +1 -0
  77. package/dist/panelTypes.js +2 -0
  78. package/dist/panelTypes.js.map +1 -0
  79. package/dist/panelVisualTheme.d.ts +76 -0
  80. package/dist/panelVisualTheme.d.ts.map +1 -0
  81. package/dist/panelVisualTheme.js +39 -0
  82. package/dist/panelVisualTheme.js.map +1 -0
  83. package/dist/panelWorkbenchHelpers.d.ts +34 -0
  84. package/dist/panelWorkbenchHelpers.d.ts.map +1 -0
  85. package/dist/panelWorkbenchHelpers.js +162 -0
  86. package/dist/panelWorkbenchHelpers.js.map +1 -0
  87. package/dist/panelWorkbenchView.d.ts +77 -0
  88. package/dist/panelWorkbenchView.d.ts.map +1 -0
  89. package/dist/panelWorkbenchView.js +409 -0
  90. package/dist/panelWorkbenchView.js.map +1 -0
  91. package/dist/query.d.ts +11 -0
  92. package/dist/query.d.ts.map +1 -0
  93. package/dist/query.js +21 -0
  94. package/dist/query.js.map +1 -0
  95. package/dist/styles.d.ts +8 -0
  96. package/dist/styles.d.ts.map +1 -0
  97. package/dist/styles.js +1073 -0
  98. package/dist/styles.js.map +1 -0
  99. package/package.json +29 -0
  100. package/src/FrameGraphInspector.ts +632 -0
  101. package/src/debugCaptureModel.ts +583 -0
  102. package/src/index.ts +21 -0
  103. package/src/panelAliasAnalysis.ts +164 -0
  104. package/src/panelCytoscapeGraphRenderer.ts +617 -0
  105. package/src/panelDiagnosticsView.ts +160 -0
  106. package/src/panelDomHelpers.ts +151 -0
  107. package/src/panelGraphLayout.ts +281 -0
  108. package/src/panelGraphRenderer.ts +21 -0
  109. package/src/panelGraphScene.ts +787 -0
  110. package/src/panelGraphView.ts +108 -0
  111. package/src/panelGraphVisuals.ts +364 -0
  112. package/src/panelIcons.ts +56 -0
  113. package/src/panelInspectorView.ts +377 -0
  114. package/src/panelMemoryView.ts +154 -0
  115. package/src/panelPassesView.ts +222 -0
  116. package/src/panelResourcesView.ts +115 -0
  117. package/src/panelSelection.ts +99 -0
  118. package/src/panelTypes.ts +28 -0
  119. package/src/panelVisualTheme.ts +39 -0
  120. package/src/panelWorkbenchHelpers.ts +216 -0
  121. package/src/panelWorkbenchView.ts +465 -0
  122. package/src/query.ts +22 -0
  123. package/src/styles.ts +1076 -0
@@ -0,0 +1,22 @@
1
+ import type { FrameGraphDebugViewModel } from './debugCaptureModel.ts';
2
+ import type { Selection } from './panelTypes.ts';
3
+ import { type WorkbenchCallbacks } from './panelWorkbenchHelpers.ts';
4
+ export declare class DiagnosticsView {
5
+ private readonly callbacks;
6
+ readonly root: HTMLElement;
7
+ private readonly scroller;
8
+ private readonly content;
9
+ private readonly rows;
10
+ private snapshot;
11
+ private selected;
12
+ constructor(callbacks: WorkbenchCallbacks, idPrefix: string);
13
+ setSnapshot(snapshot: FrameGraphDebugViewModel): void;
14
+ setSelection(selected: Selection | undefined): void;
15
+ private render;
16
+ private createSection;
17
+ private createRoots;
18
+ private createCulled;
19
+ private createSegments;
20
+ private createTiming;
21
+ }
22
+ //# sourceMappingURL=panelDiagnosticsView.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"panelDiagnosticsView.d.ts","sourceRoot":"","sources":["../src/panelDiagnosticsView.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,wBAAwB,CAAC;AAEvE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,EAON,KAAK,kBAAkB,EAEvB,MAAM,4BAA4B,CAAC;AAEpC,qBAAa,eAAe;IAQf,OAAO,CAAC,QAAQ,CAAC,SAAS;IAPtC,QAAQ,CAAC,IAAI,cAAqC;IAClD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAiC;IAC1D,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAiC;IACzD,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAoC;IACzD,OAAO,CAAC,QAAQ,CAAuC;IACvD,OAAO,CAAC,QAAQ,CAAwB;gBAEX,SAAS,EAAE,kBAAkB,EAAE,QAAQ,EAAE,MAAM;IAU5E,WAAW,CAAC,QAAQ,EAAE,wBAAwB,GAAG,IAAI;IAKrD,YAAY,CAAC,QAAQ,EAAE,SAAS,GAAG,SAAS,GAAG,IAAI;IAKnD,OAAO,CAAC,MAAM;IAad,OAAO,CAAC,aAAa;IAcrB,OAAO,CAAC,WAAW;IAmBnB,OAAO,CAAC,YAAY;IAmBpB,OAAO,CAAC,cAAc;IAyBtB,OAAO,CAAC,YAAY;CA2BpB"}
@@ -0,0 +1,123 @@
1
+ import { createCell, labelNode, labelResource } from "./panelDomHelpers.js";
2
+ import { createEmptyTableRow, createKindCell, createRelationButton, createSelectionCell, createTableScroller, registerSelectable, updateSelectedRows, } from "./panelWorkbenchHelpers.js";
3
+ export class DiagnosticsView {
4
+ callbacks;
5
+ root = document.createElement('section');
6
+ scroller = document.createElement('div');
7
+ content = document.createElement('div');
8
+ rows = new Map();
9
+ snapshot;
10
+ selected;
11
+ constructor(callbacks, idPrefix) {
12
+ this.callbacks = callbacks;
13
+ this.root.className = 'zenfg-inspector-view zenfg-inspector-diagnostics-view';
14
+ this.root.id = `${idPrefix}-view-diagnostics`;
15
+ this.root.setAttribute('role', 'tabpanel');
16
+ this.scroller.className = 'zenfg-inspector-diagnostics-scroller';
17
+ this.content.className = 'zenfg-inspector-diagnostics-grid';
18
+ this.scroller.appendChild(this.content);
19
+ this.root.appendChild(this.scroller);
20
+ }
21
+ setSnapshot(snapshot) {
22
+ this.snapshot = snapshot;
23
+ this.render();
24
+ }
25
+ setSelection(selected) {
26
+ this.selected = selected;
27
+ updateSelectedRows(this.rows, selected);
28
+ }
29
+ render() {
30
+ const snapshot = this.snapshot;
31
+ if (!snapshot)
32
+ return;
33
+ this.rows.clear();
34
+ this.content.replaceChildren(this.createRoots(snapshot), this.createCulled(snapshot), this.createSegments(snapshot), this.createTiming(snapshot));
35
+ updateSelectedRows(this.rows, this.selected);
36
+ }
37
+ createSection(title, description) {
38
+ const section = document.createElement('section');
39
+ section.className = 'zenfg-inspector-diagnostic-card';
40
+ const heading = document.createElement('h2');
41
+ heading.textContent = title;
42
+ section.appendChild(heading);
43
+ if (description) {
44
+ const text = document.createElement('p');
45
+ text.textContent = description;
46
+ section.appendChild(text);
47
+ }
48
+ return section;
49
+ }
50
+ createRoots(snapshot) {
51
+ const section = this.createSection('Retention roots', 'Roots explain why graph work survived dead-node elimination.');
52
+ const list = document.createElement('div');
53
+ list.className = 'zenfg-inspector-diagnostic-list';
54
+ snapshot.roots.forEach((root, index) => {
55
+ const selection = { kind: 'root', index };
56
+ const button = createRelationButton(`${root.reason} · ${root.resource ? labelResource(root.resource) : root.nodeId === undefined ? '-' : `node-${root.nodeId}`}`, selection, this.callbacks.onSelect);
57
+ registerSelectable(this.rows, button, selection, this.callbacks);
58
+ list.appendChild(button);
59
+ });
60
+ if (snapshot.roots.length === 0)
61
+ list.textContent = 'No retention roots.';
62
+ section.appendChild(list);
63
+ return section;
64
+ }
65
+ createCulled(snapshot) {
66
+ const section = this.createSection('Culled nodes', 'Recorded nodes that were not reachable from a retention root.');
67
+ const table = createTableScroller([{ label: 'Node' }, { label: 'Kind', column: 'kind' }, { label: 'Reason' }]);
68
+ for (const [index, entry] of snapshot.culledNodes.entries()) {
69
+ const selection = { kind: 'culled', index };
70
+ const row = document.createElement('tr');
71
+ registerSelectable(this.rows, row, selection, this.callbacks);
72
+ row.append(createSelectionCell(labelNode(entry.node), selection, this.callbacks), createKindCell(entry.node.kind), createCell(entry.reason));
73
+ table.body.appendChild(row);
74
+ }
75
+ if (snapshot.culledNodes.length === 0)
76
+ table.body.appendChild(createEmptyTableRow(3, 'No culled nodes.'));
77
+ section.appendChild(table.scroller);
78
+ return section;
79
+ }
80
+ createSegments(snapshot) {
81
+ const section = this.createSection('Execution segments', 'An opaque interval is a graph boundary around external work; it is not a count or timing of third-party GPU submissions.');
82
+ const table = createTableScroller([{ label: '#', column: 'numeric' }, { label: 'Kind', column: 'kind' }, { label: 'Nodes' }]);
83
+ for (const segment of snapshot.executionSegments) {
84
+ const selection = { kind: 'segment', index: segment.index };
85
+ const row = document.createElement('tr');
86
+ registerSelectable(this.rows, row, selection, this.callbacks);
87
+ const labels = segment.nodeIds.map((id) => snapshot.nodeById.get(id))
88
+ .filter((node) => node !== undefined)
89
+ .map((node) => labelNode(node));
90
+ row.append(createSelectionCell(String(segment.index), selection, this.callbacks, 'numeric'), createKindCell(segment.kind, segment.kind === 'frame-graph' ? 'FrameGraph command segment' : 'Opaque interval'), createCell(labels.join(', ') || '-'));
91
+ table.body.appendChild(row);
92
+ }
93
+ if (snapshot.executionSegments.length === 0)
94
+ table.body.appendChild(createEmptyTableRow(3, 'No execution segments.'));
95
+ section.appendChild(table.scroller);
96
+ return section;
97
+ }
98
+ createTiming(snapshot) {
99
+ const metrics = snapshot.metrics;
100
+ const coverage = metrics.timingEligibleNodeCount === 0
101
+ ? 'No eligible render or compute passes'
102
+ : `${metrics.timedNodeCount}/${metrics.timingEligibleNodeCount} eligible passes timed (${((metrics.timedNodeCount / metrics.timingEligibleNodeCount) * 100).toFixed(0)}%)`;
103
+ const section = this.createSection('GPU timing coverage', coverage);
104
+ if (snapshot.profiling.status === 'unavailable') {
105
+ const status = document.createElement('p');
106
+ status.textContent = `Timing unavailable: ${snapshot.profiling.reason}.`;
107
+ section.appendChild(status);
108
+ }
109
+ const table = createTableScroller([{ label: 'Pass' }, { label: 'Status', column: 'kind' }, { label: 'GPU (ms)', column: 'numeric' }]);
110
+ for (const node of snapshot.nodes.filter((candidate) => candidate.kind === 'render' || candidate.kind === 'compute')) {
111
+ const selection = { kind: 'node', id: node.id };
112
+ const row = document.createElement('tr');
113
+ registerSelectable(this.rows, row, selection, this.callbacks);
114
+ row.append(createSelectionCell(labelNode(node), selection, this.callbacks), createKindCell(node.gpuDurationMicros === undefined ? 'not-timed' : 'timed', node.gpuDurationMicros === undefined ? 'Not timed' : 'Timed'), createCell(node.gpuDurationMicros === undefined ? '-' : (node.gpuDurationMicros / 1000).toFixed(3), { column: 'numeric' }));
115
+ table.body.appendChild(row);
116
+ }
117
+ if (table.body.childElementCount === 0)
118
+ table.body.appendChild(createEmptyTableRow(3, 'No timing-eligible passes.'));
119
+ section.appendChild(table.scroller);
120
+ return section;
121
+ }
122
+ }
123
+ //# sourceMappingURL=panelDiagnosticsView.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"panelDiagnosticsView.js","sourceRoot":"","sources":["../src/panelDiagnosticsView.ts"],"names":[],"mappings":"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","sourcesContent":["import type { FrameGraphDebugViewModel } from './debugCaptureModel.ts';\nimport { createCell, labelNode, labelResource } from './panelDomHelpers.ts';\nimport type { Selection } from './panelTypes.ts';\nimport {\n\tcreateEmptyTableRow,\n\tcreateKindCell,\n\tcreateRelationButton,\n\tcreateSelectionCell,\n\tcreateTableScroller,\n\tregisterSelectable,\n\ttype WorkbenchCallbacks,\n\tupdateSelectedRows,\n} from './panelWorkbenchHelpers.ts';\n\nexport class DiagnosticsView {\n\treadonly root = document.createElement('section');\n\tprivate readonly scroller = document.createElement('div');\n\tprivate readonly content = document.createElement('div');\n\tprivate readonly rows = new Map<string, HTMLElement[]>();\n\tprivate snapshot: FrameGraphDebugViewModel | undefined;\n\tprivate selected: Selection | undefined;\n\n\tconstructor(private readonly callbacks: WorkbenchCallbacks, idPrefix: string) {\n\t\tthis.root.className = 'zenfg-inspector-view zenfg-inspector-diagnostics-view';\n\t\tthis.root.id = `${idPrefix}-view-diagnostics`;\n\t\tthis.root.setAttribute('role', 'tabpanel');\n\t\tthis.scroller.className = 'zenfg-inspector-diagnostics-scroller';\n\t\tthis.content.className = 'zenfg-inspector-diagnostics-grid';\n\t\tthis.scroller.appendChild(this.content);\n\t\tthis.root.appendChild(this.scroller);\n\t}\n\n\tsetSnapshot(snapshot: FrameGraphDebugViewModel): void {\n\t\tthis.snapshot = snapshot;\n\t\tthis.render();\n\t}\n\n\tsetSelection(selected: Selection | undefined): void {\n\t\tthis.selected = selected;\n\t\tupdateSelectedRows(this.rows, selected);\n\t}\n\n\tprivate render(): void {\n\t\tconst snapshot = this.snapshot;\n\t\tif (!snapshot) return;\n\t\tthis.rows.clear();\n\t\tthis.content.replaceChildren(\n\t\t\tthis.createRoots(snapshot),\n\t\t\tthis.createCulled(snapshot),\n\t\t\tthis.createSegments(snapshot),\n\t\t\tthis.createTiming(snapshot),\n\t\t);\n\t\tupdateSelectedRows(this.rows, this.selected);\n\t}\n\n\tprivate createSection(title: string, description?: string): HTMLElement {\n\t\tconst section = document.createElement('section');\n\t\tsection.className = 'zenfg-inspector-diagnostic-card';\n\t\tconst heading = document.createElement('h2');\n\t\theading.textContent = title;\n\t\tsection.appendChild(heading);\n\t\tif (description) {\n\t\t\tconst text = document.createElement('p');\n\t\t\ttext.textContent = description;\n\t\t\tsection.appendChild(text);\n\t\t}\n\t\treturn section;\n\t}\n\n\tprivate createRoots(snapshot: FrameGraphDebugViewModel): HTMLElement {\n\t\tconst section = this.createSection('Retention roots', 'Roots explain why graph work survived dead-node elimination.');\n\t\tconst list = document.createElement('div');\n\t\tlist.className = 'zenfg-inspector-diagnostic-list';\n\t\tsnapshot.roots.forEach((root, index) => {\n\t\t\tconst selection: Selection = { kind: 'root', index };\n\t\t\tconst button = createRelationButton(\n\t\t\t\t`${root.reason} · ${root.resource ? labelResource(root.resource) : root.nodeId === undefined ? '-' : `node-${root.nodeId}`}`,\n\t\t\t\tselection,\n\t\t\t\tthis.callbacks.onSelect,\n\t\t\t);\n\t\t\tregisterSelectable(this.rows, button, selection, this.callbacks);\n\t\t\tlist.appendChild(button);\n\t\t});\n\t\tif (snapshot.roots.length === 0) list.textContent = 'No retention roots.';\n\t\tsection.appendChild(list);\n\t\treturn section;\n\t}\n\n\tprivate createCulled(snapshot: FrameGraphDebugViewModel): HTMLElement {\n\t\tconst section = this.createSection('Culled nodes', 'Recorded nodes that were not reachable from a retention root.');\n\t\tconst table = createTableScroller([{ label: 'Node' }, { label: 'Kind', column: 'kind' }, { label: 'Reason' }]);\n\t\tfor (const [index, entry] of snapshot.culledNodes.entries()) {\n\t\t\tconst selection: Selection = { kind: 'culled', index };\n\t\t\tconst row = document.createElement('tr');\n\t\t\tregisterSelectable(this.rows, row, selection, this.callbacks);\n\t\t\trow.append(\n\t\t\t\tcreateSelectionCell(labelNode(entry.node), selection, this.callbacks),\n\t\t\t\tcreateKindCell(entry.node.kind),\n\t\t\t\tcreateCell(entry.reason),\n\t\t\t);\n\t\t\ttable.body.appendChild(row);\n\t\t}\n\t\tif (snapshot.culledNodes.length === 0) table.body.appendChild(createEmptyTableRow(3, 'No culled nodes.'));\n\t\tsection.appendChild(table.scroller);\n\t\treturn section;\n\t}\n\n\tprivate createSegments(snapshot: FrameGraphDebugViewModel): HTMLElement {\n\t\tconst section = this.createSection(\n\t\t\t'Execution segments',\n\t\t\t'An opaque interval is a graph boundary around external work; it is not a count or timing of third-party GPU submissions.',\n\t\t);\n\t\tconst table = createTableScroller([{ label: '#', column: 'numeric' }, { label: 'Kind', column: 'kind' }, { label: 'Nodes' }]);\n\t\tfor (const segment of snapshot.executionSegments) {\n\t\t\tconst selection: Selection = { kind: 'segment', index: segment.index };\n\t\t\tconst row = document.createElement('tr');\n\t\t\tregisterSelectable(this.rows, row, selection, this.callbacks);\n\t\t\tconst labels = segment.nodeIds.map((id) => snapshot.nodeById.get(id))\n\t\t\t\t.filter((node) => node !== undefined)\n\t\t\t\t.map((node) => labelNode(node));\n\t\t\trow.append(\n\t\t\t\tcreateSelectionCell(String(segment.index), selection, this.callbacks, 'numeric'),\n\t\t\t\tcreateKindCell(segment.kind, segment.kind === 'frame-graph' ? 'FrameGraph command segment' : 'Opaque interval'),\n\t\t\t\tcreateCell(labels.join(', ') || '-'),\n\t\t\t);\n\t\t\ttable.body.appendChild(row);\n\t\t}\n\t\tif (snapshot.executionSegments.length === 0) table.body.appendChild(createEmptyTableRow(3, 'No execution segments.'));\n\t\tsection.appendChild(table.scroller);\n\t\treturn section;\n\t}\n\n\tprivate createTiming(snapshot: FrameGraphDebugViewModel): HTMLElement {\n\t\tconst metrics = snapshot.metrics;\n\t\tconst coverage = metrics.timingEligibleNodeCount === 0\n\t\t\t? 'No eligible render or compute passes'\n\t\t\t: `${metrics.timedNodeCount}/${metrics.timingEligibleNodeCount} eligible passes timed (${((metrics.timedNodeCount / metrics.timingEligibleNodeCount) * 100).toFixed(0)}%)`;\n\t\tconst section = this.createSection('GPU timing coverage', coverage);\n\t\tif (snapshot.profiling.status === 'unavailable') {\n\t\t\tconst status = document.createElement('p');\n\t\t\tstatus.textContent = `Timing unavailable: ${snapshot.profiling.reason}.`;\n\t\t\tsection.appendChild(status);\n\t\t}\n\t\tconst table = createTableScroller([{ label: 'Pass' }, { label: 'Status', column: 'kind' }, { label: 'GPU (ms)', column: 'numeric' }]);\n\t\tfor (const node of snapshot.nodes.filter((candidate) => candidate.kind === 'render' || candidate.kind === 'compute')) {\n\t\t\tconst selection: Selection = { kind: 'node', id: node.id };\n\t\t\tconst row = document.createElement('tr');\n\t\t\tregisterSelectable(this.rows, row, selection, this.callbacks);\n\t\t\trow.append(\n\t\t\t\tcreateSelectionCell(labelNode(node), selection, this.callbacks),\n\t\t\t\tcreateKindCell(node.gpuDurationMicros === undefined ? 'not-timed' : 'timed', node.gpuDurationMicros === undefined ? 'Not timed' : 'Timed'),\n\t\t\t\tcreateCell(node.gpuDurationMicros === undefined ? '-' : (node.gpuDurationMicros / 1000).toFixed(3), { column: 'numeric' }),\r\n\t\t\t);\n\t\t\ttable.body.appendChild(row);\n\t\t}\n\t\tif (table.body.childElementCount === 0) table.body.appendChild(createEmptyTableRow(3, 'No timing-eligible passes.'));\n\t\tsection.appendChild(table.scroller);\n\t\treturn section;\n\t}\n}\n"]}
@@ -0,0 +1,26 @@
1
+ import type { FrameGraphDebugNode, FrameGraphDebugViewModel } from './debugCaptureModel.ts';
2
+ export declare function createSection(title: string): HTMLElement;
3
+ export declare function createTable(headers: readonly string[]): HTMLTableElement;
4
+ export declare function createStat(label: string, value: string): HTMLElement;
5
+ export type FrameGraphDebugCellOptions = {
6
+ readonly column?: 'code' | 'kind' | 'numeric';
7
+ readonly kind?: string;
8
+ };
9
+ export declare function createCell(text: string, options?: FrameGraphDebugCellOptions): HTMLTableCellElement;
10
+ export declare function createButtonCell(text: string, onClick: () => void): HTMLTableCellElement;
11
+ export declare function createToolbarButton(text: string, title: string, onClick: () => void): HTMLButtonElement;
12
+ export declare function createListButton(text: string, selected: boolean, onClick: () => void): HTMLButtonElement;
13
+ export declare function createMutedText(text: string): HTMLElement;
14
+ export declare function createMutedActionText(prefix: string, actionText: string, suffix: string, onAction: () => void): HTMLElement;
15
+ export declare function formatProfilingStatus(snapshot: FrameGraphDebugViewModel): string;
16
+ export declare function formatGpuDuration(node: FrameGraphDebugNode): string;
17
+ export declare function formatGpuFrameDuration(snapshot: FrameGraphDebugViewModel): string;
18
+ export declare function formatPoolHitRate(snapshot: FrameGraphDebugViewModel): string;
19
+ export declare function formatBytes(bytes: number): string;
20
+ export declare function labelNode(node: Pick<FrameGraphDebugNode, 'id' | 'label'>): string;
21
+ export declare function labelResource(resource: {
22
+ readonly id: string;
23
+ readonly kind: string;
24
+ readonly label?: string;
25
+ }): string;
26
+ //# sourceMappingURL=panelDomHelpers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"panelDomHelpers.d.ts","sourceRoot":"","sources":["../src/panelDomHelpers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACR,mBAAmB,EACnB,wBAAwB,EAC3B,MAAM,wBAAwB,CAAC;AAEhC,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,WAAW,CAOxD;AAED,wBAAgB,WAAW,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,GAAG,gBAAgB,CAYxE;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,WAAW,CAQpE;AAED,MAAM,MAAM,0BAA0B,GAAG;IACxC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;IAC9C,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,0BAA0B,GAAG,oBAAoB,CAMnG;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,IAAI,GAAG,oBAAoB,CAQxF;AAED,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,IAAI,GAAG,iBAAiB,CAQvG;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,IAAI,GAAG,iBAAiB,CASxG;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,CAKzD;AAED,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,IAAI,GAAG,WAAW,CAU3H;AAED,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,wBAAwB,GAAG,MAAM,CAShF;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,mBAAmB,GAAG,MAAM,CAKnE;AAED,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,wBAAwB,GAAG,MAAM,CAKjF;AAED,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,wBAAwB,GAAG,MAAM,CAM5E;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAQjD;AAED,wBAAgB,SAAS,CAAC,IAAI,EAAE,IAAI,CAAC,mBAAmB,EAAE,IAAI,GAAG,OAAO,CAAC,GAAG,MAAM,CAEjF;AAED,wBAAgB,aAAa,CAAC,QAAQ,EAAE;IAAE,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAEvH"}
@@ -0,0 +1,129 @@
1
+ export function createSection(title) {
2
+ const section = document.createElement('section');
3
+ section.className = 'zenfg-inspector-section';
4
+ const heading = document.createElement('h2');
5
+ heading.textContent = title;
6
+ section.appendChild(heading);
7
+ return section;
8
+ }
9
+ export function createTable(headers) {
10
+ const table = document.createElement('table');
11
+ const head = document.createElement('thead');
12
+ const headRow = document.createElement('tr');
13
+ for (const header of headers) {
14
+ const cell = document.createElement('th');
15
+ cell.textContent = header;
16
+ headRow.appendChild(cell);
17
+ }
18
+ head.appendChild(headRow);
19
+ table.append(head, document.createElement('tbody'));
20
+ return table;
21
+ }
22
+ export function createStat(label, value) {
23
+ const item = document.createElement('div');
24
+ const name = document.createElement('span');
25
+ name.textContent = label;
26
+ const count = document.createElement('strong');
27
+ count.textContent = value;
28
+ item.append(name, count);
29
+ return item;
30
+ }
31
+ export function createCell(text, options) {
32
+ const cell = document.createElement('td');
33
+ cell.textContent = text;
34
+ if (options?.column)
35
+ cell.dataset.column = options.column;
36
+ if (options?.kind)
37
+ cell.dataset.kind = options.kind;
38
+ return cell;
39
+ }
40
+ export function createButtonCell(text, onClick) {
41
+ const cell = document.createElement('td');
42
+ const button = document.createElement('button');
43
+ button.type = 'button';
44
+ button.textContent = text;
45
+ button.addEventListener('click', onClick);
46
+ cell.appendChild(button);
47
+ return cell;
48
+ }
49
+ export function createToolbarButton(text, title, onClick) {
50
+ const button = document.createElement('button');
51
+ button.type = 'button';
52
+ button.textContent = text;
53
+ button.title = title;
54
+ button.setAttribute('aria-label', title);
55
+ button.addEventListener('click', onClick);
56
+ return button;
57
+ }
58
+ export function createListButton(text, selected, onClick) {
59
+ const button = document.createElement('button');
60
+ button.type = 'button';
61
+ button.textContent = text;
62
+ if (selected) {
63
+ button.className = 'selected';
64
+ }
65
+ button.addEventListener('click', onClick);
66
+ return button;
67
+ }
68
+ export function createMutedText(text) {
69
+ const element = document.createElement('p');
70
+ element.className = 'zenfg-inspector-muted';
71
+ element.textContent = text;
72
+ return element;
73
+ }
74
+ export function createMutedActionText(prefix, actionText, suffix, onAction) {
75
+ const element = document.createElement('p');
76
+ element.className = 'zenfg-inspector-muted';
77
+ const button = document.createElement('button');
78
+ button.type = 'button';
79
+ button.className = 'zenfg-inspector-inline-action';
80
+ button.textContent = actionText;
81
+ button.addEventListener('click', onAction);
82
+ element.append(document.createTextNode(prefix), button, document.createTextNode(suffix));
83
+ return element;
84
+ }
85
+ export function formatProfilingStatus(snapshot) {
86
+ const profiling = snapshot.profiling;
87
+ if (!profiling) {
88
+ return 'off';
89
+ }
90
+ if (profiling.status === 'available') {
91
+ return `frame ${profiling.frameIndex}`;
92
+ }
93
+ return profiling.reason;
94
+ }
95
+ export function formatGpuDuration(node) {
96
+ if (node.gpuDurationMicros === undefined) {
97
+ return '-';
98
+ }
99
+ return (node.gpuDurationMicros / 1000).toFixed(3);
100
+ }
101
+ export function formatGpuFrameDuration(snapshot) {
102
+ if (snapshot.profiling.status !== 'available') {
103
+ return '-';
104
+ }
105
+ return (snapshot.profiling.gpuFrameDurationMicros / 1000).toFixed(3);
106
+ }
107
+ export function formatPoolHitRate(snapshot) {
108
+ const pool = snapshot.resourcePool;
109
+ if (!pool || pool.status !== 'available' || pool.acquireCount === 0) {
110
+ return '-';
111
+ }
112
+ return `${((pool.reuseCount / pool.acquireCount) * 100).toFixed(0)}%`;
113
+ }
114
+ export function formatBytes(bytes) {
115
+ if (bytes < 1024) {
116
+ return `${bytes} B`;
117
+ }
118
+ if (bytes < 1024 * 1024) {
119
+ return `${(bytes / 1024).toFixed(1)} KiB`;
120
+ }
121
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MiB`;
122
+ }
123
+ export function labelNode(node) {
124
+ return node.label ?? `node-${node.id}`;
125
+ }
126
+ export function labelResource(resource) {
127
+ return resource.label ?? `${resource.kind}-${resource.id}`;
128
+ }
129
+ //# sourceMappingURL=panelDomHelpers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"panelDomHelpers.js","sourceRoot":"","sources":["../src/panelDomHelpers.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n FrameGraphDebugNode,\n FrameGraphDebugViewModel,\n} from './debugCaptureModel.ts';\n\nexport function createSection(title: string): HTMLElement {\n const section = document.createElement('section');\n section.className = 'zenfg-inspector-section';\n const heading = document.createElement('h2');\n heading.textContent = title;\n section.appendChild(heading);\n return section;\n}\n\nexport function createTable(headers: readonly string[]): HTMLTableElement {\n const table = document.createElement('table');\n const head = document.createElement('thead');\n const headRow = document.createElement('tr');\n for (const header of headers) {\n const cell = document.createElement('th');\n cell.textContent = header;\n headRow.appendChild(cell);\n }\n head.appendChild(headRow);\n table.append(head, document.createElement('tbody'));\n return table;\n}\n\nexport function createStat(label: string, value: string): HTMLElement {\n const item = document.createElement('div');\n const name = document.createElement('span');\n name.textContent = label;\n const count = document.createElement('strong');\n count.textContent = value;\n item.append(name, count);\n return item;\n}\n\nexport type FrameGraphDebugCellOptions = {\n\treadonly column?: 'code' | 'kind' | 'numeric';\n\treadonly kind?: string;\n};\n\nexport function createCell(text: string, options?: FrameGraphDebugCellOptions): HTMLTableCellElement {\n const cell = document.createElement('td');\n cell.textContent = text;\n if (options?.column) cell.dataset.column = options.column;\n if (options?.kind) cell.dataset.kind = options.kind;\n return cell;\n}\n\nexport function createButtonCell(text: string, onClick: () => void): HTMLTableCellElement {\n const cell = document.createElement('td');\n const button = document.createElement('button');\n button.type = 'button';\n button.textContent = text;\n button.addEventListener('click', onClick);\n cell.appendChild(button);\n return cell;\n}\n\nexport function createToolbarButton(text: string, title: string, onClick: () => void): HTMLButtonElement {\n const button = document.createElement('button');\n button.type = 'button';\n button.textContent = text;\n button.title = title;\n button.setAttribute('aria-label', title);\n button.addEventListener('click', onClick);\n return button;\n}\n\nexport function createListButton(text: string, selected: boolean, onClick: () => void): HTMLButtonElement {\n const button = document.createElement('button');\n button.type = 'button';\n button.textContent = text;\n if (selected) {\n button.className = 'selected';\n }\n button.addEventListener('click', onClick);\n return button;\n}\n\nexport function createMutedText(text: string): HTMLElement {\n const element = document.createElement('p');\n element.className = 'zenfg-inspector-muted';\n element.textContent = text;\n return element;\n}\n\nexport function createMutedActionText(prefix: string, actionText: string, suffix: string, onAction: () => void): HTMLElement {\n const element = document.createElement('p');\n element.className = 'zenfg-inspector-muted';\n const button = document.createElement('button');\n button.type = 'button';\n button.className = 'zenfg-inspector-inline-action';\n button.textContent = actionText;\n button.addEventListener('click', onAction);\n element.append(document.createTextNode(prefix), button, document.createTextNode(suffix));\n return element;\n}\n\nexport function formatProfilingStatus(snapshot: FrameGraphDebugViewModel): string {\n const profiling = snapshot.profiling;\n if (!profiling) {\n return 'off';\n }\n if (profiling.status === 'available') {\n return `frame ${profiling.frameIndex}`;\n }\n return profiling.reason;\n}\n\nexport function formatGpuDuration(node: FrameGraphDebugNode): string {\n if (node.gpuDurationMicros === undefined) {\n return '-';\n }\n return (node.gpuDurationMicros / 1000).toFixed(3);\n}\n\nexport function formatGpuFrameDuration(snapshot: FrameGraphDebugViewModel): string {\n if (snapshot.profiling.status !== 'available') {\n return '-';\n }\n return (snapshot.profiling.gpuFrameDurationMicros / 1000).toFixed(3);\n}\n\nexport function formatPoolHitRate(snapshot: FrameGraphDebugViewModel): string {\n const pool = snapshot.resourcePool;\n if (!pool || pool.status !== 'available' || pool.acquireCount === 0) {\n return '-';\n }\n return `${((pool.reuseCount / pool.acquireCount) * 100).toFixed(0)}%`;\n}\n\nexport function formatBytes(bytes: number): string {\n if (bytes < 1024) {\n return `${bytes} B`;\n }\n if (bytes < 1024 * 1024) {\n return `${(bytes / 1024).toFixed(1)} KiB`;\n }\n return `${(bytes / (1024 * 1024)).toFixed(1)} MiB`;\n}\n\nexport function labelNode(node: Pick<FrameGraphDebugNode, 'id' | 'label'>): string {\n return node.label ?? `node-${node.id}`;\n}\n\nexport function labelResource(resource: { readonly id: string; readonly kind: string; readonly label?: string }): string {\n return resource.label ?? `${resource.kind}-${resource.id}`;\n}\n"]}
@@ -0,0 +1,28 @@
1
+ import type cytoscape from 'cytoscape';
2
+ import type { ELK, ElkNode } from 'elkjs/lib/elk-api';
3
+ import type { GraphScene, GraphSceneElementId } from './panelGraphScene.ts';
4
+ export type GraphPosition = {
5
+ readonly x: number;
6
+ readonly y: number;
7
+ };
8
+ export type GraphEdgeRoute = {
9
+ readonly startPoint: GraphPosition;
10
+ readonly bendPoints: readonly GraphPosition[];
11
+ readonly endPoint: GraphPosition;
12
+ };
13
+ export type GraphLayoutResult = {
14
+ readonly positions: ReadonlyMap<GraphSceneElementId, GraphPosition>;
15
+ readonly routes: ReadonlyMap<GraphSceneElementId, GraphEdgeRoute>;
16
+ };
17
+ export type CytoscapeEdgeRouteData = {
18
+ readonly sourceEndpoint: string;
19
+ readonly targetEndpoint: string;
20
+ readonly segmentWeights: string;
21
+ readonly segmentDistances: string;
22
+ };
23
+ export declare function layoutGraphScene(elk: ELK, scene: GraphScene): Promise<GraphLayoutResult>;
24
+ export declare function createElkLayoutGraph(scene: GraphScene): ElkNode;
25
+ export declare function applyGraphLayout(core: cytoscape.Core, scene: GraphScene, layout: GraphLayoutResult): void;
26
+ export declare function syncExpandedGroupLabelGeometry(core: cytoscape.Core): void;
27
+ export declare function createCytoscapeEdgeRouteData(route: GraphEdgeRoute | undefined, sourceCenter: GraphPosition, targetCenter: GraphPosition): CytoscapeEdgeRouteData | undefined;
28
+ //# sourceMappingURL=panelGraphLayout.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"panelGraphLayout.d.ts","sourceRoot":"","sources":["../src/panelGraphLayout.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,SAAS,MAAM,WAAW,CAAC;AACvC,OAAO,KAAK,EAAE,GAAG,EAAmB,OAAO,EAAW,MAAM,mBAAmB,CAAC;AAEhF,OAAO,KAAK,EAAE,UAAU,EAAE,mBAAmB,EAAkB,MAAM,sBAAsB,CAAC;AAG5F,MAAM,MAAM,aAAa,GAAG;IAAE,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AACvE,MAAM,MAAM,cAAc,GAAG;IACzB,QAAQ,CAAC,UAAU,EAAE,aAAa,CAAC;IACnC,QAAQ,CAAC,UAAU,EAAE,SAAS,aAAa,EAAE,CAAC;IAC9C,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAC;CACpC,CAAC;AACF,MAAM,MAAM,iBAAiB,GAAG;IAC5B,QAAQ,CAAC,SAAS,EAAE,WAAW,CAAC,mBAAmB,EAAE,aAAa,CAAC,CAAC;IACpE,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC,mBAAmB,EAAE,cAAc,CAAC,CAAC;CACrE,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG;IACjC,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;CACrC,CAAC;AAEF,wBAAsB,gBAAgB,CAAC,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAU9F;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CA2E/D;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,SAAS,CAAC,IAAI,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,iBAAiB,GAAG,IAAI,CAoBzG;AAED,wBAAgB,8BAA8B,CAAC,IAAI,EAAE,SAAS,CAAC,IAAI,GAAG,IAAI,CAWzE;AAED,wBAAgB,4BAA4B,CACxC,KAAK,EAAE,cAAc,GAAG,SAAS,EACjC,YAAY,EAAE,aAAa,EAC3B,YAAY,EAAE,aAAa,GAC5B,sBAAsB,GAAG,SAAS,CASpC"}
@@ -0,0 +1,223 @@
1
+ import { expandedGroupLabelMaxWidth, GRAPH_GEOMETRY, nodeDimensions } from "./panelGraphVisuals.js";
2
+ export async function layoutGraphScene(elk, scene) {
3
+ const graph = createElkLayoutGraph(scene);
4
+ const layout = await elk.layout(graph);
5
+ const positions = new Map();
6
+ collectLayoutPositions(layout, 0, 0, positions);
7
+ const nodeOffsets = new Map();
8
+ collectLayoutOffsets(layout, 0, 0, nodeOffsets);
9
+ const routes = new Map();
10
+ collectEdgeRoutes(layout, 0, 0, nodeOffsets, routes);
11
+ return { positions, routes };
12
+ }
13
+ export function createElkLayoutGraph(scene) {
14
+ const childrenByParent = new Map();
15
+ const portsByNode = new Map();
16
+ const elkEdges = scene.edges.map((edge) => {
17
+ const sourcePortId = `${edge.id}:source`;
18
+ const targetPortId = `${edge.id}:target`;
19
+ appendPort(portsByNode, edge.from, {
20
+ id: sourcePortId,
21
+ width: 1,
22
+ height: 1,
23
+ layoutOptions: { 'elk.port.side': 'EAST' },
24
+ });
25
+ appendPort(portsByNode, edge.to, {
26
+ id: targetPortId,
27
+ width: 1,
28
+ height: 1,
29
+ layoutOptions: { 'elk.port.side': 'WEST' },
30
+ });
31
+ return {
32
+ id: edge.id,
33
+ sources: [sourcePortId],
34
+ targets: [targetPortId],
35
+ };
36
+ });
37
+ for (const node of scene.nodes) {
38
+ const children = childrenByParent.get(node.parentId) ?? [];
39
+ children.push(node);
40
+ childrenByParent.set(node.parentId, children);
41
+ }
42
+ const createNode = (node) => {
43
+ const children = childrenByParent.get(node.id)?.map(createNode);
44
+ const dimensions = nodeDimensions(node);
45
+ return {
46
+ id: node.id,
47
+ width: dimensions.width,
48
+ height: dimensions.height,
49
+ children,
50
+ ports: portsByNode.get(node.id),
51
+ layoutOptions: children?.length ? {
52
+ 'elk.algorithm': 'layered',
53
+ 'elk.direction': 'RIGHT',
54
+ 'elk.edgeRouting': 'ORTHOGONAL',
55
+ 'elk.hierarchyHandling': 'INCLUDE_CHILDREN',
56
+ 'elk.portConstraints': 'FIXED_SIDE',
57
+ 'elk.layered.mergeEdges': 'false',
58
+ 'elk.layered.mergeHierarchyEdges': 'false',
59
+ 'elk.padding': elkPadding(),
60
+ 'elk.spacing.nodeNode': '36',
61
+ 'elk.layered.spacing.nodeNodeBetweenLayers': '72',
62
+ 'elk.spacing.edgeEdge': '10',
63
+ 'elk.layered.spacing.edgeEdgeBetweenLayers': '10',
64
+ } : {
65
+ 'elk.portConstraints': 'FIXED_SIDE',
66
+ },
67
+ };
68
+ };
69
+ return {
70
+ id: 'root',
71
+ children: (childrenByParent.get(undefined) ?? []).map(createNode),
72
+ edges: elkEdges,
73
+ layoutOptions: {
74
+ 'elk.algorithm': 'layered',
75
+ 'elk.direction': 'RIGHT',
76
+ 'elk.edgeRouting': 'ORTHOGONAL',
77
+ 'elk.hierarchyHandling': 'INCLUDE_CHILDREN',
78
+ 'elk.layered.mergeEdges': 'false',
79
+ 'elk.layered.mergeHierarchyEdges': 'false',
80
+ 'elk.spacing.nodeNode': '42',
81
+ 'elk.layered.spacing.nodeNodeBetweenLayers': '88',
82
+ 'elk.spacing.edgeNode': '20',
83
+ 'elk.spacing.edgeEdge': '10',
84
+ 'elk.layered.spacing.edgeEdgeBetweenLayers': '10',
85
+ 'elk.layered.considerModelOrder.strategy': 'NODES_AND_EDGES',
86
+ },
87
+ };
88
+ }
89
+ export function applyGraphLayout(core, scene, layout) {
90
+ applyPositions(core, layout.positions);
91
+ core.batch(() => {
92
+ for (const edge of scene.edges) {
93
+ const route = layout.routes.get(edge.id);
94
+ const cytoscapeEdge = core.getElementById(edge.id);
95
+ if (cytoscapeEdge.empty())
96
+ continue;
97
+ const sourceNode = core.getElementById(edge.from);
98
+ const targetNode = core.getElementById(edge.to);
99
+ const routeData = sourceNode.nonempty() && targetNode.nonempty()
100
+ ? createCytoscapeEdgeRouteData(route, sourceNode.position(), targetNode.position())
101
+ : undefined;
102
+ cytoscapeEdge
103
+ .removeClass('elk-route elk-segments')
104
+ .removeData('sourceEndpoint', 'targetEndpoint', 'segmentWeights', 'segmentDistances');
105
+ if (!routeData)
106
+ continue;
107
+ cytoscapeEdge.data(routeData).addClass('elk-route');
108
+ if (route.bendPoints.length > 0)
109
+ cytoscapeEdge.addClass('elk-segments');
110
+ }
111
+ });
112
+ }
113
+ export function syncExpandedGroupLabelGeometry(core) {
114
+ const groups = core.nodes('node[kind = "group"][collapsed = 0]');
115
+ for (let iteration = 0; iteration < 2; iteration++) {
116
+ const updates = groups.map((node) => ({
117
+ node,
118
+ labelMaxWidth: expandedGroupLabelMaxWidth(node.outerWidth()),
119
+ }));
120
+ core.batch(() => {
121
+ for (const update of updates)
122
+ update.node.data('labelMaxWidth', update.labelMaxWidth);
123
+ });
124
+ }
125
+ }
126
+ export function createCytoscapeEdgeRouteData(route, sourceCenter, targetCenter) {
127
+ if (!route || !isValidRoute(route))
128
+ return undefined;
129
+ const segments = route.bendPoints.map((point) => segmentFromPoint(route.startPoint, route.endPoint, point));
130
+ return {
131
+ sourceEndpoint: endpointOffset(route.startPoint, sourceCenter),
132
+ targetEndpoint: endpointOffset(route.endPoint, targetCenter),
133
+ segmentWeights: segments.map((segment) => segment.weight).join(' '),
134
+ segmentDistances: segments.map((segment) => segment.distance).join(' '),
135
+ };
136
+ }
137
+ function elkPadding() {
138
+ const padding = GRAPH_GEOMETRY.elkGroupPadding;
139
+ return `[top=${padding.top},left=${padding.left},bottom=${padding.bottom},right=${padding.right}]`;
140
+ }
141
+ function appendPort(portsByNode, nodeId, port) {
142
+ const ports = portsByNode.get(nodeId) ?? [];
143
+ ports.push(port);
144
+ portsByNode.set(nodeId, ports);
145
+ }
146
+ function collectLayoutPositions(node, offsetX, offsetY, positions) {
147
+ for (const child of node.children ?? []) {
148
+ const x = offsetX + (child.x ?? 0);
149
+ const y = offsetY + (child.y ?? 0);
150
+ positions.set(child.id, {
151
+ x: x + (child.width ?? 0) / 2,
152
+ y: y + (child.height ?? 0) / 2,
153
+ });
154
+ collectLayoutPositions(child, x, y, positions);
155
+ }
156
+ }
157
+ function collectLayoutOffsets(node, offsetX, offsetY, offsets) {
158
+ offsets.set(node.id, { x: offsetX, y: offsetY });
159
+ for (const child of node.children ?? []) {
160
+ collectLayoutOffsets(child, offsetX + (child.x ?? 0), offsetY + (child.y ?? 0), offsets);
161
+ }
162
+ }
163
+ function collectEdgeRoutes(node, offsetX, offsetY, nodeOffsets, routes) {
164
+ for (const edge of node.edges ?? []) {
165
+ if (edge.sections?.length !== 1)
166
+ continue;
167
+ const section = edge.sections[0];
168
+ if (!section)
169
+ continue;
170
+ // ELK may keep hierarchy edges on an ancestor while expressing their sections in container-local coordinates.
171
+ const containerOffset = edge.container === undefined ? undefined : nodeOffsets.get(edge.container);
172
+ const routeOffsetX = containerOffset?.x ?? offsetX;
173
+ const routeOffsetY = containerOffset?.y ?? offsetY;
174
+ routes.set(edge.id, {
175
+ startPoint: offsetPoint(section.startPoint, routeOffsetX, routeOffsetY),
176
+ bendPoints: (section.bendPoints ?? []).map((point) => offsetPoint(point, routeOffsetX, routeOffsetY)),
177
+ endPoint: offsetPoint(section.endPoint, routeOffsetX, routeOffsetY),
178
+ });
179
+ }
180
+ for (const child of node.children ?? []) {
181
+ collectEdgeRoutes(child, offsetX + (child.x ?? 0), offsetY + (child.y ?? 0), nodeOffsets, routes);
182
+ }
183
+ }
184
+ function offsetPoint(point, offsetX, offsetY) {
185
+ return { x: point.x + offsetX, y: point.y + offsetY };
186
+ }
187
+ function isValidRoute(route) {
188
+ const dx = route.endPoint.x - route.startPoint.x;
189
+ const dy = route.endPoint.y - route.startPoint.y;
190
+ return isFinitePoint(route.startPoint)
191
+ && isFinitePoint(route.endPoint)
192
+ && dx * dx + dy * dy > Number.EPSILON
193
+ && route.bendPoints.every(isFinitePoint);
194
+ }
195
+ function isFinitePoint(point) {
196
+ return Number.isFinite(point.x) && Number.isFinite(point.y);
197
+ }
198
+ function endpointOffset(point, center) {
199
+ return `${point.x - center.x}px ${point.y - center.y}px`;
200
+ }
201
+ function applyPositions(core, positions) {
202
+ core.batch(() => {
203
+ for (const [id, position] of positions) {
204
+ const node = core.getElementById(id);
205
+ if (node.nonempty() && node.isNode() && !node.isParent())
206
+ node.position(position);
207
+ }
208
+ });
209
+ }
210
+ function segmentFromPoint(source, target, point) {
211
+ const dx = target.x - source.x;
212
+ const dy = target.y - source.y;
213
+ const lengthSquared = dx * dx + dy * dy;
214
+ if (lengthSquared === 0)
215
+ return { weight: 0.5, distance: 0 };
216
+ const weight = ((point.x - source.x) * dx + (point.y - source.y) * dy) / lengthSquared;
217
+ const projectedX = source.x + weight * dx;
218
+ const projectedY = source.y + weight * dy;
219
+ const length = Math.sqrt(lengthSquared);
220
+ const distance = ((point.x - projectedX) * -dy + (point.y - projectedY) * dx) / length;
221
+ return { weight, distance };
222
+ }
223
+ //# sourceMappingURL=panelGraphLayout.js.map