@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.
- package/README.md +59 -0
- package/dist/Graph.svelte +108 -134
- package/dist/Graph.svelte.d.ts +1 -1
- package/dist/GraphLegend.svelte +81 -10
- package/dist/GraphLegend.svelte.d.ts +15 -0
- package/dist/GraphState.svelte.d.ts +161 -52
- package/dist/GraphState.svelte.js +380 -304
- package/dist/actions/canvas.d.ts +18 -0
- package/dist/actions/canvas.js +59 -0
- package/dist/actions/choices.d.ts +12 -0
- package/dist/actions/choices.js +23 -0
- package/dist/actions/interactions.d.ts +20 -0
- package/dist/actions/interactions.js +57 -0
- package/dist/actions/listen.d.ts +2 -0
- package/dist/actions/listen.js +10 -0
- package/dist/canvas/frame.d.ts +41 -0
- package/dist/canvas/frame.js +34 -0
- package/dist/controls/BundleControl.svelte +11 -10
- package/dist/controls/BundleControl.svelte.d.ts +0 -7
- package/dist/controls/DensityControl.svelte +17 -9
- package/dist/controls/DensityControl.svelte.d.ts +0 -8
- package/dist/controls/DepthControl.svelte +11 -4
- package/dist/controls/DepthControl.svelte.d.ts +0 -9
- package/dist/controls/DrillBar.svelte +63 -0
- package/dist/controls/DrillBar.svelte.d.ts +9 -0
- package/dist/controls/EdgeStyleControl.svelte +10 -10
- package/dist/controls/EdgeStyleControl.svelte.d.ts +0 -6
- package/dist/controls/HiddenControl.svelte +27 -0
- package/dist/controls/HiddenControl.svelte.d.ts +7 -0
- package/dist/controls/MeasureControl.svelte +39 -0
- package/dist/controls/MeasureControl.svelte.d.ts +12 -0
- package/dist/controls/ToggleControl.svelte +33 -0
- package/dist/controls/ToggleControl.svelte.d.ts +13 -0
- package/dist/controls/ViolationsControl.svelte +27 -0
- package/dist/controls/ViolationsControl.svelte.d.ts +7 -0
- package/dist/controls/ZoomControl.svelte +21 -18
- package/dist/controls/zoom.d.ts +3 -0
- package/dist/controls/zoom.js +6 -0
- package/dist/diagrams/ArcDiagram.svelte +116 -0
- package/dist/diagrams/ArcDiagram.svelte.d.ts +31 -0
- package/dist/diagrams/CallTree.svelte +17 -5
- package/dist/diagrams/CallTree.svelte.d.ts +5 -1
- package/dist/diagrams/DependencyDiagram.svelte +18 -6
- package/dist/diagrams/DependencyDiagram.svelte.d.ts +5 -1
- package/dist/diagrams/DependencyMatrix.svelte +11 -32
- package/dist/diagrams/ErDiagram.svelte +18 -6
- package/dist/diagrams/ErDiagram.svelte.d.ts +5 -1
- package/dist/diagrams/LayersDiagram.svelte +124 -0
- package/dist/diagrams/LayersDiagram.svelte.d.ts +30 -0
- package/dist/diagrams/Neighborhood.svelte +26 -6
- package/dist/diagrams/Neighborhood.svelte.d.ts +11 -1
- package/dist/diagrams/PolymetricTree.svelte +116 -0
- package/dist/diagrams/PolymetricTree.svelte.d.ts +28 -0
- package/dist/diagrams/StructureDiagram.svelte +38 -8
- package/dist/diagrams/StructureDiagram.svelte.d.ts +9 -1
- package/dist/diagrams/Sunburst.svelte +44 -8
- package/dist/diagrams/Sunburst.svelte.d.ts +12 -2
- package/dist/diagrams/Treemap.svelte +44 -8
- package/dist/diagrams/Treemap.svelte.d.ts +12 -2
- package/dist/index.d.ts +10 -3
- package/dist/index.js +9 -2
- package/dist/layout/arcs.d.ts +6 -0
- package/dist/layout/arcs.js +141 -0
- package/dist/layout/cards.js +9 -2
- package/dist/layout/edges.d.ts +13 -1
- package/dist/layout/edges.js +26 -2
- package/dist/layout/extent.d.ts +17 -0
- package/dist/layout/extent.js +27 -0
- package/dist/layout/flow.js +3 -1
- package/dist/layout/index.d.ts +4 -1
- package/dist/layout/index.js +17 -2
- package/dist/layout/layers.d.ts +6 -0
- package/dist/layout/layers.js +180 -0
- package/dist/layout/matrix.d.ts +12 -0
- package/dist/layout/matrix.js +20 -0
- package/dist/layout/neighborhood.js +16 -7
- package/dist/layout/options.d.ts +1 -1
- package/dist/layout/options.js +16 -3
- package/dist/layout/percentile.d.ts +8 -0
- package/dist/layout/percentile.js +13 -0
- package/dist/layout/polymetric.d.ts +3 -0
- package/dist/layout/polymetric.js +221 -0
- package/dist/layout/radial.js +3 -1
- package/dist/layout/structure.js +7 -1
- package/dist/layout/sunburst.js +11 -4
- package/dist/layout/types.d.ts +79 -0
- package/dist/layout/world.js +11 -4
- package/dist/messages.d.ts +14 -0
- package/dist/messages.js +17 -0
- package/dist/model/condense.d.ts +8 -0
- package/dist/model/condense.js +114 -0
- package/dist/model/entities.d.ts +12 -0
- package/dist/model/entities.js +12 -0
- package/dist/model/normalize.d.ts +2 -6
- package/dist/model/normalize.js +64 -2
- package/dist/model/relationships.d.ts +30 -0
- package/dist/model/relationships.js +51 -0
- package/dist/model/share.d.ts +9 -0
- package/dist/model/share.js +43 -0
- package/dist/model/tree.d.ts +7 -1
- package/dist/model/tree.js +11 -4
- package/dist/preset.d.ts +18 -0
- package/dist/preset.js +34 -3
- package/dist/schema/EntitiesView.svelte +8 -6
- package/dist/schema/EntityView.svelte +13 -10
- package/dist/state/GraphConfig.svelte.d.ts +124 -0
- package/dist/state/GraphConfig.svelte.js +253 -0
- package/dist/state/GraphDrill.svelte.d.ts +52 -0
- package/dist/state/GraphDrill.svelte.js +115 -0
- package/dist/state/GraphGroups.svelte.d.ts +35 -0
- package/dist/state/GraphGroups.svelte.js +73 -0
- package/dist/state/GraphSelection.svelte.d.ts +53 -0
- package/dist/state/GraphSelection.svelte.js +94 -0
- package/dist/state/intents.d.ts +33 -0
- package/dist/state/intents.js +30 -0
- package/dist/state/text.d.ts +28 -0
- package/dist/state/text.js +56 -0
- package/dist/types.d.ts +72 -1
- package/package.json +5 -4
|
@@ -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
|
-
|
|
99
|
-
|
|
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
|
-
<
|
|
109
|
-
|
|
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
|
@@ -8,24 +8,31 @@ 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
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';
|
|
11
14
|
export { default as DensityControl } from './controls/DensityControl.svelte';
|
|
12
15
|
export { default as EdgeStyleControl } from './controls/EdgeStyleControl.svelte';
|
|
13
16
|
export { default as ZoomControl } from './controls/ZoomControl.svelte';
|
|
14
17
|
export { default as DepthControl } from './controls/DepthControl.svelte';
|
|
15
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';
|
|
16
23
|
export { ZOOM_MIN, ZOOM_MAX, ZOOM_STEP, clampZoom } from './controls/zoom.js';
|
|
17
24
|
export { GraphState } from './GraphState.svelte.js';
|
|
18
25
|
export { normalizeGraph } from './model/normalize.js';
|
|
19
26
|
export { buildTree, findNode } from './model/tree.js';
|
|
20
27
|
export type { TreeNode } from './model/tree.js';
|
|
21
28
|
export { readPath } from './model/path.js';
|
|
22
|
-
export { createGraphPreset, defaultGraphPreset, resolveGroupStyles } from './preset.js';
|
|
29
|
+
export { createGraphPreset, defaultGraphPreset, resolveGroupStyles, resolveShade } from './preset.js';
|
|
23
30
|
export { DEFAULT_ICONS } from './icons.js';
|
|
24
|
-
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';
|
|
25
32
|
export { LAYOUT_OPTIONS, appliesTo } from './layout/options.js';
|
|
26
33
|
export { buildMatrix } from './layout/matrix.js';
|
|
27
34
|
export type { Matrix, MatrixCell, MatrixBlock, MatrixOptions } from './layout/matrix.js';
|
|
28
35
|
export type { EntityRow, GraphStateConfig, Relationship } from './GraphState.svelte.js';
|
|
29
|
-
export type { GraphChannel, GraphPreset, GraphShades } from './preset.js';
|
|
36
|
+
export type { GraphChannel, GraphPreset, GraphShadeRamp, GraphShades } from './preset.js';
|
|
30
37
|
export type * from './types.js';
|
|
31
38
|
export type * from './layout/types.js';
|
package/dist/index.js
CHANGED
|
@@ -14,19 +14,26 @@ 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
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';
|
|
17
20
|
// Controls, for composing your own arrangement over the bare canvas.
|
|
18
21
|
export { default as DensityControl } from './controls/DensityControl.svelte';
|
|
19
22
|
export { default as EdgeStyleControl } from './controls/EdgeStyleControl.svelte';
|
|
20
23
|
export { default as ZoomControl } from './controls/ZoomControl.svelte';
|
|
21
24
|
export { default as DepthControl } from './controls/DepthControl.svelte';
|
|
22
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';
|
|
23
30
|
export { ZOOM_MIN, ZOOM_MAX, ZOOM_STEP, clampZoom } from './controls/zoom.js';
|
|
24
31
|
export { GraphState } from './GraphState.svelte.js';
|
|
25
32
|
export { normalizeGraph } from './model/normalize.js';
|
|
26
33
|
export { buildTree, findNode } from './model/tree.js';
|
|
27
34
|
export { readPath } from './model/path.js';
|
|
28
|
-
export { createGraphPreset, defaultGraphPreset, resolveGroupStyles } from './preset.js';
|
|
35
|
+
export { createGraphPreset, defaultGraphPreset, resolveGroupStyles, resolveShade } from './preset.js';
|
|
29
36
|
export { DEFAULT_ICONS } from './icons.js';
|
|
30
|
-
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';
|
|
31
38
|
export { LAYOUT_OPTIONS, appliesTo } from './layout/options.js';
|
|
32
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
|
+
};
|
package/dist/layout/cards.js
CHANGED
|
@@ -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
|
-
|
|
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
|
}
|
package/dist/layout/edges.d.ts
CHANGED
|
@@ -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;
|
package/dist/layout/edges.js
CHANGED
|
@@ -36,13 +36,37 @@ function selfLoop(edge, i, card, { y1, y2 }) {
|
|
|
36
36
|
s2: 1
|
|
37
37
|
};
|
|
38
38
|
}
|
|
39
|
-
/**
|
|
40
|
-
|
|
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) {
|
|
41
57
|
const out = {};
|
|
58
|
+
if (edge.conformance)
|
|
59
|
+
out.conformance = edge.conformance;
|
|
42
60
|
if (edge.overlay)
|
|
43
61
|
out.overlay = true;
|
|
44
62
|
if (edge.weight !== undefined)
|
|
45
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;
|
|
46
70
|
return out;
|
|
47
71
|
}
|
|
48
72
|
/** Which side of each card the connector leaves from. 1 = right, -1 = left. */
|
|
@@ -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 {};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/** Bottom-right extent of a set of boxes (a box with no size is a point), or {0,0}. */
|
|
2
|
+
function extentOf(boxes) {
|
|
3
|
+
let w = 0;
|
|
4
|
+
let h = 0;
|
|
5
|
+
for (const box of boxes) {
|
|
6
|
+
w = Math.max(w, box.x + (box.w ?? 0));
|
|
7
|
+
h = Math.max(h, box.y + (box.h ?? 0));
|
|
8
|
+
}
|
|
9
|
+
return { w, h };
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* The true content extent of a layout, as distinct from its `size`.
|
|
13
|
+
*
|
|
14
|
+
* `LayoutResult.size` adds a +60 margin on the right and bottom only, so fitting to it hugs the
|
|
15
|
+
* left/top edge and floats away from the right/bottom. Centring needs the real bounds: the
|
|
16
|
+
* clusters', or — for an ungrouped layout (neighborhood), which reports none — the cards', and
|
|
17
|
+
* the layout size only when both are empty.
|
|
18
|
+
*/
|
|
19
|
+
export function contentExtent(clusters, cards, size) {
|
|
20
|
+
let { w, h } = extentOf(clusters);
|
|
21
|
+
if (!w || !h) {
|
|
22
|
+
const fromCards = extentOf(Object.values(cards));
|
|
23
|
+
w = Math.max(w, fromCards.w);
|
|
24
|
+
h = Math.max(h, fromCards.h);
|
|
25
|
+
}
|
|
26
|
+
return { w: w || size.w, h: h || size.h };
|
|
27
|
+
}
|
package/dist/layout/flow.js
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
* edge is still drawn straight over any box between its endpoints, because routing it around
|
|
14
14
|
* needs dummy nodes on the intervening ranks and that is its own slice.
|
|
15
15
|
*/
|
|
16
|
+
import { carried } from './edges.js';
|
|
16
17
|
import { buildCards } from './cards.js';
|
|
17
18
|
import { rank } from './rank.js';
|
|
18
19
|
import { order } from './order.js';
|
|
@@ -65,7 +66,8 @@ function identity(edge, i) {
|
|
|
65
66
|
fromKey: edge.source,
|
|
66
67
|
toKey: edge.target,
|
|
67
68
|
kind: edge.kind,
|
|
68
|
-
relation: edge.relation
|
|
69
|
+
relation: edge.relation,
|
|
70
|
+
...carried(edge)
|
|
69
71
|
};
|
|
70
72
|
}
|
|
71
73
|
/**
|
package/dist/layout/index.d.ts
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
|
+
import { arcs } from './arcs.js';
|
|
1
2
|
import { cluster } from './cluster.js';
|
|
2
3
|
import { flow } from './flow.js';
|
|
4
|
+
import { layers } from './layers.js';
|
|
3
5
|
import { neighborhood } from './neighborhood.js';
|
|
4
6
|
import { world } from './world.js';
|
|
5
7
|
import { points } from './points.js';
|
|
8
|
+
import { polymetric } from './polymetric.js';
|
|
6
9
|
import { radial } from './radial.js';
|
|
7
10
|
import { structure } from './structure.js';
|
|
8
11
|
import { sunburst } from './sunburst.js';
|
|
@@ -10,6 +13,6 @@ import type { LayoutFn } from './types.js';
|
|
|
10
13
|
/** Built-in layouts, addressable by name from `Graph`'s `layout` prop. */
|
|
11
14
|
export declare const layouts: Record<string, LayoutFn>;
|
|
12
15
|
export type LayoutName = keyof typeof layouts;
|
|
13
|
-
export { cluster, flow, neighborhood, points, radial, structure, sunburst, world };
|
|
16
|
+
export { arcs, cluster, flow, layers, neighborhood, points, polymetric, radial, structure, sunburst, world };
|
|
14
17
|
export { LAYOUT_OPTIONS, appliesTo } from './options.js';
|
|
15
18
|
export * from './types.js';
|
package/dist/layout/index.js
CHANGED
|
@@ -1,13 +1,28 @@
|
|
|
1
|
+
import { arcs } from './arcs.js';
|
|
1
2
|
import { cluster } from './cluster.js';
|
|
2
3
|
import { flow } from './flow.js';
|
|
4
|
+
import { layers } from './layers.js';
|
|
3
5
|
import { neighborhood } from './neighborhood.js';
|
|
4
6
|
import { world } from './world.js';
|
|
5
7
|
import { points } from './points.js';
|
|
8
|
+
import { polymetric } from './polymetric.js';
|
|
6
9
|
import { radial } from './radial.js';
|
|
7
10
|
import { structure } from './structure.js';
|
|
8
11
|
import { sunburst } from './sunburst.js';
|
|
9
12
|
/** Built-in layouts, addressable by name from `Graph`'s `layout` prop. */
|
|
10
|
-
export const layouts = {
|
|
11
|
-
|
|
13
|
+
export const layouts = {
|
|
14
|
+
arcs,
|
|
15
|
+
cluster,
|
|
16
|
+
flow,
|
|
17
|
+
layers,
|
|
18
|
+
neighborhood,
|
|
19
|
+
points,
|
|
20
|
+
polymetric,
|
|
21
|
+
radial,
|
|
22
|
+
structure,
|
|
23
|
+
sunburst,
|
|
24
|
+
world
|
|
25
|
+
};
|
|
26
|
+
export { arcs, cluster, flow, layers, neighborhood, points, polymetric, radial, structure, sunburst, world };
|
|
12
27
|
export { LAYOUT_OPTIONS, appliesTo } from './options.js';
|
|
13
28
|
export * from './types.js';
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { LayoutFn } from './types.js';
|
|
2
|
+
import type { Conformance } from '../types.js';
|
|
3
|
+
/** How an edge sits against the layering, from the two layers the host gave. */
|
|
4
|
+
export declare function deriveConformance(from?: number, to?: number): Conformance | undefined;
|
|
5
|
+
/** Host-assigned layers as horizontal bands, layer 0 at the top. */
|
|
6
|
+
export declare const layers: LayoutFn;
|