@rokkit/graph 1.7.0 → 1.8.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 (120) hide show
  1. package/README.md +67 -1
  2. package/dist/Graph.svelte +234 -259
  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 +168 -51
  7. package/dist/GraphState.svelte.js +406 -305
  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 +156 -0
  46. package/dist/diagrams/DependencyMatrix.svelte.d.ts +34 -0
  47. package/dist/diagrams/ErDiagram.svelte +18 -6
  48. package/dist/diagrams/ErDiagram.svelte.d.ts +5 -1
  49. package/dist/diagrams/LayersDiagram.svelte +124 -0
  50. package/dist/diagrams/LayersDiagram.svelte.d.ts +30 -0
  51. package/dist/diagrams/Neighborhood.svelte +26 -6
  52. package/dist/diagrams/Neighborhood.svelte.d.ts +11 -1
  53. package/dist/diagrams/PolymetricTree.svelte +116 -0
  54. package/dist/diagrams/PolymetricTree.svelte.d.ts +28 -0
  55. package/dist/diagrams/StructureDiagram.svelte +38 -8
  56. package/dist/diagrams/StructureDiagram.svelte.d.ts +9 -1
  57. package/dist/diagrams/Sunburst.svelte +44 -8
  58. package/dist/diagrams/Sunburst.svelte.d.ts +12 -2
  59. package/dist/diagrams/Treemap.svelte +44 -8
  60. package/dist/diagrams/Treemap.svelte.d.ts +12 -2
  61. package/dist/index.d.ts +13 -3
  62. package/dist/index.js +11 -2
  63. package/dist/layout/arcs.d.ts +6 -0
  64. package/dist/layout/arcs.js +141 -0
  65. package/dist/layout/cards.js +9 -2
  66. package/dist/layout/edges.d.ts +13 -1
  67. package/dist/layout/edges.js +35 -0
  68. package/dist/layout/extent.d.ts +17 -0
  69. package/dist/layout/extent.js +27 -0
  70. package/dist/layout/flow.js +3 -1
  71. package/dist/layout/index.d.ts +4 -1
  72. package/dist/layout/index.js +17 -2
  73. package/dist/layout/layers.d.ts +6 -0
  74. package/dist/layout/layers.js +180 -0
  75. package/dist/layout/matrix.d.ts +50 -0
  76. package/dist/layout/matrix.js +110 -0
  77. package/dist/layout/neighborhood.js +16 -7
  78. package/dist/layout/options.d.ts +1 -1
  79. package/dist/layout/options.js +16 -3
  80. package/dist/layout/percentile.d.ts +8 -0
  81. package/dist/layout/percentile.js +13 -0
  82. package/dist/layout/polymetric.d.ts +3 -0
  83. package/dist/layout/polymetric.js +221 -0
  84. package/dist/layout/radial.js +3 -1
  85. package/dist/layout/structure.js +7 -1
  86. package/dist/layout/sunburst.js +11 -4
  87. package/dist/layout/types.d.ts +83 -0
  88. package/dist/layout/world.js +11 -4
  89. package/dist/messages.d.ts +14 -0
  90. package/dist/messages.js +17 -0
  91. package/dist/model/condense.d.ts +8 -0
  92. package/dist/model/condense.js +114 -0
  93. package/dist/model/entities.d.ts +12 -0
  94. package/dist/model/entities.js +12 -0
  95. package/dist/model/normalize.d.ts +2 -6
  96. package/dist/model/normalize.js +83 -5
  97. package/dist/model/relationships.d.ts +30 -0
  98. package/dist/model/relationships.js +51 -0
  99. package/dist/model/share.d.ts +9 -0
  100. package/dist/model/share.js +43 -0
  101. package/dist/model/tree.d.ts +7 -1
  102. package/dist/model/tree.js +11 -4
  103. package/dist/preset.d.ts +18 -0
  104. package/dist/preset.js +34 -3
  105. package/dist/schema/EntitiesView.svelte +8 -6
  106. package/dist/schema/EntityView.svelte +13 -10
  107. package/dist/state/GraphConfig.svelte.d.ts +124 -0
  108. package/dist/state/GraphConfig.svelte.js +253 -0
  109. package/dist/state/GraphDrill.svelte.d.ts +52 -0
  110. package/dist/state/GraphDrill.svelte.js +115 -0
  111. package/dist/state/GraphGroups.svelte.d.ts +35 -0
  112. package/dist/state/GraphGroups.svelte.js +73 -0
  113. package/dist/state/GraphSelection.svelte.d.ts +53 -0
  114. package/dist/state/GraphSelection.svelte.js +94 -0
  115. package/dist/state/intents.d.ts +33 -0
  116. package/dist/state/intents.js +30 -0
  117. package/dist/state/text.d.ts +28 -0
  118. package/dist/state/text.js +56 -0
  119. package/dist/types.d.ts +92 -1
  120. package/package.json +5 -4
@@ -13,9 +13,11 @@
13
13
  import GraphLegend from '../GraphLegend.svelte'
14
14
  import DiagramFrame from './DiagramFrame.svelte'
15
15
  import DepthControl from '../controls/DepthControl.svelte'
16
+ import DrillBar from '../controls/DrillBar.svelte'
17
+ import MeasureControl from '../controls/MeasureControl.svelte'
16
18
  import ZoomControl from '../controls/ZoomControl.svelte'
17
19
  import { GraphState } from '../GraphState.svelte.js'
18
- import type { GraphFields } from '../types.js'
20
+ import type { GraphFields, GraphNode } from '../types.js'
19
21
  import type { GraphPreset } from '../preset.js'
20
22
 
21
23
  type Props = {
@@ -30,6 +32,8 @@
30
32
  fields?: GraphFields
31
33
  /** What area encodes. A `measures` key, or `weight`, or `degree`. */
32
34
  sizeBy?: string
35
+ /** The measure — a 0..1 share — each box is shaded by (#164). Bindable; the picker moves it. */
36
+ shadeBy?: string
33
37
  /** How many levels below the focus to materialise. */
34
38
  levels?: number
35
39
  /** The subtree to render as the whole canvas. `[]` is the root. Drilling, not zooming. */
@@ -42,6 +46,14 @@
42
46
  mode?: 'light' | 'dark'
43
47
  label?: string
44
48
  onselect?: (id: string | null) => void
49
+ /**
50
+ * The reader opened a box — `path` is the new `focusPath`, `node` the declared node there
51
+ * (null for a synthesised container). Return a promise while you load that level; the
52
+ * canvas shows it pending, and a rejection returns to the previous level.
53
+ */
54
+ ondrill?: (path: string[], node: GraphNode | null) => void | Promise<void>
55
+ /** The reader climbed out; `path` is the new, shorter `focusPath`. */
56
+ ondrillup?: (path: string[]) => void | Promise<void>
45
57
  class?: string
46
58
  }
47
59
 
@@ -51,6 +63,7 @@
51
63
  edges = [],
52
64
  fields = {},
53
65
  sizeBy = 'weight',
66
+ shadeBy = $bindable(undefined),
54
67
  levels = $bindable(2),
55
68
  focusPath = $bindable([]),
56
69
  controls = false,
@@ -61,6 +74,8 @@
61
74
  mode = 'light',
62
75
  label = undefined,
63
76
  onselect = undefined,
77
+ ondrill = undefined,
78
+ ondrillup = undefined,
64
79
  class: className = ''
65
80
  }: Props = $props()
66
81
 
@@ -72,13 +87,19 @@
72
87
  fields,
73
88
  layout: 'sunburst',
74
89
  sizeBy,
90
+ shadeBy,
75
91
  levels,
76
92
  focusPath,
77
93
  value,
78
94
  preset,
79
95
  mode,
80
96
  label,
81
- onselect
97
+ onselect,
98
+ ondrill,
99
+ ondrillup,
100
+ // Drilling moves focusPath inside the state; keep the bindable prop in step, or the next
101
+ // update() would put the old value back.
102
+ onfocuspath: (path: string[]) => (focusPath = path)
82
103
  })
83
104
 
84
105
  // svelte-ignore state_referenced_locally
@@ -95,8 +116,19 @@
95
116
  // and are never reset here. What this component DOES own is the options its own
96
117
  // controls drive — the layout above all, since choosing this component is choosing it.
97
118
  // `apply` merges; `update` would revert everything it does not name.
98
- const { nodes: _n, edges: _e, fields: _f, preset: _p, mode: _m, value: _v, ...mine } =
99
- config()
119
+ // The drill path too: a caller's state holds where the reader drilled, and pushing this
120
+ // component's own focusPath prop back over it undid the drill on any prop change.
121
+ const {
122
+ nodes: _n,
123
+ edges: _e,
124
+ fields: _f,
125
+ preset: _p,
126
+ mode: _m,
127
+ value: _v,
128
+ focusPath: _fp,
129
+ onfocuspath: _sync,
130
+ ...mine
131
+ } = config()
100
132
  graph.apply(mine)
101
133
  })
102
134
  </script>
@@ -105,17 +137,21 @@
105
137
  inside is always defined, so the frame would render an empty control bar over
106
138
  every diagram that asked for none. -->
107
139
  {#snippet controlBar()}
108
- <DepthControl {levels} max={maxLevels} onchange={(v) => (levels = v)} />
109
- <ZoomControl {zoom} onchange={(v) => (zoom = v)} />
140
+ <DrillBar state={graph} />
141
+ {#if controls}
142
+ <DepthControl {levels} max={maxLevels} onchange={(v) => (levels = v)} />
143
+ <MeasureControl channel="color" value={shadeBy} options={graph.measureKeys} allowNone onchange={(v) => (shadeBy = v)} />
144
+ <ZoomControl {zoom} onchange={(v) => (zoom = v)} />
145
+ {/if}
110
146
  {/snippet}
111
147
 
112
148
  {#snippet legendBar()}
113
- <GraphLegend state={graph} groups />
149
+ <GraphLegend state={graph} groups shade />
114
150
  {/snippet}
115
151
 
116
152
  <DiagramFrame
117
153
  class={className}
118
- overlay={controls ? controlBar : undefined}
154
+ overlay={controls || graph.showsDrillBar ? controlBar : undefined}
119
155
  footer={legend ? legendBar : undefined}
120
156
  >
121
157
  {#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
  /**
@@ -13,6 +13,8 @@ type Props = {
13
13
  fields?: GraphFields;
14
14
  /** What area encodes. A `measures` key, or `weight`, or `degree`. */
15
15
  sizeBy?: string;
16
+ /** The measure — a 0..1 share — each box is shaded by (#164). Bindable; the picker moves it. */
17
+ shadeBy?: string;
16
18
  /** How many levels below the focus to materialise. */
17
19
  levels?: number;
18
20
  /** The subtree to render as the whole canvas. `[]` is the root. Drilling, not zooming. */
@@ -25,8 +27,16 @@ type Props = {
25
27
  mode?: 'light' | 'dark';
26
28
  label?: string;
27
29
  onselect?: (id: string | null) => void;
30
+ /**
31
+ * The reader opened a box — `path` is the new `focusPath`, `node` the declared node there
32
+ * (null for a synthesised container). Return a promise while you load that level; the
33
+ * canvas shows it pending, and a rejection returns to the previous level.
34
+ */
35
+ ondrill?: (path: string[], node: GraphNode | null) => void | Promise<void>;
36
+ /** The reader climbed out; `path` is the new, shorter `focusPath`. */
37
+ ondrillup?: (path: string[]) => void | Promise<void>;
28
38
  class?: string;
29
39
  };
30
- declare const Sunburst: import("svelte").Component<Props, {}, "value" | "focusPath" | "levels">;
40
+ declare const Sunburst: import("svelte").Component<Props, {}, "value" | "focusPath" | "levels" | "shadeBy">;
31
41
  type Sunburst = ReturnType<typeof Sunburst>;
32
42
  export default Sunburst;
@@ -13,9 +13,11 @@
13
13
  import GraphLegend from '../GraphLegend.svelte'
14
14
  import DiagramFrame from './DiagramFrame.svelte'
15
15
  import DepthControl from '../controls/DepthControl.svelte'
16
+ import DrillBar from '../controls/DrillBar.svelte'
17
+ import MeasureControl from '../controls/MeasureControl.svelte'
16
18
  import ZoomControl from '../controls/ZoomControl.svelte'
17
19
  import { GraphState } from '../GraphState.svelte.js'
18
- import type { GraphFields } from '../types.js'
20
+ import type { GraphFields, GraphNode } from '../types.js'
19
21
  import type { GraphPreset } from '../preset.js'
20
22
 
21
23
  type Props = {
@@ -30,6 +32,8 @@
30
32
  fields?: GraphFields
31
33
  /** What area encodes. A `measures` key, or `weight`, or `degree`. */
32
34
  sizeBy?: string
35
+ /** The measure — a 0..1 share — each box is shaded by (#164). Bindable; the picker moves it. */
36
+ shadeBy?: string
33
37
  /** How many levels below the focus to materialise. */
34
38
  levels?: number
35
39
  /** The subtree to render as the whole canvas. `[]` is the root. Drilling, not zooming. */
@@ -42,6 +46,14 @@
42
46
  mode?: 'light' | 'dark'
43
47
  label?: string
44
48
  onselect?: (id: string | null) => void
49
+ /**
50
+ * The reader opened a box — `path` is the new `focusPath`, `node` the declared node there
51
+ * (null for a synthesised container). Return a promise while you load that level; the
52
+ * canvas shows it pending, and a rejection returns to the previous level.
53
+ */
54
+ ondrill?: (path: string[], node: GraphNode | null) => void | Promise<void>
55
+ /** The reader climbed out; `path` is the new, shorter `focusPath`. */
56
+ ondrillup?: (path: string[]) => void | Promise<void>
45
57
  class?: string
46
58
  }
47
59
 
@@ -51,6 +63,7 @@
51
63
  edges = [],
52
64
  fields = {},
53
65
  sizeBy = 'weight',
66
+ shadeBy = $bindable(undefined),
54
67
  levels = $bindable(2),
55
68
  focusPath = $bindable([]),
56
69
  controls = false,
@@ -61,6 +74,8 @@
61
74
  mode = 'light',
62
75
  label = undefined,
63
76
  onselect = undefined,
77
+ ondrill = undefined,
78
+ ondrillup = undefined,
64
79
  class: className = ''
65
80
  }: Props = $props()
66
81
 
@@ -72,13 +87,19 @@
72
87
  fields,
73
88
  layout: 'world',
74
89
  sizeBy,
90
+ shadeBy,
75
91
  levels,
76
92
  focusPath,
77
93
  value,
78
94
  preset,
79
95
  mode,
80
96
  label,
81
- onselect
97
+ onselect,
98
+ ondrill,
99
+ ondrillup,
100
+ // Drilling moves focusPath inside the state; keep the bindable prop in step, or the next
101
+ // update() would put the old value back.
102
+ onfocuspath: (path: string[]) => (focusPath = path)
82
103
  })
83
104
 
84
105
  // svelte-ignore state_referenced_locally
@@ -95,8 +116,19 @@
95
116
  // and are never reset here. What this component DOES own is the options its own
96
117
  // controls drive — the layout above all, since choosing this component is choosing it.
97
118
  // `apply` merges; `update` would revert everything it does not name.
98
- const { nodes: _n, edges: _e, fields: _f, preset: _p, mode: _m, value: _v, ...mine } =
99
- config()
119
+ // The drill path too: a caller's state holds where the reader drilled, and pushing this
120
+ // component's own focusPath prop back over it undid the drill on any prop change.
121
+ const {
122
+ nodes: _n,
123
+ edges: _e,
124
+ fields: _f,
125
+ preset: _p,
126
+ mode: _m,
127
+ value: _v,
128
+ focusPath: _fp,
129
+ onfocuspath: _sync,
130
+ ...mine
131
+ } = config()
100
132
  graph.apply(mine)
101
133
  })
102
134
  </script>
@@ -105,17 +137,21 @@
105
137
  inside is always defined, so the frame would render an empty control bar over
106
138
  every diagram that asked for none. -->
107
139
  {#snippet controlBar()}
108
- <DepthControl {levels} max={maxLevels} onchange={(v) => (levels = v)} />
109
- <ZoomControl {zoom} onchange={(v) => (zoom = v)} />
140
+ <DrillBar state={graph} />
141
+ {#if controls}
142
+ <DepthControl {levels} max={maxLevels} onchange={(v) => (levels = v)} />
143
+ <MeasureControl channel="color" value={shadeBy} options={graph.measureKeys} allowNone onchange={(v) => (shadeBy = v)} />
144
+ <ZoomControl {zoom} onchange={(v) => (zoom = v)} />
145
+ {/if}
110
146
  {/snippet}
111
147
 
112
148
  {#snippet legendBar()}
113
- <GraphLegend state={graph} groups />
149
+ <GraphLegend state={graph} groups shade />
114
150
  {/snippet}
115
151
 
116
152
  <DiagramFrame
117
153
  class={className}
118
- overlay={controls ? controlBar : undefined}
154
+ overlay={controls || graph.showsDrillBar ? controlBar : undefined}
119
155
  footer={legend ? legendBar : undefined}
120
156
  >
121
157
  {#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
  /**
@@ -13,6 +13,8 @@ type Props = {
13
13
  fields?: GraphFields;
14
14
  /** What area encodes. A `measures` key, or `weight`, or `degree`. */
15
15
  sizeBy?: string;
16
+ /** The measure — a 0..1 share — each box is shaded by (#164). Bindable; the picker moves it. */
17
+ shadeBy?: string;
16
18
  /** How many levels below the focus to materialise. */
17
19
  levels?: number;
18
20
  /** The subtree to render as the whole canvas. `[]` is the root. Drilling, not zooming. */
@@ -25,8 +27,16 @@ type Props = {
25
27
  mode?: 'light' | 'dark';
26
28
  label?: string;
27
29
  onselect?: (id: string | null) => void;
30
+ /**
31
+ * The reader opened a box — `path` is the new `focusPath`, `node` the declared node there
32
+ * (null for a synthesised container). Return a promise while you load that level; the
33
+ * canvas shows it pending, and a rejection returns to the previous level.
34
+ */
35
+ ondrill?: (path: string[], node: GraphNode | null) => void | Promise<void>;
36
+ /** The reader climbed out; `path` is the new, shorter `focusPath`. */
37
+ ondrillup?: (path: string[]) => void | Promise<void>;
28
38
  class?: string;
29
39
  };
30
- declare const Treemap: import("svelte").Component<Props, {}, "value" | "focusPath" | "levels">;
40
+ declare const Treemap: import("svelte").Component<Props, {}, "value" | "focusPath" | "levels" | "shadeBy">;
31
41
  type Treemap = ReturnType<typeof Treemap>;
32
42
  export default Treemap;
package/dist/index.d.ts CHANGED
@@ -7,22 +7,32 @@ export { default as Treemap } from './diagrams/Treemap.svelte';
7
7
  export { default as Sunburst } from './diagrams/Sunburst.svelte';
8
8
  export { default as StructureDiagram } from './diagrams/StructureDiagram.svelte';
9
9
  export { default as Neighborhood } from './diagrams/Neighborhood.svelte';
10
+ export { default as DependencyMatrix } from './diagrams/DependencyMatrix.svelte';
11
+ export { default as LayersDiagram } from './diagrams/LayersDiagram.svelte';
12
+ export { default as PolymetricTree } from './diagrams/PolymetricTree.svelte';
13
+ export { default as ArcDiagram } from './diagrams/ArcDiagram.svelte';
10
14
  export { default as DensityControl } from './controls/DensityControl.svelte';
11
15
  export { default as EdgeStyleControl } from './controls/EdgeStyleControl.svelte';
12
16
  export { default as ZoomControl } from './controls/ZoomControl.svelte';
13
17
  export { default as DepthControl } from './controls/DepthControl.svelte';
14
18
  export { default as BundleControl } from './controls/BundleControl.svelte';
19
+ export { default as ViolationsControl } from './controls/ViolationsControl.svelte';
20
+ export { default as MeasureControl } from './controls/MeasureControl.svelte';
21
+ export { default as HiddenControl } from './controls/HiddenControl.svelte';
22
+ export { default as DrillBar } from './controls/DrillBar.svelte';
15
23
  export { ZOOM_MIN, ZOOM_MAX, ZOOM_STEP, clampZoom } from './controls/zoom.js';
16
24
  export { GraphState } from './GraphState.svelte.js';
17
25
  export { normalizeGraph } from './model/normalize.js';
18
26
  export { buildTree, findNode } from './model/tree.js';
19
27
  export type { TreeNode } from './model/tree.js';
20
28
  export { readPath } from './model/path.js';
21
- export { createGraphPreset, defaultGraphPreset, resolveGroupStyles } from './preset.js';
29
+ export { createGraphPreset, defaultGraphPreset, resolveGroupStyles, resolveShade } from './preset.js';
22
30
  export { DEFAULT_ICONS } from './icons.js';
23
- export { cluster, flow, neighborhood, points, radial, structure, sunburst, world, layouts } from './layout/index.js';
31
+ export { cluster, flow, layers, neighborhood, points, polymetric, arcs, radial, structure, sunburst, world, layouts } from './layout/index.js';
24
32
  export { LAYOUT_OPTIONS, appliesTo } from './layout/options.js';
33
+ export { buildMatrix } from './layout/matrix.js';
34
+ export type { Matrix, MatrixCell, MatrixBlock, MatrixOptions } from './layout/matrix.js';
25
35
  export type { EntityRow, GraphStateConfig, Relationship } from './GraphState.svelte.js';
26
- export type { GraphChannel, GraphPreset, GraphShades } from './preset.js';
36
+ export type { GraphChannel, GraphPreset, GraphShadeRamp, GraphShades } from './preset.js';
27
37
  export type * from './types.js';
28
38
  export type * from './layout/types.js';
package/dist/index.js CHANGED
@@ -13,18 +13,27 @@ export { default as Treemap } from './diagrams/Treemap.svelte';
13
13
  export { default as Sunburst } from './diagrams/Sunburst.svelte';
14
14
  export { default as StructureDiagram } from './diagrams/StructureDiagram.svelte';
15
15
  export { default as Neighborhood } from './diagrams/Neighborhood.svelte';
16
+ export { default as DependencyMatrix } from './diagrams/DependencyMatrix.svelte';
17
+ export { default as LayersDiagram } from './diagrams/LayersDiagram.svelte';
18
+ export { default as PolymetricTree } from './diagrams/PolymetricTree.svelte';
19
+ export { default as ArcDiagram } from './diagrams/ArcDiagram.svelte';
16
20
  // Controls, for composing your own arrangement over the bare canvas.
17
21
  export { default as DensityControl } from './controls/DensityControl.svelte';
18
22
  export { default as EdgeStyleControl } from './controls/EdgeStyleControl.svelte';
19
23
  export { default as ZoomControl } from './controls/ZoomControl.svelte';
20
24
  export { default as DepthControl } from './controls/DepthControl.svelte';
21
25
  export { default as BundleControl } from './controls/BundleControl.svelte';
26
+ export { default as ViolationsControl } from './controls/ViolationsControl.svelte';
27
+ export { default as MeasureControl } from './controls/MeasureControl.svelte';
28
+ export { default as HiddenControl } from './controls/HiddenControl.svelte';
29
+ export { default as DrillBar } from './controls/DrillBar.svelte';
22
30
  export { ZOOM_MIN, ZOOM_MAX, ZOOM_STEP, clampZoom } from './controls/zoom.js';
23
31
  export { GraphState } from './GraphState.svelte.js';
24
32
  export { normalizeGraph } from './model/normalize.js';
25
33
  export { buildTree, findNode } from './model/tree.js';
26
34
  export { readPath } from './model/path.js';
27
- export { createGraphPreset, defaultGraphPreset, resolveGroupStyles } from './preset.js';
35
+ export { createGraphPreset, defaultGraphPreset, resolveGroupStyles, resolveShade } from './preset.js';
28
36
  export { DEFAULT_ICONS } from './icons.js';
29
- export { cluster, flow, neighborhood, points, radial, structure, sunburst, world, layouts } from './layout/index.js';
37
+ export { cluster, flow, layers, neighborhood, points, polymetric, arcs, radial, structure, sunburst, world, layouts } from './layout/index.js';
30
38
  export { LAYOUT_OPTIONS, appliesTo } from './layout/options.js';
39
+ export { buildMatrix } from './layout/matrix.js';
@@ -0,0 +1,6 @@
1
+ import type { LayoutFn } from './types.js';
2
+ import type { GraphEdge } from '../types.js';
3
+ /** Which side an edge belongs on: the named relation's when `above` is given, else overlays. */
4
+ export declare const sideOf: (edge: GraphEdge, above: string | undefined) => "above" | "below";
5
+ /** The items on one axis, imports arcing left and shared commits right. */
6
+ export declare const arcs: LayoutFn;
@@ -0,0 +1,141 @@
1
+ /* The dual arc diagram (#169): two relations over one shared, ordered axis.
2
+ *
3
+ * Items stand in one column, each a labelled box. The coupling the code declares (imports) arcs
4
+ * out of the boxes' left edges, and the coupling the history reveals (shared commits) out of
5
+ * their right edges. The finding is a pair with an arc on one side and none on the other — the
6
+ * host flags a co-change pair with no import `hidden`, and `showEdges: 'hidden'` keeps only those.
7
+ *
8
+ * Vertical, so ~50 item labels read horizontally, one per row. Each side normalises its weights
9
+ * on its own — an import count and a commit count are different units, and one shared maximum
10
+ * would draw a whole side hairline — and to its 95th percentile, not its maximum, so one
11
+ * outlying pair does not flatten the rest.
12
+ */
13
+ import { carried, keepsEdge } from './edges.js';
14
+ import { warnUnknownOptions } from './options.js';
15
+ import { capOf } from './percentile.js';
16
+ const BOX_W = 168;
17
+ const BOX_H = 16;
18
+ const ROW = 20;
19
+ const PAD = 16;
20
+ /** An arc's horizontal reach as a share of half its span: flatter than a semicircle, so the
21
+ * longest arcs do not push the canvas twice as wide as it is tall. */
22
+ const BULGE = 0.5;
23
+ /** By group, in the order groups first appear; within a group, as given. */
24
+ function axisOrder(nodes) {
25
+ const rank = new Map();
26
+ for (const n of nodes)
27
+ if (!rank.has(n.group ?? ''))
28
+ rank.set(n.group ?? '', rank.size);
29
+ return nodes
30
+ .map((node, i) => ({ node, i }))
31
+ .sort((a, b) => rank.get(a.node.group ?? '') - rank.get(b.node.group ?? '') || a.i - b.i)
32
+ .map(({ node }) => node);
33
+ }
34
+ /** Which side an edge belongs on: the named relation's when `above` is given, else overlays. */
35
+ export const sideOf = (edge, above) => (above === undefined ? edge.overlay : edge.relation === above) ? 'above' : 'below';
36
+ /** An arc between two rows, out of the left edge (below) or the right edge (above). */
37
+ function arc(edge, i, side, { x, y1, y2 }) {
38
+ const [top, bottom] = y1 < y2 ? [y1, y2] : [y2, y1];
39
+ const ry = (bottom - top) / 2;
40
+ // Drawn top to bottom: sweep 0 bends it left, 1 right.
41
+ const sweep = side === 'below' ? 0 : 1;
42
+ const s = side === 'below' ? -1 : 1;
43
+ return {
44
+ i,
45
+ id: edge.id,
46
+ fromKey: edge.source,
47
+ toKey: edge.target,
48
+ kind: edge.kind,
49
+ ...(edge.relation ? { relation: edge.relation } : {}),
50
+ ...carried(edge),
51
+ side,
52
+ self: false,
53
+ x1: x,
54
+ y1,
55
+ x2: x,
56
+ y2,
57
+ s1: s,
58
+ s2: s,
59
+ path: `M ${x} ${top} A ${ry * BULGE} ${ry} 0 0 ${sweep} ${x} ${bottom}`
60
+ };
61
+ }
62
+ /** Each side's weights as 0..1 of that side's 95th percentile, clamped past it. */
63
+ function strengthen(edges) {
64
+ for (const side of ['above', 'below']) {
65
+ const mine = edges.filter((e) => e.side === side && e.weight !== undefined);
66
+ const cap = capOf(mine.map((e) => e.weight));
67
+ for (const e of mine)
68
+ e.strength = Math.min(1, Math.max(0, e.weight / cap));
69
+ }
70
+ }
71
+ function route(model, placed, x0, options) {
72
+ const routed = [];
73
+ for (const edge of [...model.edges, ...model.overlays]) {
74
+ const from = placed.get(edge.source);
75
+ const to = placed.get(edge.target);
76
+ if (!from || !to || from === to)
77
+ continue;
78
+ const side = sideOf(edge, options.above);
79
+ const x = side === 'below' ? x0 : x0 + BOX_W;
80
+ routed.push(arc(edge, routed.length, side, { x, y1: from.y + BOX_H / 2, y2: to.y + BOX_H / 2 }));
81
+ }
82
+ // Strength first, so filtering to the hidden pairs does not re-scale what is left.
83
+ strengthen(routed);
84
+ return routed.filter((e) => keepsEdge(options.showEdges ?? 'all', e));
85
+ }
86
+ /** How far the widest arc on each side reaches past the box column. */
87
+ function reach(model, placed, above) {
88
+ const out = { above: 0, below: 0 };
89
+ for (const edge of [...model.edges, ...model.overlays]) {
90
+ const from = placed.get(edge.source);
91
+ const to = placed.get(edge.target);
92
+ if (!from || !to)
93
+ continue;
94
+ const side = sideOf(edge, above);
95
+ out[side] = Math.max(out[side], (Math.abs(to.y - from.y) / 2) * BULGE);
96
+ }
97
+ return out;
98
+ }
99
+ function degrees(model) {
100
+ const count = new Map();
101
+ for (const e of [...model.edges, ...model.overlays]) {
102
+ if (e.source === e.target)
103
+ continue;
104
+ count.set(e.source, (count.get(e.source) ?? 0) + 1);
105
+ count.set(e.target, (count.get(e.target) ?? 0) + 1);
106
+ }
107
+ return count;
108
+ }
109
+ /** The items on one axis, imports arcing left and shared commits right. */
110
+ export const arcs = (model, options) => {
111
+ warnUnknownOptions(options, 'arcs');
112
+ if (model.nodes.length === 0)
113
+ return { clusters: [], cards: {}, edges: [], size: { w: 0, h: 0 } };
114
+ const order = axisOrder(model.nodes);
115
+ const placed = new Map(order.map((node, row) => [node.id, { node, y: PAD + row * ROW }]));
116
+ // The axis is fixed by the items alone, so filtering edges never moves a box.
117
+ const { above, below } = reach(model, placed, options.above);
118
+ const x0 = PAD + below;
119
+ const degree = degrees(model);
120
+ const clusters = order.map((node) => ({
121
+ name: node.label,
122
+ list: [],
123
+ count: degree.get(node.id) ?? 0,
124
+ groupIndex: 0,
125
+ x: x0,
126
+ y: placed.get(node.id).y,
127
+ w: BOX_W,
128
+ h: BOX_H,
129
+ depth: 0,
130
+ nodeId: node.id,
131
+ kind: node.kind,
132
+ declared: node.id,
133
+ ...(node.group ? { ramp: node.group } : {})
134
+ }));
135
+ return {
136
+ clusters,
137
+ cards: {},
138
+ edges: route(model, placed, x0, options),
139
+ size: { w: x0 + BOX_W + above + PAD, h: PAD * 2 + (order.length - 1) * ROW + BOX_H }
140
+ };
141
+ };
@@ -18,14 +18,21 @@ function selectedRows(node, density, select) {
18
18
  }
19
19
  return node.rows;
20
20
  }
21
+ /**
22
+ * Head, the shown rows, and a control row when there is something to control: hidden rows
23
+ * ("+3 more"), or a group's "Expand" (#166). Bottom padding only under content.
24
+ */
25
+ function heightOf(node, shown, more) {
26
+ const controlRow = more > 0 || (node.members?.length ?? 0) > 0;
27
+ return HEAD_H + shown * ROW_H + (controlRow ? MORE_H : 0) + (shown || controlRow ? PAD_B : 0);
28
+ }
21
29
  /** Build a card per node. Height is derived from row count — nothing is measured. */
22
30
  export function buildCards(nodes, density, options = {}) {
23
31
  const cards = {};
24
32
  for (const node of nodes) {
25
33
  const vis = visibleRows(node, density, options);
26
34
  const more = node.rows.length - vis.length;
27
- const h = HEAD_H + vis.length * ROW_H + (more > 0 ? MORE_H : 0) + (vis.length || more > 0 ? PAD_B : 0);
28
- cards[node.id] = { node, vis, more, w: CARD_W, h, x: 0, y: 0 };
35
+ cards[node.id] = { node, vis, more, w: CARD_W, h: heightOf(node, vis.length, more), x: 0, y: 0 };
29
36
  }
30
37
  return cards;
31
38
  }
@@ -1,5 +1,17 @@
1
- import type { Cards, EdgeStyle, RoutedEdge } from './types.js';
1
+ import type { Cards, EdgeStyle, RoutedEdge, ShowEdges } from './types.js';
2
2
  import type { GraphEdge } from '../types.js';
3
+ /**
4
+ * Whether a `showEdges` filter keeps this edge: `violations` keeps the edges that climb a layer
5
+ * (#167), `hidden` the coupling no import explains (#169), `all` everything.
6
+ */
7
+ export declare function keepsEdge(showEdges: ShowEdges, edge: Pick<RoutedEdge, 'conformance' | 'hidden'>): boolean;
8
+ /**
9
+ * What an edge carries onto the canvas whatever routes it — overlay flag, weight, the weakest
10
+ * and hidden flags and a condensed edge's count — copied only when set, so existing routed edges stay
11
+ * key-identical. Every layout that builds routed edges spreads this; one that built its own
12
+ * identity half without it silently drew weighted edges at the normal width.
13
+ */
14
+ export declare function carried(edge: GraphEdge): Pick<RoutedEdge, 'overlay' | 'weight' | 'weakest' | 'hidden' | 'count' | 'conformance'>;
3
15
  /** Build all edge geometry, skipping edges whose endpoints aren't laid out. */
4
16
  export declare function buildEdges(edges: GraphEdge[], cards: Cards): RoutedEdge[];
5
17
  export declare function edgePath(e: RoutedEdge, style: EdgeStyle): string;
@@ -26,6 +26,7 @@ function selfLoop(edge, i, card, { y1, y2 }) {
26
26
  toKey: edge.target,
27
27
  kind: edge.kind,
28
28
  relation: edge.relation,
29
+ ...carried(edge),
29
30
  self: true,
30
31
  x1: card.x + card.w,
31
32
  y1,
@@ -35,6 +36,39 @@ function selfLoop(edge, i, card, { y1, y2 }) {
35
36
  s2: 1
36
37
  };
37
38
  }
39
+ /**
40
+ * Whether a `showEdges` filter keeps this edge: `violations` keeps the edges that climb a layer
41
+ * (#167), `hidden` the coupling no import explains (#169), `all` everything.
42
+ */
43
+ export function keepsEdge(showEdges, edge) {
44
+ if (showEdges === 'violations')
45
+ return edge.conformance === 'up';
46
+ if (showEdges === 'hidden')
47
+ return edge.hidden === true;
48
+ return true;
49
+ }
50
+ /**
51
+ * What an edge carries onto the canvas whatever routes it — overlay flag, weight, the weakest
52
+ * and hidden flags and a condensed edge's count — copied only when set, so existing routed edges stay
53
+ * key-identical. Every layout that builds routed edges spreads this; one that built its own
54
+ * identity half without it silently drew weighted edges at the normal width.
55
+ */
56
+ export function carried(edge) {
57
+ const out = {};
58
+ if (edge.conformance)
59
+ out.conformance = edge.conformance;
60
+ if (edge.overlay)
61
+ out.overlay = true;
62
+ if (edge.weight !== undefined)
63
+ out.weight = edge.weight;
64
+ if (edge.weakest)
65
+ out.weakest = true;
66
+ if (edge.hidden)
67
+ out.hidden = true;
68
+ if (edge.count !== undefined)
69
+ out.count = edge.count;
70
+ return out;
71
+ }
38
72
  /** Which side of each card the connector leaves from. 1 = right, -1 = left. */
39
73
  function sides(a, b) {
40
74
  if (a.x + a.w + 50 <= b.x)
@@ -61,6 +95,7 @@ function buildEdge(edge, i, cards) {
61
95
  toKey: edge.target,
62
96
  kind: edge.kind,
63
97
  relation: edge.relation,
98
+ ...carried(edge),
64
99
  self: false,
65
100
  x1: s1 === 1 ? a.x + a.w : a.x,
66
101
  y1,
@@ -0,0 +1,17 @@
1
+ import type { Size } from './types.js';
2
+ type Box = {
3
+ x: number;
4
+ y: number;
5
+ w?: number;
6
+ h?: number;
7
+ };
8
+ /**
9
+ * The true content extent of a layout, as distinct from its `size`.
10
+ *
11
+ * `LayoutResult.size` adds a +60 margin on the right and bottom only, so fitting to it hugs the
12
+ * left/top edge and floats away from the right/bottom. Centring needs the real bounds: the
13
+ * clusters', or — for an ungrouped layout (neighborhood), which reports none — the cards', and
14
+ * the layout size only when both are empty.
15
+ */
16
+ export declare function contentExtent(clusters: Box[], cards: Record<string, Box>, size: Size): Size;
17
+ export {};