@rokkit/graph 1.8.0 → 1.8.2

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 (119) hide show
  1. package/README.md +59 -0
  2. package/dist/Graph.svelte +108 -134
  3. package/dist/Graph.svelte.d.ts +1 -1
  4. package/dist/GraphLegend.svelte +81 -10
  5. package/dist/GraphLegend.svelte.d.ts +15 -0
  6. package/dist/GraphState.svelte.d.ts +161 -52
  7. package/dist/GraphState.svelte.js +380 -304
  8. package/dist/actions/canvas.d.ts +18 -0
  9. package/dist/actions/canvas.js +59 -0
  10. package/dist/actions/choices.d.ts +12 -0
  11. package/dist/actions/choices.js +23 -0
  12. package/dist/actions/interactions.d.ts +20 -0
  13. package/dist/actions/interactions.js +57 -0
  14. package/dist/actions/listen.d.ts +2 -0
  15. package/dist/actions/listen.js +10 -0
  16. package/dist/canvas/frame.d.ts +41 -0
  17. package/dist/canvas/frame.js +34 -0
  18. package/dist/controls/BundleControl.svelte +11 -10
  19. package/dist/controls/BundleControl.svelte.d.ts +0 -7
  20. package/dist/controls/DensityControl.svelte +17 -9
  21. package/dist/controls/DensityControl.svelte.d.ts +0 -8
  22. package/dist/controls/DepthControl.svelte +11 -4
  23. package/dist/controls/DepthControl.svelte.d.ts +0 -9
  24. package/dist/controls/DrillBar.svelte +63 -0
  25. package/dist/controls/DrillBar.svelte.d.ts +9 -0
  26. package/dist/controls/EdgeStyleControl.svelte +10 -10
  27. package/dist/controls/EdgeStyleControl.svelte.d.ts +0 -6
  28. package/dist/controls/HiddenControl.svelte +27 -0
  29. package/dist/controls/HiddenControl.svelte.d.ts +7 -0
  30. package/dist/controls/MeasureControl.svelte +39 -0
  31. package/dist/controls/MeasureControl.svelte.d.ts +12 -0
  32. package/dist/controls/ToggleControl.svelte +33 -0
  33. package/dist/controls/ToggleControl.svelte.d.ts +13 -0
  34. package/dist/controls/ViolationsControl.svelte +27 -0
  35. package/dist/controls/ViolationsControl.svelte.d.ts +7 -0
  36. package/dist/controls/ZoomControl.svelte +21 -18
  37. package/dist/controls/zoom.d.ts +3 -0
  38. package/dist/controls/zoom.js +6 -0
  39. package/dist/diagrams/ArcDiagram.svelte +116 -0
  40. package/dist/diagrams/ArcDiagram.svelte.d.ts +31 -0
  41. package/dist/diagrams/CallTree.svelte +17 -5
  42. package/dist/diagrams/CallTree.svelte.d.ts +5 -1
  43. package/dist/diagrams/DependencyDiagram.svelte +18 -6
  44. package/dist/diagrams/DependencyDiagram.svelte.d.ts +5 -1
  45. package/dist/diagrams/DependencyMatrix.svelte +11 -32
  46. package/dist/diagrams/ErDiagram.svelte +18 -6
  47. package/dist/diagrams/ErDiagram.svelte.d.ts +5 -1
  48. package/dist/diagrams/LayersDiagram.svelte +124 -0
  49. package/dist/diagrams/LayersDiagram.svelte.d.ts +30 -0
  50. package/dist/diagrams/Neighborhood.svelte +26 -6
  51. package/dist/diagrams/Neighborhood.svelte.d.ts +11 -1
  52. package/dist/diagrams/PolymetricTree.svelte +116 -0
  53. package/dist/diagrams/PolymetricTree.svelte.d.ts +28 -0
  54. package/dist/diagrams/StructureDiagram.svelte +38 -8
  55. package/dist/diagrams/StructureDiagram.svelte.d.ts +9 -1
  56. package/dist/diagrams/Sunburst.svelte +44 -8
  57. package/dist/diagrams/Sunburst.svelte.d.ts +12 -2
  58. package/dist/diagrams/Treemap.svelte +44 -8
  59. package/dist/diagrams/Treemap.svelte.d.ts +12 -2
  60. package/dist/index.d.ts +10 -3
  61. package/dist/index.js +9 -2
  62. package/dist/layout/arcs.d.ts +6 -0
  63. package/dist/layout/arcs.js +141 -0
  64. package/dist/layout/cards.js +9 -2
  65. package/dist/layout/edges.d.ts +13 -1
  66. package/dist/layout/edges.js +26 -2
  67. package/dist/layout/extent.d.ts +17 -0
  68. package/dist/layout/extent.js +27 -0
  69. package/dist/layout/flow.js +3 -1
  70. package/dist/layout/index.d.ts +4 -1
  71. package/dist/layout/index.js +17 -2
  72. package/dist/layout/layers.d.ts +6 -0
  73. package/dist/layout/layers.js +180 -0
  74. package/dist/layout/matrix.d.ts +12 -0
  75. package/dist/layout/matrix.js +20 -0
  76. package/dist/layout/neighborhood.js +16 -7
  77. package/dist/layout/options.d.ts +1 -1
  78. package/dist/layout/options.js +16 -3
  79. package/dist/layout/percentile.d.ts +8 -0
  80. package/dist/layout/percentile.js +13 -0
  81. package/dist/layout/polymetric.d.ts +3 -0
  82. package/dist/layout/polymetric.js +221 -0
  83. package/dist/layout/radial.js +3 -1
  84. package/dist/layout/structure.js +7 -1
  85. package/dist/layout/sunburst.js +11 -4
  86. package/dist/layout/types.d.ts +79 -0
  87. package/dist/layout/world.js +11 -4
  88. package/dist/messages.d.ts +14 -0
  89. package/dist/messages.js +17 -0
  90. package/dist/model/condense.d.ts +8 -0
  91. package/dist/model/condense.js +114 -0
  92. package/dist/model/entities.d.ts +12 -0
  93. package/dist/model/entities.js +12 -0
  94. package/dist/model/normalize.d.ts +2 -6
  95. package/dist/model/normalize.js +64 -2
  96. package/dist/model/relationships.d.ts +30 -0
  97. package/dist/model/relationships.js +51 -0
  98. package/dist/model/share.d.ts +9 -0
  99. package/dist/model/share.js +43 -0
  100. package/dist/model/tree.d.ts +7 -1
  101. package/dist/model/tree.js +11 -4
  102. package/dist/preset.d.ts +18 -0
  103. package/dist/preset.js +34 -3
  104. package/dist/schema/EntitiesView.svelte +8 -6
  105. package/dist/schema/EntityView.svelte +13 -10
  106. package/dist/state/GraphConfig.svelte.d.ts +124 -0
  107. package/dist/state/GraphConfig.svelte.js +253 -0
  108. package/dist/state/GraphDrill.svelte.d.ts +52 -0
  109. package/dist/state/GraphDrill.svelte.js +115 -0
  110. package/dist/state/GraphGroups.svelte.d.ts +35 -0
  111. package/dist/state/GraphGroups.svelte.js +73 -0
  112. package/dist/state/GraphSelection.svelte.d.ts +53 -0
  113. package/dist/state/GraphSelection.svelte.js +94 -0
  114. package/dist/state/intents.d.ts +33 -0
  115. package/dist/state/intents.js +30 -0
  116. package/dist/state/text.d.ts +28 -0
  117. package/dist/state/text.js +56 -0
  118. package/dist/types.d.ts +72 -1
  119. package/package.json +5 -4
@@ -0,0 +1,12 @@
1
+ type Props = {
2
+ channel: 'width' | 'height' | 'color';
3
+ value?: string;
4
+ /** The measures to offer — `GraphState.measureKeys`, what the data has. */
5
+ options: string[];
6
+ /** Offer "none" — a host with two metrics yet can leave shade off. */
7
+ allowNone?: boolean;
8
+ onchange?: (value: string | undefined) => void;
9
+ };
10
+ declare const MeasureControl: import("svelte").Component<Props, {}, "">;
11
+ type MeasureControl = ReturnType<typeof MeasureControl>;
12
+ export default MeasureControl;
@@ -0,0 +1,33 @@
1
+ <script lang="ts">
2
+ /**
3
+ * An on/off control — the shape of every graph toggle (violations, hidden pairs, bundling,
4
+ * edge style). It reports the state the reader asked for; each public control maps that
5
+ * back to its own value and keeps its own `data-*` hook. Internal: not exported.
6
+ */
7
+ import { choices } from '../actions/choices.js'
8
+
9
+ type Props = {
10
+ pressed: boolean
11
+ label: string
12
+ title: string
13
+ /** A glyph beside the label, decorative. */
14
+ glyph?: string
15
+ /** The control's own style hooks, e.g. `{ 'data-graph-bundle': 'bundled' }`. */
16
+ hooks?: Record<string, string>
17
+ onchange?: (pressed: boolean) => void
18
+ }
19
+
20
+ let { pressed, label, title, glyph = undefined, hooks = {}, onchange }: Props = $props()
21
+ </script>
22
+
23
+ <button
24
+ type="button"
25
+ {...hooks}
26
+ aria-pressed={pressed}
27
+ {title}
28
+ data-graph-choice={pressed ? 'off' : 'on'}
29
+ use:choices={{ onchoose: (next) => onchange?.(next === 'on') }}
30
+ >
31
+ {#if glyph}<span data-graph-control-glyph aria-hidden="true">{glyph}</span>{/if}
32
+ <span data-graph-control-label>{label}</span>
33
+ </button>
@@ -0,0 +1,13 @@
1
+ type Props = {
2
+ pressed: boolean;
3
+ label: string;
4
+ title: string;
5
+ /** A glyph beside the label, decorative. */
6
+ glyph?: string;
7
+ /** The control's own style hooks, e.g. `{ 'data-graph-bundle': 'bundled' }`. */
8
+ hooks?: Record<string, string>;
9
+ onchange?: (pressed: boolean) => void;
10
+ };
11
+ declare const ToggleControl: import("svelte").Component<Props, {}, "">;
12
+ type ToggleControl = ReturnType<typeof ToggleControl>;
13
+ export default ToggleControl;
@@ -0,0 +1,27 @@
1
+ <script lang="ts">
2
+ /**
3
+ * Every edge, or only the ones that climb (#167).
4
+ *
5
+ * A toggle: on a real codebase the conformant edges are the vast majority and drown the
6
+ * signal, so "only violations" is the view a reviewer actually reads.
7
+ */
8
+ import ToggleControl from './ToggleControl.svelte'
9
+ import { say } from '../messages.js'
10
+
11
+ type Props = {
12
+ showEdges?: 'all' | 'violations'
13
+ onchange?: (value: 'all' | 'violations') => void
14
+ }
15
+
16
+ let { showEdges = 'all', onchange }: Props = $props()
17
+
18
+ const only = $derived(showEdges === 'violations')
19
+ </script>
20
+
21
+ <ToggleControl
22
+ pressed={only}
23
+ hooks={{ 'data-graph-violations': showEdges }}
24
+ label={say(only ? 'violationsOnly' : 'allEdges')}
25
+ title={say(only ? 'violationsOnlyTitle' : 'allEdgesTitle')}
26
+ onchange={(on) => onchange?.(on ? 'violations' : 'all')}
27
+ />
@@ -0,0 +1,7 @@
1
+ type Props = {
2
+ showEdges?: 'all' | 'violations';
3
+ onchange?: (value: 'all' | 'violations') => void;
4
+ };
5
+ declare const ViolationsControl: import("svelte").Component<Props, {}, "">;
6
+ type ViolationsControl = ReturnType<typeof ViolationsControl>;
7
+ export default ViolationsControl;
@@ -6,7 +6,10 @@
6
6
  * navigable without a trackpad. The canvas handles ctrl+wheel and pinch itself — this is
7
7
  * the discoverable path to the same value, not the only one.
8
8
  */
9
- import { ZOOM_MAX, ZOOM_MIN, ZOOM_STEP, clampZoom } from './zoom.js'
9
+ import { choices } from '../actions/choices.js'
10
+ import { say } from '../messages.js'
11
+ import { ZOOM_MAX, ZOOM_MIN, nextZoom } from './zoom.js'
12
+ import type { ZoomMove } from './zoom.js'
10
13
 
11
14
  type Props = {
12
15
  zoom?: number
@@ -16,32 +19,32 @@
16
19
  let { zoom = 1, onchange }: Props = $props()
17
20
 
18
21
  const percent = $derived(Math.round(zoom * 100))
19
-
20
- // Clamped here as well as in the canvas: the buttons disable at the ends, but a caller can
21
- // hand this control a value from anywhere, and reporting one outside the range would push
22
- // the canvas past a limit it has already agreed to.
23
- const step = (factor: number) => onchange?.(clampZoom(zoom * factor))
24
22
  </script>
25
23
 
26
- <div data-graph-zoom-controls role="group" aria-label="Zoom">
24
+ <!-- Clamped by `nextZoom` as well as in the canvas: the buttons disable at the ends, but a
25
+ caller can hand this control a value from anywhere, and reporting one outside the range
26
+ would push the canvas past a limit it has already agreed to. -->
27
+ <div
28
+ data-graph-zoom-controls
29
+ role="group"
30
+ aria-label={say('zoom')}
31
+ use:choices={{ onchoose: (move) => onchange?.(nextZoom(zoom, move as ZoomMove)) }}
32
+ >
27
33
  <button
28
34
  type="button"
29
35
  data-graph-zoom="out"
30
- aria-label="Zoom out"
31
- disabled={zoom <= ZOOM_MIN}
32
- onclick={() => step(1 / ZOOM_STEP)}>−</button
36
+ data-graph-choice="out"
37
+ aria-label={say('zoomOut')}
38
+ disabled={zoom <= ZOOM_MIN}>−</button
33
39
  >
34
- <button
35
- type="button"
36
- data-graph-zoom="reset"
37
- aria-label="Reset zoom to fit"
38
- onclick={() => onchange?.(1)}>{percent}%</button
40
+ <button type="button" data-graph-zoom="reset" data-graph-choice="reset" aria-label={say('zoomReset')}
41
+ >{percent}%</button
39
42
  >
40
43
  <button
41
44
  type="button"
42
45
  data-graph-zoom="in"
43
- aria-label="Zoom in"
44
- disabled={zoom >= ZOOM_MAX}
45
- onclick={() => step(ZOOM_STEP)}>+</button
46
+ data-graph-choice="in"
47
+ aria-label={say('zoomIn')}
48
+ disabled={zoom >= ZOOM_MAX}>+</button
46
49
  >
47
50
  </div>
@@ -4,3 +4,6 @@ export declare const ZOOM_MAX = 4;
4
4
  export declare const ZOOM_STEP = 1.25;
5
5
  /** `value` brought inside the range. */
6
6
  export declare const clampZoom: (value: number) => number;
7
+ export type ZoomMove = 'in' | 'out' | 'reset';
8
+ /** The zoom after one move: a notch in or out, kept inside the range, or back to fit. */
9
+ export declare function nextZoom(zoom: number, move: ZoomMove): number;
@@ -10,3 +10,9 @@ export const ZOOM_MAX = 4;
10
10
  export const ZOOM_STEP = 1.25;
11
11
  /** `value` brought inside the range. */
12
12
  export const clampZoom = (value) => Math.min(ZOOM_MAX, Math.max(ZOOM_MIN, value));
13
+ /** The zoom after one move: a notch in or out, kept inside the range, or back to fit. */
14
+ export function nextZoom(zoom, move) {
15
+ if (move === 'reset')
16
+ return 1;
17
+ return clampZoom(zoom * (move === 'in' ? ZOOM_STEP : 1 / ZOOM_STEP));
18
+ }
@@ -0,0 +1,116 @@
1
+ <script lang="ts">
2
+ /**
3
+ * Two relations over one shared, ordered axis (#169): the coupling the code declares on the
4
+ * left, the coupling the history reveals on the right.
5
+ *
6
+ * The finding is a pair with an arc on one side and none on the other — files that change
7
+ * together with nothing in the code tying them. The host flags those `hidden` (rokkit
8
+ * computes no history); `HiddenControl` shows only them. The legend is on by default: which
9
+ * side is which cannot be read off the arcs.
10
+ */
11
+ import Graph from '../Graph.svelte'
12
+ import GraphLegend from '../GraphLegend.svelte'
13
+ import DiagramFrame from './DiagramFrame.svelte'
14
+ import DrillBar from '../controls/DrillBar.svelte'
15
+ import HiddenControl from '../controls/HiddenControl.svelte'
16
+ import ZoomControl from '../controls/ZoomControl.svelte'
17
+ import { GraphState } from '../GraphState.svelte.js'
18
+ import type { GraphFields } from '../types.js'
19
+ import type { GraphPreset } from '../preset.js'
20
+
21
+ type Props = {
22
+ /** Share one state with another view — see `DependencyDiagram`. */
23
+ state?: GraphState
24
+ nodes?: unknown[]
25
+ edges?: unknown[]
26
+ fields?: GraphFields
27
+ /** The relation drawn on the right. Omitted, overlays go right and the rest left. */
28
+ above?: string
29
+ /** Every pair, or only the hidden ones. Bindable — the control moves it. */
30
+ showEdges?: 'all' | 'hidden'
31
+ /**
32
+ * The two sides' names in the legend, left then right. Omitted, they come from the data:
33
+ * the relations drawn on each side, else Declared / Observed from the locale.
34
+ */
35
+ sideLabels?: [string, string]
36
+ controls?: boolean
37
+ /** On by default: the two sides are unreadable without a key. */
38
+ legend?: boolean
39
+ value?: string | null
40
+ preset?: GraphPreset
41
+ mode?: 'light' | 'dark'
42
+ label?: string
43
+ onselect?: (id: string | null) => void
44
+ class?: string
45
+ }
46
+
47
+ let {
48
+ state: provided,
49
+ nodes = [],
50
+ edges = [],
51
+ fields = {},
52
+ above = undefined,
53
+ showEdges = $bindable('all'),
54
+ sideLabels = undefined,
55
+ controls = false,
56
+ legend = true,
57
+ value = $bindable(undefined),
58
+ preset = undefined,
59
+ mode = 'light',
60
+ label = undefined,
61
+ onselect = undefined,
62
+ class: className = ''
63
+ }: Props = $props()
64
+
65
+ let zoom = $state(1)
66
+
67
+ const config = () => ({
68
+ nodes,
69
+ edges,
70
+ fields,
71
+ layout: 'arcs',
72
+ above,
73
+ showEdges,
74
+ value,
75
+ preset,
76
+ mode,
77
+ label,
78
+ onselect
79
+ })
80
+
81
+ // svelte-ignore state_referenced_locally
82
+ const graph = provided ?? new GraphState(config())
83
+
84
+ $effect(() => {
85
+ if (!provided) {
86
+ graph.update(config())
87
+ return
88
+ }
89
+ // A supplied state is the caller's — see DependencyDiagram.
90
+ const { nodes: _n, edges: _e, fields: _f, preset: _p, mode: _m, value: _v, ...mine } =
91
+ config()
92
+ graph.apply(mine)
93
+ })
94
+ </script>
95
+
96
+ {#snippet controlBar()}
97
+ <DrillBar state={graph} />
98
+ {#if controls}
99
+ <HiddenControl {showEdges} onchange={(v) => (showEdges = v)} />
100
+ <ZoomControl {zoom} onchange={(v) => (zoom = v)} />
101
+ {/if}
102
+ {/snippet}
103
+
104
+ {#snippet legendBar()}
105
+ <GraphLegend state={graph} sides={sideLabels ?? true} hidden />
106
+ {/snippet}
107
+
108
+ <DiagramFrame
109
+ class={className}
110
+ overlay={controls || graph.showsDrillBar ? controlBar : undefined}
111
+ footer={legend ? legendBar : undefined}
112
+ >
113
+ {#snippet canvas()}
114
+ <Graph state={graph} bind:zoom arrows={false} />
115
+ {/snippet}
116
+ </DiagramFrame>
@@ -0,0 +1,31 @@
1
+ import { GraphState } from '../GraphState.svelte.js';
2
+ import type { GraphFields } from '../types.js';
3
+ import type { GraphPreset } from '../preset.js';
4
+ type Props = {
5
+ /** Share one state with another view — see `DependencyDiagram`. */
6
+ state?: GraphState;
7
+ nodes?: unknown[];
8
+ edges?: unknown[];
9
+ fields?: GraphFields;
10
+ /** The relation drawn on the right. Omitted, overlays go right and the rest left. */
11
+ above?: string;
12
+ /** Every pair, or only the hidden ones. Bindable — the control moves it. */
13
+ showEdges?: 'all' | 'hidden';
14
+ /**
15
+ * The two sides' names in the legend, left then right. Omitted, they come from the data:
16
+ * the relations drawn on each side, else Declared / Observed from the locale.
17
+ */
18
+ sideLabels?: [string, string];
19
+ controls?: boolean;
20
+ /** On by default: the two sides are unreadable without a key. */
21
+ legend?: boolean;
22
+ value?: string | null;
23
+ preset?: GraphPreset;
24
+ mode?: 'light' | 'dark';
25
+ label?: string;
26
+ onselect?: (id: string | null) => void;
27
+ class?: string;
28
+ };
29
+ declare const ArcDiagram: import("svelte").Component<Props, {}, "value" | "showEdges">;
30
+ type ArcDiagram = ReturnType<typeof ArcDiagram>;
31
+ export default ArcDiagram;
@@ -14,9 +14,10 @@
14
14
  import GraphLegend from '../GraphLegend.svelte'
15
15
  import DiagramFrame from './DiagramFrame.svelte'
16
16
  import DepthControl from '../controls/DepthControl.svelte'
17
+ import DrillBar from '../controls/DrillBar.svelte'
17
18
  import ZoomControl from '../controls/ZoomControl.svelte'
18
19
  import { GraphState } from '../GraphState.svelte.js'
19
- import type { GraphFields } from '../types.js'
20
+ import type { GraphFields, GraphNode } from '../types.js'
20
21
  import type { GraphPreset } from '../preset.js'
21
22
 
22
23
  type Props = {
@@ -48,6 +49,10 @@
48
49
  arrows?: boolean
49
50
  label?: string
50
51
  onselect?: (id: string | null) => void
52
+ /** The reader expanded a group node (#166) — with its members in `nodes`, they appear at once. */
53
+ onexpand?: (id: string, node: GraphNode) => void
54
+ /** The reader collapsed a group back into one node. */
55
+ oncollapse?: (id: string, node: GraphNode) => void
51
56
  class?: string
52
57
  }
53
58
 
@@ -70,6 +75,8 @@
70
75
  arrows = true,
71
76
  label = undefined,
72
77
  onselect = undefined,
78
+ onexpand = undefined,
79
+ oncollapse = undefined,
73
80
  class: className = ''
74
81
  }: Props = $props()
75
82
 
@@ -89,7 +96,9 @@
89
96
  preset,
90
97
  mode,
91
98
  label,
92
- onselect
99
+ onselect,
100
+ onexpand,
101
+ oncollapse
93
102
  })
94
103
 
95
104
  // svelte-ignore state_referenced_locally
@@ -116,8 +125,11 @@
116
125
  inside is always defined, so the frame would render an empty control bar over
117
126
  every diagram that asked for none. -->
118
127
  {#snippet controlBar()}
119
- <DepthControl levels={levels ?? maxLevels} max={maxLevels} onchange={(v) => (levels = v)} />
120
- <ZoomControl {zoom} onchange={(v) => (zoom = v)} />
128
+ <DrillBar state={graph} />
129
+ {#if controls}
130
+ <DepthControl levels={levels ?? maxLevels} max={maxLevels} onchange={(v) => (levels = v)} />
131
+ <ZoomControl {zoom} onchange={(v) => (zoom = v)} />
132
+ {/if}
121
133
  {/snippet}
122
134
 
123
135
  {#snippet legendBar()}
@@ -126,7 +138,7 @@
126
138
 
127
139
  <DiagramFrame
128
140
  class={className}
129
- overlay={controls ? controlBar : undefined}
141
+ overlay={controls || graph.showsDrillBar ? controlBar : undefined}
130
142
  footer={legend ? legendBar : undefined}
131
143
  >
132
144
  {#snippet canvas()}
@@ -1,5 +1,5 @@
1
1
  import { GraphState } from '../GraphState.svelte.js';
2
- import type { GraphFields } from '../types.js';
2
+ import type { GraphFields, GraphNode } from '../types.js';
3
3
  import type { GraphPreset } from '../preset.js';
4
4
  type Props = {
5
5
  /**
@@ -30,6 +30,10 @@ type Props = {
30
30
  arrows?: boolean;
31
31
  label?: string;
32
32
  onselect?: (id: string | null) => void;
33
+ /** The reader expanded a group node (#166) — with its members in `nodes`, they appear at once. */
34
+ onexpand?: (id: string, node: GraphNode) => void;
35
+ /** The reader collapsed a group back into one node. */
36
+ oncollapse?: (id: string, node: GraphNode) => void;
33
37
  class?: string;
34
38
  };
35
39
  declare const CallTree: import("svelte").Component<Props, {}, "value" | "levels" | "root">;
@@ -13,11 +13,12 @@
13
13
  import GraphLegend from '../GraphLegend.svelte'
14
14
  import DiagramFrame from './DiagramFrame.svelte'
15
15
  import DensityControl from '../controls/DensityControl.svelte'
16
+ import DrillBar from '../controls/DrillBar.svelte'
16
17
  import EdgeStyleControl from '../controls/EdgeStyleControl.svelte'
17
18
  import ZoomControl from '../controls/ZoomControl.svelte'
18
19
  import { GraphState } from '../GraphState.svelte.js'
19
20
  import type { Density, EdgeStyle } from '../layout/types.js'
20
- import type { GraphFields } from '../types.js'
21
+ import type { GraphFields, GraphNode } from '../types.js'
21
22
  import type { GraphPreset } from '../preset.js'
22
23
 
23
24
  type Props = {
@@ -41,6 +42,10 @@
41
42
  arrows?: boolean
42
43
  label?: string
43
44
  onselect?: (id: string | null) => void
45
+ /** The reader expanded a group node (#166) — with its members in `nodes`, they appear at once. */
46
+ onexpand?: (id: string, node: GraphNode) => void
47
+ /** The reader collapsed a group back into one node. */
48
+ oncollapse?: (id: string, node: GraphNode) => void
44
49
  class?: string
45
50
  }
46
51
 
@@ -60,6 +65,8 @@
60
65
  arrows = true,
61
66
  label = undefined,
62
67
  onselect = undefined,
68
+ onexpand = undefined,
69
+ oncollapse = undefined,
63
70
  class: className = ''
64
71
  }: Props = $props()
65
72
 
@@ -77,7 +84,9 @@
77
84
  preset,
78
85
  mode,
79
86
  label,
80
- onselect
87
+ onselect,
88
+ onexpand,
89
+ oncollapse
81
90
  })
82
91
 
83
92
  // svelte-ignore state_referenced_locally
@@ -104,9 +113,12 @@
104
113
  inside is always defined, so the frame would render an empty control bar over
105
114
  every diagram that asked for none. -->
106
115
  {#snippet controlBar()}
107
- <DensityControl {density} onchange={(v) => (density = v)} />
108
- <EdgeStyleControl {edgeStyle} onchange={(v) => (edgeStyle = v)} />
109
- <ZoomControl {zoom} onchange={(v) => (zoom = v)} />
116
+ <DrillBar state={graph} />
117
+ {#if controls}
118
+ <DensityControl {density} onchange={(v) => (density = v)} />
119
+ <EdgeStyleControl {edgeStyle} onchange={(v) => (edgeStyle = v)} />
120
+ <ZoomControl {zoom} onchange={(v) => (zoom = v)} />
121
+ {/if}
110
122
  {/snippet}
111
123
 
112
124
  {#snippet legendBar()}
@@ -115,7 +127,7 @@
115
127
 
116
128
  <DiagramFrame
117
129
  class={className}
118
- overlay={controls ? controlBar : undefined}
130
+ overlay={controls || graph.showsDrillBar ? controlBar : undefined}
119
131
  footer={legend ? legendBar : undefined}
120
132
  >
121
133
  {#snippet canvas()}
@@ -1,6 +1,6 @@
1
1
  import { GraphState } from '../GraphState.svelte.js';
2
2
  import type { Density, EdgeStyle } from '../layout/types.js';
3
- import type { GraphFields } from '../types.js';
3
+ import type { GraphFields, GraphNode } from '../types.js';
4
4
  import type { GraphPreset } from '../preset.js';
5
5
  type Props = {
6
6
  /**
@@ -23,6 +23,10 @@ type Props = {
23
23
  arrows?: boolean;
24
24
  label?: string;
25
25
  onselect?: (id: string | null) => void;
26
+ /** The reader expanded a group node (#166) — with its members in `nodes`, they appear at once. */
27
+ onexpand?: (id: string, node: GraphNode) => void;
28
+ /** The reader collapsed a group back into one node. */
29
+ oncollapse?: (id: string, node: GraphNode) => void;
26
30
  class?: string;
27
31
  };
28
32
  declare const DependencyDiagram: import("svelte").Component<Props, {}, "value" | "density" | "edgeStyle">;
@@ -13,7 +13,8 @@
13
13
  * node in both.
14
14
  */
15
15
  import { GraphState } from '../GraphState.svelte.js'
16
- import { buildMatrix } from '../layout/matrix.js'
16
+ import { buildMatrix, cellState, matrixFrame, matrixLabel } from '../layout/matrix.js'
17
+ import { interactions } from '../actions/interactions.js'
17
18
  import type { NodeAxis } from '../layout/types.js'
18
19
  import type { GraphFields } from '../types.js'
19
20
 
@@ -66,37 +67,15 @@
66
67
 
67
68
  const matrix = $derived(buildMatrix(graph.model, { groupBy: groupBy ?? undefined }))
68
69
 
69
- // Labels are measured by length, not by the DOM — deterministic, like every layout here.
70
- const labelW = $derived(
71
- Math.min(220, Math.max(48, Math.max(0, ...matrix.labels.map((l) => l.length)) * 6.5 + 14))
72
- )
73
- const n = $derived(matrix.order.length)
74
- const width = $derived(labelW + n * cell + 8)
75
- const height = $derived(labelW + n * cell + 8)
70
+ const frame = $derived(matrixFrame(matrix, cell))
71
+ const labelW = $derived(frame.labelW)
72
+ const n = $derived(frame.n)
76
73
  const selected = $derived(graph.value)
77
-
78
- const name = $derived(
79
- label ??
80
- `Dependency matrix: ${n} nodes, ${matrix.cells.length} dependencies, ${matrix.above} above the diagonal`
81
- )
82
-
83
- function toggle(id: string) {
84
- if (selected === id) graph.clear()
85
- else graph.select(id)
86
- }
87
-
88
- function onKey(event: KeyboardEvent, id: string) {
89
- if (event.key !== 'Enter' && event.key !== ' ') return
90
- event.preventDefault()
91
- toggle(id)
92
- }
93
-
94
- const cellState = (source: string, target: string) =>
95
- selected && (source === selected || target === selected) ? 'highlight' : undefined
74
+ const name = $derived(label ?? matrixLabel(n, matrix.cells.length, matrix.above))
96
75
  </script>
97
76
 
98
- <div data-graph-matrix class={className} role="group" aria-label={name}>
99
- <svg {width} {height} viewBox="0 0 {width} {height}">
77
+ <div data-graph-matrix class={className} role="group" aria-label={name} use:interactions={{ state: graph }}>
78
+ <svg width={frame.width} height={frame.height} viewBox="0 0 {frame.width} {frame.height}">
100
79
  <!-- Column headings, rotated so a long name costs height rather than width. -->
101
80
  {#each matrix.labels as text, i (matrix.order[i])}
102
81
  <text
@@ -116,8 +95,8 @@
116
95
  tabindex="0"
117
96
  aria-pressed={selected === id}
118
97
  aria-label={text}
119
- onclick={() => toggle(id)}
120
- onkeydown={(event) => onKey(event, id)}
98
+ data-graph-press="toggle"
99
+ data-graph-key={id}
121
100
  >
122
101
  <rect
123
102
  x="0"
@@ -151,7 +130,7 @@
151
130
  data-matrix-from={c.source}
152
131
  data-matrix-to={c.target}
153
132
  data-matrix-above={c.above ? '' : undefined}
154
- data-matrix-state={cellState(c.source, c.target)}
133
+ data-matrix-state={cellState(selected, c.source, c.target)}
155
134
  style:--cell-weight={c.count / matrix.maxCount}
156
135
  >
157
136
  <rect
@@ -14,11 +14,12 @@
14
14
  import GraphLegend from '../GraphLegend.svelte'
15
15
  import DiagramFrame from './DiagramFrame.svelte'
16
16
  import DensityControl from '../controls/DensityControl.svelte'
17
+ import DrillBar from '../controls/DrillBar.svelte'
17
18
  import EdgeStyleControl from '../controls/EdgeStyleControl.svelte'
18
19
  import ZoomControl from '../controls/ZoomControl.svelte'
19
20
  import { GraphState } from '../GraphState.svelte.js'
20
21
  import type { Density, EdgeStyle } from '../layout/types.js'
21
- import type { GraphFields } from '../types.js'
22
+ import type { GraphFields, GraphNode } from '../types.js'
22
23
  import type { GraphPreset } from '../preset.js'
23
24
 
24
25
  type Props = {
@@ -45,6 +46,10 @@
45
46
  arrows?: boolean
46
47
  label?: string
47
48
  onselect?: (id: string | null) => void
49
+ /** The reader expanded a group node (#166) — with its members in `nodes`, they appear at once. */
50
+ onexpand?: (id: string, node: GraphNode) => void
51
+ /** The reader collapsed a group back into one node. */
52
+ oncollapse?: (id: string, node: GraphNode) => void
48
53
  class?: string
49
54
  }
50
55
 
@@ -64,6 +69,8 @@
64
69
  arrows = true,
65
70
  label = undefined,
66
71
  onselect = undefined,
72
+ onexpand = undefined,
73
+ oncollapse = undefined,
67
74
  class: className = ''
68
75
  }: Props = $props()
69
76
 
@@ -81,7 +88,9 @@
81
88
  preset,
82
89
  mode,
83
90
  label,
84
- onselect
91
+ onselect,
92
+ onexpand,
93
+ oncollapse
85
94
  })
86
95
 
87
96
  // svelte-ignore state_referenced_locally
@@ -108,9 +117,12 @@
108
117
  inside is always defined, so the frame would render an empty control bar over
109
118
  every diagram that asked for none. -->
110
119
  {#snippet controlBar()}
111
- <DensityControl {density} onchange={(v) => (density = v)} />
112
- <EdgeStyleControl {edgeStyle} onchange={(v) => (edgeStyle = v)} />
113
- <ZoomControl {zoom} onchange={(v) => (zoom = v)} />
120
+ <DrillBar state={graph} />
121
+ {#if controls}
122
+ <DensityControl {density} onchange={(v) => (density = v)} />
123
+ <EdgeStyleControl {edgeStyle} onchange={(v) => (edgeStyle = v)} />
124
+ <ZoomControl {zoom} onchange={(v) => (zoom = v)} />
125
+ {/if}
114
126
  {/snippet}
115
127
 
116
128
  {#snippet legendBar()}
@@ -119,7 +131,7 @@
119
131
 
120
132
  <DiagramFrame
121
133
  class={className}
122
- overlay={controls ? controlBar : undefined}
134
+ overlay={controls || graph.showsDrillBar ? controlBar : undefined}
123
135
  footer={legend ? legendBar : undefined}
124
136
  >
125
137
  {#snippet canvas()}
@@ -1,6 +1,6 @@
1
1
  import { GraphState } from '../GraphState.svelte.js';
2
2
  import type { Density, EdgeStyle } from '../layout/types.js';
3
- import type { GraphFields } from '../types.js';
3
+ import type { GraphFields, GraphNode } from '../types.js';
4
4
  import type { GraphPreset } from '../preset.js';
5
5
  type Props = {
6
6
  /**
@@ -26,6 +26,10 @@ type Props = {
26
26
  arrows?: boolean;
27
27
  label?: string;
28
28
  onselect?: (id: string | null) => void;
29
+ /** The reader expanded a group node (#166) — with its members in `nodes`, they appear at once. */
30
+ onexpand?: (id: string, node: GraphNode) => void;
31
+ /** The reader collapsed a group back into one node. */
32
+ oncollapse?: (id: string, node: GraphNode) => void;
29
33
  class?: string;
30
34
  };
31
35
  declare const ErDiagram: import("svelte").Component<Props, {}, "value" | "density" | "edgeStyle">;