@zenfg/inspector 0.1.0-beta.2 → 0.1.0-beta.4
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.
- package/GUIDE.md +182 -0
- package/README.md +60 -26
- package/THEMING.md +124 -0
- package/dist/FrameGraphInspector.d.ts +24 -8
- package/dist/FrameGraphInspector.d.ts.map +1 -1
- package/dist/FrameGraphInspector.js +151 -65
- package/dist/FrameGraphInspector.js.map +1 -1
- package/dist/capture.d.ts +5 -0
- package/dist/capture.d.ts.map +1 -0
- package/dist/capture.js +2 -0
- package/dist/capture.js.map +1 -0
- package/dist/debugCaptureModel.d.ts +28 -4
- package/dist/debugCaptureModel.d.ts.map +1 -1
- package/dist/debugCaptureModel.js +72 -16
- package/dist/debugCaptureModel.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/panelAliasAnalysis.d.ts +1 -0
- package/dist/panelAliasAnalysis.d.ts.map +1 -1
- package/dist/panelAliasAnalysis.js +23 -54
- package/dist/panelAliasAnalysis.js.map +1 -1
- package/dist/panelCytoscapeGraphRenderer.d.ts +18 -3
- package/dist/panelCytoscapeGraphRenderer.d.ts.map +1 -1
- package/dist/panelCytoscapeGraphRenderer.js +183 -41
- package/dist/panelCytoscapeGraphRenderer.js.map +1 -1
- package/dist/panelDetailLayout.d.ts +27 -0
- package/dist/panelDetailLayout.d.ts.map +1 -0
- package/dist/panelDetailLayout.js +174 -0
- package/dist/panelDetailLayout.js.map +1 -0
- package/dist/panelDetailStyles.d.ts +2 -0
- package/dist/panelDetailStyles.d.ts.map +1 -0
- package/dist/panelDetailStyles.js +57 -0
- package/dist/panelDetailStyles.js.map +1 -0
- package/dist/panelDiagnosticsView.d.ts +10 -1
- package/dist/panelDiagnosticsView.d.ts.map +1 -1
- package/dist/panelDiagnosticsView.js +167 -58
- package/dist/panelDiagnosticsView.js.map +1 -1
- package/dist/panelDomHelpers.d.ts.map +1 -1
- package/dist/panelDomHelpers.js +6 -5
- package/dist/panelDomHelpers.js.map +1 -1
- package/dist/panelGraphLayout.d.ts +3 -2
- package/dist/panelGraphLayout.d.ts.map +1 -1
- package/dist/panelGraphLayout.js +20 -5
- package/dist/panelGraphLayout.js.map +1 -1
- package/dist/panelGraphRenderer.d.ts +10 -0
- package/dist/panelGraphRenderer.d.ts.map +1 -1
- package/dist/panelGraphRenderer.js.map +1 -1
- package/dist/panelGraphScene.d.ts +15 -26
- package/dist/panelGraphScene.d.ts.map +1 -1
- package/dist/panelGraphScene.js +191 -208
- package/dist/panelGraphScene.js.map +1 -1
- package/dist/panelGraphSearch.d.ts +16 -0
- package/dist/panelGraphSearch.d.ts.map +1 -0
- package/dist/panelGraphSearch.js +93 -0
- package/dist/panelGraphSearch.js.map +1 -0
- package/dist/panelGraphView.d.ts +82 -0
- package/dist/panelGraphView.d.ts.map +1 -1
- package/dist/panelGraphView.js +42 -9
- package/dist/panelGraphView.js.map +1 -1
- package/dist/panelGraphVisuals.d.ts +18 -6
- package/dist/panelGraphVisuals.d.ts.map +1 -1
- package/dist/panelGraphVisuals.js +156 -116
- package/dist/panelGraphVisuals.js.map +1 -1
- package/dist/panelInspectorView.d.ts +13 -0
- package/dist/panelInspectorView.d.ts.map +1 -1
- package/dist/panelInspectorView.js +326 -46
- package/dist/panelInspectorView.js.map +1 -1
- package/dist/panelListStyles.d.ts +3 -0
- package/dist/panelListStyles.d.ts.map +1 -0
- package/dist/panelListStyles.js +155 -0
- package/dist/panelListStyles.js.map +1 -0
- package/dist/panelMemoryStyles.d.ts +3 -0
- package/dist/panelMemoryStyles.d.ts.map +1 -0
- package/dist/panelMemoryStyles.js +42 -0
- package/dist/panelMemoryStyles.js.map +1 -0
- package/dist/panelMemoryView.d.ts +13 -1
- package/dist/panelMemoryView.d.ts.map +1 -1
- package/dist/panelMemoryView.js +161 -41
- package/dist/panelMemoryView.js.map +1 -1
- package/dist/panelPassesView.d.ts +15 -0
- package/dist/panelPassesView.d.ts.map +1 -1
- package/dist/panelPassesView.js +202 -72
- package/dist/panelPassesView.js.map +1 -1
- package/dist/panelRawView.d.ts +15 -0
- package/dist/panelRawView.d.ts.map +1 -0
- package/dist/panelRawView.js +105 -0
- package/dist/panelRawView.js.map +1 -0
- package/dist/panelResourcesView.d.ts +8 -0
- package/dist/panelResourcesView.d.ts.map +1 -1
- package/dist/panelResourcesView.js +62 -31
- package/dist/panelResourcesView.js.map +1 -1
- package/dist/panelSelection.d.ts +9 -1
- package/dist/panelSelection.d.ts.map +1 -1
- package/dist/panelSelection.js +36 -6
- package/dist/panelSelection.js.map +1 -1
- package/dist/panelTheme.d.ts +13 -0
- package/dist/panelTheme.d.ts.map +1 -0
- package/dist/panelTheme.js +104 -0
- package/dist/panelTheme.js.map +1 -0
- package/dist/panelTypes.d.ts +18 -4
- package/dist/panelTypes.d.ts.map +1 -1
- package/dist/panelTypes.js.map +1 -1
- package/dist/panelVisualTheme.d.ts +176 -71
- package/dist/panelVisualTheme.d.ts.map +1 -1
- package/dist/panelVisualTheme.js +34 -36
- package/dist/panelVisualTheme.js.map +1 -1
- package/dist/panelWorkbenchHelpers.d.ts +12 -1
- package/dist/panelWorkbenchHelpers.d.ts.map +1 -1
- package/dist/panelWorkbenchHelpers.js +63 -2
- package/dist/panelWorkbenchHelpers.js.map +1 -1
- package/dist/panelWorkbenchStyles.d.ts +2 -0
- package/dist/panelWorkbenchStyles.d.ts.map +1 -0
- package/dist/panelWorkbenchStyles.js +101 -0
- package/dist/panelWorkbenchStyles.js.map +1 -0
- package/dist/panelWorkbenchView.d.ts +16 -0
- package/dist/panelWorkbenchView.d.ts.map +1 -1
- package/dist/panelWorkbenchView.js +228 -50
- package/dist/panelWorkbenchView.js.map +1 -1
- package/dist/query.js.map +1 -1
- package/dist/styles.d.ts.map +1 -1
- package/dist/styles.js +161 -205
- package/dist/styles.js.map +1 -1
- package/dist/theme.d.ts +22 -0
- package/dist/theme.d.ts.map +1 -0
- package/dist/theme.js +10 -0
- package/dist/theme.js.map +1 -0
- package/dist/themeDefinitions.d.ts +251 -0
- package/dist/themeDefinitions.d.ts.map +1 -0
- package/dist/themeDefinitions.js +96 -0
- package/dist/themeDefinitions.js.map +1 -0
- package/dist/themePalette.d.ts +66 -0
- package/dist/themePalette.d.ts.map +1 -0
- package/dist/themePalette.js +37 -0
- package/dist/themePalette.js.map +1 -0
- package/dist/themes.css +165 -0
- package/package.json +44 -10
- package/src/FrameGraphInspector.ts +145 -68
- package/src/capture.ts +4 -0
- package/src/debugCaptureModel.ts +97 -21
- package/src/index.ts +6 -2
- package/src/panelAliasAnalysis.ts +24 -70
- package/src/panelCytoscapeGraphRenderer.ts +177 -40
- package/src/panelDetailLayout.ts +155 -0
- package/src/panelDetailStyles.ts +56 -0
- package/src/panelDiagnosticsView.ts +167 -86
- package/src/panelDomHelpers.ts +4 -5
- package/src/panelGraphLayout.ts +20 -5
- package/src/panelGraphRenderer.ts +7 -0
- package/src/panelGraphScene.ts +204 -292
- package/src/panelGraphSearch.ts +96 -0
- package/src/panelGraphView.ts +41 -9
- package/src/panelGraphVisuals.ts +152 -118
- package/src/panelInspectorView.ts +315 -48
- package/src/panelListStyles.ts +154 -0
- package/src/panelMemoryStyles.ts +41 -0
- package/src/panelMemoryView.ts +178 -53
- package/src/panelPassesView.ts +190 -137
- package/src/panelRawView.ts +99 -0
- package/src/panelResourcesView.ts +69 -56
- package/src/panelSelection.ts +39 -7
- package/src/panelTheme.ts +85 -0
- package/src/panelTypes.ts +16 -4
- package/src/panelVisualTheme.ts +38 -38
- package/src/panelWorkbenchHelpers.ts +62 -4
- package/src/panelWorkbenchStyles.ts +100 -0
- package/src/panelWorkbenchView.ts +201 -52
- package/src/query.ts +11 -11
- package/src/styles.ts +162 -205
- package/src/theme.ts +24 -0
- package/src/themeDefinitions.ts +103 -0
- package/src/themePalette.ts +44 -0
package/src/panelGraphVisuals.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type cytoscape from 'cytoscape';
|
|
2
2
|
|
|
3
3
|
import type { GraphScene, GraphSceneEdge, GraphSceneNode } from './panelGraphScene.ts';
|
|
4
|
-
import { GRAPH_VISUAL_THEME } from './panelVisualTheme.ts';
|
|
4
|
+
import { GRAPH_VISUAL_THEME, type GraphVisualTheme } from './panelVisualTheme.ts';
|
|
5
|
+
import { declarationEntrances, type FrameGraphDebugViewModel } from './debugCaptureModel.ts';
|
|
5
6
|
|
|
6
7
|
export { GRAPH_VISUAL_THEME } from './panelVisualTheme.ts';
|
|
7
8
|
|
|
@@ -16,76 +17,75 @@ export const GRAPH_GEOMETRY = {
|
|
|
16
17
|
groupPadding: 32,
|
|
17
18
|
elkGroupPadding: { top: 46, right: 24, bottom: 24, left: 24 },
|
|
18
19
|
edgeCornerRadius: 6,
|
|
20
|
+
outputWidth: 208,
|
|
21
|
+
outputTipWidth: 24,
|
|
22
|
+
outputLabelWidth: 160,
|
|
19
23
|
} as const;
|
|
20
24
|
|
|
21
25
|
const ENDPOINT_EDGE_DISTANCES = 'endpoints' as unknown as cytoscape.Css.Edge['edge-distances'];
|
|
22
26
|
const EDGE_SEGMENT_RADII = `${GRAPH_GEOMETRY.edgeCornerRadius}px` as unknown as cytoscape.Css.Edge['segment-radii'];
|
|
23
27
|
|
|
24
28
|
export type GraphLegendEntry = {
|
|
29
|
+
readonly group: 'Execution' | 'Resources' | 'Relationships';
|
|
25
30
|
readonly key: string;
|
|
26
31
|
readonly label: string;
|
|
27
32
|
readonly color: string;
|
|
28
|
-
readonly shape: 'box' | 'ellipse' | 'group' | 'line';
|
|
33
|
+
readonly shape: 'box' | 'cut-rectangle' | 'tag' | 'ellipse' | 'group' | 'line';
|
|
29
34
|
readonly lineStyle?: 'solid' | 'dotted' | 'dashed';
|
|
30
35
|
readonly hollowArrow?: boolean;
|
|
31
36
|
};
|
|
32
37
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
38
|
+
export function createGraphLegend(snapshot: FrameGraphDebugViewModel, theme: GraphVisualTheme = GRAPH_VISUAL_THEME, showResourceDeclarations = true): readonly GraphLegendEntry[] {
|
|
39
|
+
const nodeEntries = {
|
|
40
|
+
render: nodeLegend('render', 'Render', theme.render.stroke),
|
|
41
|
+
compute: nodeLegend('compute', 'Compute', theme.compute.stroke),
|
|
42
|
+
copy: nodeLegend('copy', 'Copy', theme.copy.stroke),
|
|
43
|
+
'clear-buffer': nodeLegend('clear', 'Clear', theme.clear.stroke),
|
|
44
|
+
command: nodeLegend('command', 'Command', theme.command.stroke),
|
|
45
|
+
'external-submission': { ...nodeLegend('external', 'External', theme.external.stroke), shape: 'cut-rectangle' as const },
|
|
46
|
+
} as const;
|
|
41
47
|
|
|
42
|
-
export function createGraphLegend(scene: GraphScene): readonly GraphLegendEntry[] {
|
|
43
48
|
const entries: GraphLegendEntry[] = [];
|
|
44
|
-
const passKinds = new Set(
|
|
45
|
-
? [node.passKind]
|
|
46
|
-
: []));
|
|
49
|
+
const passKinds = new Set(snapshot.nodes.map((node) => node.kind));
|
|
47
50
|
for (const kind of ['render', 'compute', 'copy', 'clear-buffer', 'command', 'external-submission'] as const) {
|
|
48
|
-
if (passKinds.has(kind)) entries.push(
|
|
49
|
-
}
|
|
50
|
-
if (scene.nodes.some((node) => node.kind === 'group')) {
|
|
51
|
-
entries.push({ key: 'group', label: 'Group', color: GRAPH_VISUAL_THEME.group.stroke, shape: 'group' });
|
|
51
|
+
if (passKinds.has(kind)) entries.push(nodeEntries[kind]);
|
|
52
52
|
}
|
|
53
|
-
|
|
54
|
-
|
|
53
|
+
const retained = new Set(snapshot.nodes.map((node) => node.id));
|
|
54
|
+
const used = new Set([
|
|
55
|
+
...snapshot.accessEdges.filter((access) => retained.has(access.nodeId)).map((access) => access.resource.id),
|
|
56
|
+
...snapshot.roots.flatMap((root) => root.resource ? [root.resource.id] : []),
|
|
57
|
+
]);
|
|
58
|
+
const resources = showResourceDeclarations ? snapshot.resources.filter((resource) => used.has(resource.id)) : [];
|
|
59
|
+
if (resources.length) entries.push({ group: 'Resources', key: 'declaration', label: 'Declaration', color: theme.declaration.stroke, shape: 'ellipse' });
|
|
60
|
+
if (snapshot.roots.some((root) => root.resource)) entries.push({ group: 'Resources', key: 'output', label: 'Output', color: theme.output.stroke, shape: 'tag' });
|
|
61
|
+
if ([...snapshot.nodes, ...resources].some((item) => item.debugGroupId !== undefined)) {
|
|
62
|
+
entries.push({ group: 'Relationships', key: 'group', label: 'Group', color: theme.group.stroke, shape: 'group' });
|
|
55
63
|
}
|
|
56
|
-
if (
|
|
57
|
-
|
|
64
|
+
if (snapshot.edges.some((edge) => edge.kind === 'value')
|
|
65
|
+
|| (showResourceDeclarations && declarationEntrances(snapshot.nodes, snapshot.accessEdges, snapshot.edges).length)
|
|
66
|
+
|| snapshot.roots.some((root) => root.resource && root.resolution && ((showResourceDeclarations && root.resolution.usesInitialContents) || root.resolution.producerNodeIds.length))) {
|
|
67
|
+
entries.push(edgeLegend('flow', 'Resource Flow', theme.dependency.value, 'solid'));
|
|
58
68
|
}
|
|
59
|
-
if (
|
|
69
|
+
if (snapshot.edges.some((edge) => edge.kind === 'ordering')) {
|
|
60
70
|
entries.push({
|
|
61
|
-
|
|
62
|
-
label: 'Culled',
|
|
63
|
-
color: GRAPH_VISUAL_THEME.culled.stroke,
|
|
64
|
-
shape: 'box',
|
|
65
|
-
lineStyle: 'dashed',
|
|
66
|
-
});
|
|
67
|
-
}
|
|
68
|
-
if (scene.edges.some((edge) => edge.kind === 'dependency' && edge.dependencyKind === 'value')) {
|
|
69
|
-
entries.push(edgeLegend('value', 'Value', GRAPH_VISUAL_THEME.dependency.value, 'solid'));
|
|
70
|
-
}
|
|
71
|
-
if (scene.edges.some((edge) => edge.kind === 'dependency' && edge.dependencyKind === 'ordering')) {
|
|
72
|
-
entries.push({
|
|
73
|
-
...edgeLegend('ordering', 'Order', GRAPH_VISUAL_THEME.dependency.ordering, 'dotted'),
|
|
71
|
+
...edgeLegend('ordering', 'Order', theme.dependency.ordering, 'dotted'),
|
|
74
72
|
hollowArrow: true,
|
|
75
73
|
});
|
|
76
74
|
}
|
|
77
|
-
if (scene.edges.some((edge) => edge.kind === 'access' && edge.accessMode === 'read')) {
|
|
78
|
-
entries.push(edgeLegend('read', 'Read', GRAPH_VISUAL_THEME.access.read, 'solid'));
|
|
79
|
-
}
|
|
80
|
-
if (scene.edges.some((edge) => edge.kind === 'access' && edge.accessMode === 'write')) {
|
|
81
|
-
entries.push(edgeLegend('write', 'Write', GRAPH_VISUAL_THEME.access.write, 'solid'));
|
|
82
|
-
}
|
|
83
75
|
return entries;
|
|
84
76
|
}
|
|
85
77
|
|
|
86
|
-
export function createGraphStyles(): cytoscape.StylesheetJson {
|
|
87
|
-
const theme = GRAPH_VISUAL_THEME;
|
|
78
|
+
export function createGraphStyles(theme: GraphVisualTheme = GRAPH_VISUAL_THEME): cytoscape.StylesheetJson {
|
|
88
79
|
return [
|
|
80
|
+
{
|
|
81
|
+
selector: 'core',
|
|
82
|
+
style: {
|
|
83
|
+
'active-bg-color': theme.activeBackground.color,
|
|
84
|
+
'active-bg-opacity': theme.activeBackground.opacity,
|
|
85
|
+
'active-bg-size': theme.activeBackground.size,
|
|
86
|
+
// Cytoscape accepts partial core styles; its Core type marks every property required.
|
|
87
|
+
} satisfies Partial<cytoscape.Css.Core> as cytoscape.Css.Core,
|
|
88
|
+
},
|
|
89
89
|
{
|
|
90
90
|
selector: 'node',
|
|
91
91
|
style: {
|
|
@@ -94,14 +94,16 @@ export function createGraphStyles(): cytoscape.StylesheetJson {
|
|
|
94
94
|
'height': 'data(height)',
|
|
95
95
|
'label': 'data(displayLabel)',
|
|
96
96
|
'text-wrap': 'wrap',
|
|
97
|
-
'text-max-width': '
|
|
98
|
-
'font-family':
|
|
99
|
-
'font-size':
|
|
97
|
+
'text-max-width': 'data(labelMaxWidth)',
|
|
98
|
+
'font-family': theme.fontFamily,
|
|
99
|
+
'font-size': (element: cytoscape.SingularElementArgument) => (element.data('labelFontSize') as number | undefined) ?? theme.fontSize,
|
|
100
|
+
'line-height': 1.2,
|
|
100
101
|
'min-zoomed-font-size': GRAPH_GEOMETRY.minimumZoomedFontSize,
|
|
101
102
|
'color': theme.text,
|
|
102
103
|
'background-color': theme.surfaceRaised,
|
|
103
104
|
'border-color': theme.group.stroke,
|
|
104
|
-
'border-width':
|
|
105
|
+
'border-width': theme.nodeBorderWidth,
|
|
106
|
+
'border-style': 'solid',
|
|
105
107
|
'text-valign': 'center',
|
|
106
108
|
'text-halign': 'center',
|
|
107
109
|
'overlay-opacity': 0,
|
|
@@ -118,34 +120,28 @@ export function createGraphStyles(): cytoscape.StylesheetJson {
|
|
|
118
120
|
style: {
|
|
119
121
|
'background-color': theme.external.fill,
|
|
120
122
|
'border-color': theme.external.stroke,
|
|
121
|
-
'
|
|
122
|
-
'
|
|
123
|
+
'shape': 'cut-rectangle',
|
|
124
|
+
'corner-radius': '10px',
|
|
123
125
|
},
|
|
124
126
|
},
|
|
125
127
|
{
|
|
126
|
-
selector: 'node[kind = "
|
|
127
|
-
style: {
|
|
128
|
-
'background-color': theme.culled.fill,
|
|
129
|
-
'border-color': theme.culled.stroke,
|
|
130
|
-
'color': theme.muted,
|
|
131
|
-
'border-style': 'dashed',
|
|
132
|
-
'opacity': 0.72,
|
|
133
|
-
},
|
|
134
|
-
},
|
|
135
|
-
{
|
|
136
|
-
selector: 'node[kind = "resource"][resourceKind = "texture"]',
|
|
128
|
+
selector: 'node[kind = "resource"]',
|
|
137
129
|
style: {
|
|
138
130
|
'shape': 'ellipse',
|
|
139
|
-
'background-color': theme.
|
|
140
|
-
'border-color': theme.
|
|
131
|
+
'background-color': theme.declaration.fill,
|
|
132
|
+
'border-color': theme.declaration.stroke,
|
|
141
133
|
},
|
|
142
134
|
},
|
|
143
135
|
{
|
|
144
|
-
selector: 'node[kind = "
|
|
136
|
+
selector: 'node[kind = "root"]',
|
|
145
137
|
style: {
|
|
146
|
-
'shape': '
|
|
147
|
-
'
|
|
148
|
-
'
|
|
138
|
+
'shape': 'polygon',
|
|
139
|
+
'shape-polygon-points': `-1 -1 ${1 - 2 * GRAPH_GEOMETRY.outputTipWidth / GRAPH_GEOMETRY.outputWidth} -1 1 0 ${1 - 2 * GRAPH_GEOMETRY.outputTipWidth / GRAPH_GEOMETRY.outputWidth} 1 -1 1`,
|
|
140
|
+
'background-color': theme.output.fill,
|
|
141
|
+
'border-color': theme.output.stroke,
|
|
142
|
+
'text-max-width': 'data(labelMaxWidth)',
|
|
143
|
+
// Keep a short tip and center the label in the rectangular body.
|
|
144
|
+
'text-margin-x': -GRAPH_GEOMETRY.outputTipWidth / 2 * theme.fontSize / GRAPH_GEOMETRY.baseFontSize,
|
|
149
145
|
},
|
|
150
146
|
},
|
|
151
147
|
{
|
|
@@ -159,13 +155,13 @@ export function createGraphStyles(): cytoscape.StylesheetJson {
|
|
|
159
155
|
style: {
|
|
160
156
|
'background-color': theme.group.fill,
|
|
161
157
|
'border-color': theme.group.stroke,
|
|
162
|
-
'border-width':
|
|
158
|
+
'border-width': theme.groupBorderWidth,
|
|
163
159
|
'color': theme.text,
|
|
164
160
|
},
|
|
165
161
|
},
|
|
166
162
|
{
|
|
167
163
|
selector: 'node[kind = "group"][collapsed = 0]',
|
|
168
|
-
style: expandedGroupStyle(theme.group.fill),
|
|
164
|
+
style: expandedGroupStyle(theme.group.fill, theme),
|
|
169
165
|
},
|
|
170
166
|
{
|
|
171
167
|
selector: 'node[kind = "group"][collapsed = 0][depthBand = 1]',
|
|
@@ -174,16 +170,16 @@ export function createGraphStyles(): cytoscape.StylesheetJson {
|
|
|
174
170
|
{
|
|
175
171
|
selector: 'edge',
|
|
176
172
|
style: {
|
|
177
|
-
'width':
|
|
173
|
+
'width': theme.edgeWidth,
|
|
178
174
|
'line-color': theme.dependency.value,
|
|
179
|
-
'line-opacity':
|
|
175
|
+
'line-opacity': theme.edgeOpacity,
|
|
180
176
|
'target-arrow-color': theme.dependency.value,
|
|
181
177
|
'target-arrow-shape': 'triangle',
|
|
182
178
|
'target-arrow-fill': 'filled',
|
|
183
|
-
'arrow-scale':
|
|
179
|
+
'arrow-scale': theme.arrowScale,
|
|
184
180
|
'curve-style': 'straight',
|
|
185
181
|
'label': 'data(displayLabel)',
|
|
186
|
-
'font-family':
|
|
182
|
+
'font-family': theme.fontFamily,
|
|
187
183
|
'font-size': 10,
|
|
188
184
|
'min-zoomed-font-size': GRAPH_GEOMETRY.minimumZoomedFontSize,
|
|
189
185
|
'color': theme.text,
|
|
@@ -196,33 +192,15 @@ export function createGraphStyles(): cytoscape.StylesheetJson {
|
|
|
196
192
|
},
|
|
197
193
|
},
|
|
198
194
|
{
|
|
199
|
-
selector: 'edge[
|
|
195
|
+
selector: 'edge[kind = "ordering"]',
|
|
200
196
|
style: {
|
|
201
197
|
'line-color': theme.dependency.ordering,
|
|
202
198
|
'target-arrow-color': theme.dependency.ordering,
|
|
203
199
|
'line-style': 'dotted',
|
|
204
200
|
'target-arrow-fill': 'hollow',
|
|
205
|
-
'line-opacity':
|
|
206
|
-
},
|
|
207
|
-
},
|
|
208
|
-
{
|
|
209
|
-
selector: 'edge[accessMode = "read"]',
|
|
210
|
-
style: {
|
|
211
|
-
'line-color': theme.access.read,
|
|
212
|
-
'target-arrow-color': theme.access.read,
|
|
201
|
+
'line-opacity': theme.orderingOpacity,
|
|
213
202
|
},
|
|
214
203
|
},
|
|
215
|
-
{
|
|
216
|
-
selector: 'edge[accessMode = "write"]',
|
|
217
|
-
style: {
|
|
218
|
-
'line-color': theme.access.write,
|
|
219
|
-
'target-arrow-color': theme.access.write,
|
|
220
|
-
},
|
|
221
|
-
},
|
|
222
|
-
{
|
|
223
|
-
selector: 'edge[dashed = 1]',
|
|
224
|
-
style: { 'line-style': 'dashed', 'line-opacity': 0.46, 'opacity': 0.58 },
|
|
225
|
-
},
|
|
226
204
|
{
|
|
227
205
|
selector: 'edge.elk-route',
|
|
228
206
|
style: {
|
|
@@ -244,7 +222,7 @@ export function createGraphStyles(): cytoscape.StylesheetJson {
|
|
|
244
222
|
selector: 'node.semantic-hover',
|
|
245
223
|
style: {
|
|
246
224
|
'border-color': theme.hover,
|
|
247
|
-
'border-width':
|
|
225
|
+
'border-width': theme.hoverWidth,
|
|
248
226
|
'z-index': 15,
|
|
249
227
|
},
|
|
250
228
|
},
|
|
@@ -254,7 +232,7 @@ export function createGraphStyles(): cytoscape.StylesheetJson {
|
|
|
254
232
|
'line-color': theme.hover,
|
|
255
233
|
'target-arrow-color': theme.hover,
|
|
256
234
|
'line-opacity': 1,
|
|
257
|
-
'width':
|
|
235
|
+
'width': theme.hoverWidth,
|
|
258
236
|
'z-index': 15,
|
|
259
237
|
},
|
|
260
238
|
},
|
|
@@ -262,10 +240,7 @@ export function createGraphStyles(): cytoscape.StylesheetJson {
|
|
|
262
240
|
selector: 'node.semantic-selected',
|
|
263
241
|
style: {
|
|
264
242
|
'border-color': theme.selected,
|
|
265
|
-
'border-width':
|
|
266
|
-
'underlay-color': theme.selected,
|
|
267
|
-
'underlay-padding': 5,
|
|
268
|
-
'underlay-opacity': 0.18,
|
|
243
|
+
'border-width': theme.selectedWidth,
|
|
269
244
|
'opacity': 1,
|
|
270
245
|
'z-index': 20,
|
|
271
246
|
},
|
|
@@ -276,50 +251,108 @@ export function createGraphStyles(): cytoscape.StylesheetJson {
|
|
|
276
251
|
'line-color': theme.selected,
|
|
277
252
|
'target-arrow-color': theme.selected,
|
|
278
253
|
'line-opacity': 1,
|
|
279
|
-
'width':
|
|
254
|
+
'width': theme.selectedWidth,
|
|
280
255
|
'z-index': 20,
|
|
281
256
|
},
|
|
282
257
|
},
|
|
283
258
|
];
|
|
284
259
|
}
|
|
285
260
|
|
|
286
|
-
|
|
261
|
+
function baseNodeDimensions(node: GraphSceneNode): { readonly width: number; readonly height: number } {
|
|
287
262
|
switch (node.kind) {
|
|
288
263
|
case 'pass':
|
|
289
264
|
return { width: 184, height: node.label.includes('\n') ? 62 : 48 };
|
|
290
|
-
case '
|
|
291
|
-
return { width:
|
|
265
|
+
case 'root':
|
|
266
|
+
return { width: GRAPH_GEOMETRY.outputWidth, height: Math.max(58, node.label.split('\n').length * 17 + 24) };
|
|
292
267
|
case 'resource':
|
|
293
|
-
return { width:
|
|
268
|
+
return { width: 184, height: Math.max(58, node.label.split('\n').length * 17 + 24) };
|
|
294
269
|
case 'group':
|
|
295
|
-
return node.collapsed ? { width:
|
|
270
|
+
return node.collapsed ? { width: 240, height: 80 } : { width: 120, height: 80 };
|
|
296
271
|
}
|
|
297
272
|
}
|
|
298
273
|
|
|
274
|
+
export function nodeDimensions(node: GraphSceneNode, theme: GraphVisualTheme = GRAPH_VISUAL_THEME): { readonly width: number; readonly height: number } {
|
|
275
|
+
const base = baseNodeDimensions(node);
|
|
276
|
+
const scale = theme.fontSize / GRAPH_GEOMETRY.baseFontSize;
|
|
277
|
+
return { width: base.width * scale, height: base.height * scale };
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/** Enlarge short labels within the existing box; retain the theme size for dense text. */
|
|
281
|
+
export function graphLabelFontSize(label: string, width: number, height: number, theme: GraphVisualTheme): number {
|
|
282
|
+
const context = graphTextContext();
|
|
283
|
+
if (context) context.font = theme.fontSize + 'px ' + theme.fontFamily;
|
|
284
|
+
const lines = label.split('\n');
|
|
285
|
+
const widest = Math.max(1, ...lines.map(line => context?.measureText(line).width ?? [...line].length * theme.fontSize * .62));
|
|
286
|
+
const widthScale = width / widest;
|
|
287
|
+
const heightScale = height / (lines.length * theme.fontSize * 1.2);
|
|
288
|
+
return Math.max(theme.fontSize, Math.floor(theme.fontSize * Math.min(1.25, widthScale, heightScale) * 4) / 4);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/** Wrap only the group name (up to two lines); keep the node-count row distinct. */
|
|
292
|
+
export function fitCollapsedGroupLabel(label: string, width: number, theme: GraphVisualTheme): string {
|
|
293
|
+
const [name = '', ...summary] = label.split('\n');
|
|
294
|
+
const context = graphTextContext();
|
|
295
|
+
if (context) context.font = theme.fontSize + 'px ' + theme.fontFamily;
|
|
296
|
+
const measure = (text: string) => context?.measureText(text).width ?? [...text].length * theme.fontSize * .62;
|
|
297
|
+
const chars = [...name];
|
|
298
|
+
let split = 0;
|
|
299
|
+
while (split < chars.length && measure(chars.slice(0, split + 1).join('')) <= width) split++;
|
|
300
|
+
if (split === chars.length) return fitGraphLabel(label, width, theme);
|
|
301
|
+
// Prefer a word/path boundary without losing characters, including Unicode.
|
|
302
|
+
const prefix = chars.slice(0, split).join('');
|
|
303
|
+
const boundary = Math.max(prefix.lastIndexOf(' '), prefix.lastIndexOf('.'), prefix.lastIndexOf('/'));
|
|
304
|
+
if (boundary > prefix.length / 2) split = [...prefix.slice(0, boundary + 1)].length;
|
|
305
|
+
return fitGraphLabel([
|
|
306
|
+
chars.slice(0, split).join('').trimEnd(),
|
|
307
|
+
chars.slice(split).join('').trimStart(),
|
|
308
|
+
...summary,
|
|
309
|
+
].join('\n'), width, theme);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/** Fit the already semantic label to its available width after font changes. */
|
|
313
|
+
export function fitGraphLabel(label: string, width: number, theme: GraphVisualTheme): string {
|
|
314
|
+
const context = graphTextContext();
|
|
315
|
+
if (context) context.font = theme.fontSize + 'px ' + theme.fontFamily;
|
|
316
|
+
const measure = (text: string) => context?.measureText(text).width ?? [...text].length * theme.fontSize * .62;
|
|
317
|
+
return label.split('\n').map(line => {
|
|
318
|
+
if (measure(line) <= width) return line;
|
|
319
|
+
const chars = [...line];
|
|
320
|
+
while (chars.length && measure(chars.join('') + '…') > width) chars.pop();
|
|
321
|
+
return chars.join('') + '…';
|
|
322
|
+
}).join('\n');
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
let textContext: CanvasRenderingContext2D | null | undefined;
|
|
326
|
+
function graphTextContext(): CanvasRenderingContext2D | null {
|
|
327
|
+
if (typeof document === 'undefined') return null;
|
|
328
|
+
if (textContext === undefined) textContext = document.createElement('canvas').getContext('2d');
|
|
329
|
+
return textContext;
|
|
330
|
+
}
|
|
331
|
+
|
|
299
332
|
export function graphLayoutGeometryKey(scene: GraphScene): string {
|
|
300
333
|
return JSON.stringify({
|
|
301
334
|
topology: scene.topologyKey,
|
|
302
335
|
dimensions: scene.nodes.map((node) => {
|
|
303
336
|
const dimensions = nodeDimensions(node);
|
|
304
|
-
return [node.id, dimensions.width, dimensions.height];
|
|
337
|
+
return [node.id, dimensions.width, dimensions.height, node.kind === 'pass' && node.passKind === 'external-submission'];
|
|
305
338
|
}),
|
|
306
339
|
});
|
|
307
340
|
}
|
|
308
341
|
|
|
309
|
-
export function graphEdgeDisplayLabel(
|
|
310
|
-
return
|
|
342
|
+
export function graphEdgeDisplayLabel(_edge: GraphSceneEdge): string {
|
|
343
|
+
return '';
|
|
311
344
|
}
|
|
312
345
|
|
|
313
346
|
export function expandedGroupLabelMaxWidth(outerWidth: number): number {
|
|
314
347
|
return Math.max(80, Math.floor(outerWidth - GRAPH_GEOMETRY.groupTitleInset * 2));
|
|
315
348
|
}
|
|
316
349
|
|
|
317
|
-
export function isOverviewGraphScale(zoom: number): boolean {
|
|
318
|
-
return
|
|
350
|
+
export function isOverviewGraphScale(zoom: number, theme: GraphVisualTheme = GRAPH_VISUAL_THEME): boolean {
|
|
351
|
+
return theme.fontSize * zoom < GRAPH_GEOMETRY.overviewFontThreshold;
|
|
319
352
|
}
|
|
320
353
|
|
|
321
354
|
function nodeLegend(key: string, label: string, color: string): GraphLegendEntry {
|
|
322
|
-
return { key, label, color, shape: 'box' };
|
|
355
|
+
return { group: 'Execution', key, label, color, shape: 'box' };
|
|
323
356
|
}
|
|
324
357
|
|
|
325
358
|
function edgeLegend(
|
|
@@ -328,7 +361,7 @@ function edgeLegend(
|
|
|
328
361
|
color: string,
|
|
329
362
|
lineStyle: NonNullable<GraphLegendEntry['lineStyle']>,
|
|
330
363
|
): GraphLegendEntry {
|
|
331
|
-
return { key, label, color, shape: 'line', lineStyle };
|
|
364
|
+
return { group: 'Relationships', key, label, color, shape: 'line', lineStyle };
|
|
332
365
|
}
|
|
333
366
|
|
|
334
367
|
function passStyle(
|
|
@@ -341,15 +374,16 @@ function passStyle(
|
|
|
341
374
|
};
|
|
342
375
|
}
|
|
343
376
|
|
|
344
|
-
function expandedGroupStyle(backgroundColor: string): cytoscape.Css.Node {
|
|
377
|
+
function expandedGroupStyle(backgroundColor: string, theme: GraphVisualTheme): cytoscape.Css.Node {
|
|
345
378
|
const geometry = GRAPH_GEOMETRY;
|
|
346
379
|
return {
|
|
347
380
|
'background-color': backgroundColor,
|
|
348
|
-
'background-opacity':
|
|
349
|
-
'border-color':
|
|
350
|
-
'border-width':
|
|
381
|
+
'background-opacity': theme.groupOpacity,
|
|
382
|
+
'border-color': theme.group.stroke,
|
|
383
|
+
'border-width': theme.groupBorderWidth,
|
|
351
384
|
'padding': `${geometry.groupPadding}px`,
|
|
352
|
-
'compound-sizing-wrt-labels': '
|
|
385
|
+
'compound-sizing-wrt-labels': 'exclude',
|
|
386
|
+
'text-wrap': 'ellipsis',
|
|
353
387
|
'min-width': '120px',
|
|
354
388
|
'min-height': '80px',
|
|
355
389
|
'text-valign': 'top-inside',
|
|
@@ -358,7 +392,7 @@ function expandedGroupStyle(backgroundColor: string): cytoscape.Css.Node {
|
|
|
358
392
|
'text-margin-x': geometry.groupTitleInset - geometry.groupPadding * 2,
|
|
359
393
|
'text-margin-y': geometry.groupTitleInset - geometry.groupPadding * 2,
|
|
360
394
|
'line-height': geometry.groupTitleLineHeight,
|
|
361
|
-
'color':
|
|
395
|
+
'color': theme.text,
|
|
362
396
|
'font-weight': 600,
|
|
363
397
|
};
|
|
364
398
|
}
|