@rokkit/graph 1.6.0 → 1.8.0
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 +66 -2
- package/dist/Graph.svelte +219 -169
- package/dist/GraphLegend.svelte +112 -0
- package/dist/GraphLegend.svelte.d.ts +33 -0
- package/dist/GraphState.svelte.d.ts +115 -15
- package/dist/GraphState.svelte.js +213 -5
- package/dist/controls/BundleControl.svelte +30 -0
- package/dist/controls/BundleControl.svelte.d.ts +16 -0
- package/dist/controls/DensityControl.svelte +39 -0
- package/dist/controls/DensityControl.svelte.d.ts +16 -0
- package/dist/controls/DepthControl.svelte +34 -0
- package/dist/controls/DepthControl.svelte.d.ts +17 -0
- package/dist/controls/EdgeStyleControl.svelte +31 -0
- package/dist/controls/EdgeStyleControl.svelte.d.ts +14 -0
- package/dist/controls/ZoomControl.svelte +47 -0
- package/dist/controls/ZoomControl.svelte.d.ts +7 -0
- package/dist/controls/zoom.d.ts +6 -0
- package/dist/controls/zoom.js +12 -0
- package/dist/diagrams/CallTree.svelte +135 -0
- package/dist/diagrams/CallTree.svelte.d.ts +37 -0
- package/dist/diagrams/DependencyDiagram.svelte +124 -0
- package/dist/diagrams/DependencyDiagram.svelte.d.ts +30 -0
- package/dist/diagrams/DependencyMatrix.svelte +177 -0
- package/dist/diagrams/DependencyMatrix.svelte.d.ts +34 -0
- package/dist/diagrams/DiagramFrame.svelte +36 -0
- package/dist/diagrams/DiagramFrame.svelte.d.ts +20 -0
- package/dist/diagrams/ErDiagram.svelte +128 -0
- package/dist/diagrams/ErDiagram.svelte.d.ts +33 -0
- package/dist/diagrams/Neighborhood.svelte +127 -0
- package/dist/diagrams/Neighborhood.svelte.d.ts +33 -0
- package/dist/diagrams/StructureDiagram.svelte +131 -0
- package/dist/diagrams/StructureDiagram.svelte.d.ts +31 -0
- package/dist/diagrams/Sunburst.svelte +124 -0
- package/dist/diagrams/Sunburst.svelte.d.ts +32 -0
- package/dist/diagrams/Treemap.svelte +124 -0
- package/dist/diagrams/Treemap.svelte.d.ts +32 -0
- package/dist/icons.d.ts +5 -0
- package/dist/icons.js +5 -0
- package/dist/index.d.ts +21 -1
- package/dist/index.js +25 -1
- package/dist/layout/arc.d.ts +13 -0
- package/dist/layout/arc.js +58 -0
- package/dist/layout/bundle.d.ts +30 -0
- package/dist/layout/bundle.js +80 -0
- package/dist/layout/cluster.js +2 -0
- package/dist/layout/edges.js +11 -0
- package/dist/layout/flow.d.ts +8 -0
- package/dist/layout/flow.js +141 -0
- package/dist/layout/hierarchy.d.ts +42 -0
- package/dist/layout/hierarchy.js +175 -0
- package/dist/layout/index.d.ts +7 -1
- package/dist/layout/index.js +8 -2
- package/dist/layout/matrix.d.ts +38 -0
- package/dist/layout/matrix.js +90 -0
- package/dist/layout/neighborhood.js +164 -28
- package/dist/layout/options.d.ts +44 -0
- package/dist/layout/options.js +106 -0
- package/dist/layout/order.d.ts +11 -0
- package/dist/layout/order.js +153 -0
- package/dist/layout/points.js +27 -35
- package/dist/layout/radial.d.ts +16 -0
- package/dist/layout/radial.js +214 -0
- package/dist/layout/rank.d.ts +15 -0
- package/dist/layout/rank.js +105 -0
- package/dist/layout/rings.d.ts +20 -0
- package/dist/layout/rings.js +92 -0
- package/dist/layout/sizing.d.ts +37 -0
- package/dist/layout/sizing.js +79 -0
- package/dist/layout/squarify.d.ts +14 -0
- package/dist/layout/squarify.js +114 -0
- package/dist/layout/structure.d.ts +9 -0
- package/dist/layout/structure.js +367 -0
- package/dist/layout/sunburst.d.ts +8 -0
- package/dist/layout/sunburst.js +126 -0
- package/dist/layout/types.d.ts +145 -0
- package/dist/layout/world.d.ts +9 -0
- package/dist/layout/world.js +144 -0
- package/dist/model/normalize.js +66 -4
- package/dist/model/tree.d.ts +27 -0
- package/dist/model/tree.js +182 -0
- package/dist/types.d.ts +75 -3
- package/package.json +9 -4
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* The shell every named diagram shares: a canvas, controls over it, a legend under it.
|
|
4
|
+
*
|
|
5
|
+
* Internal on purpose. A consumer composes by picking a diagram — `ErDiagram`, `CallTree`
|
|
6
|
+
* — not by assembling a frame, exactly as they pick `BarChart` rather than assembling a
|
|
7
|
+
* `Plot` with a geom. This exists so the seven diagrams do not each re-declare the same
|
|
8
|
+
* positioned box.
|
|
9
|
+
*/
|
|
10
|
+
import type { Snippet } from 'svelte'
|
|
11
|
+
|
|
12
|
+
type Props = {
|
|
13
|
+
canvas: Snippet
|
|
14
|
+
/** Controls drawn OVER the canvas, anchored to its corners. */
|
|
15
|
+
overlay?: Snippet
|
|
16
|
+
/** A legend or caption under the canvas, in the layout flow rather than over it. */
|
|
17
|
+
footer?: Snippet
|
|
18
|
+
class?: string
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
let { canvas, overlay, footer, class: className = '' }: Props = $props()
|
|
22
|
+
</script>
|
|
23
|
+
|
|
24
|
+
<div data-graph-diagram class={className}>
|
|
25
|
+
<!-- The canvas fills what is left after the footer, so a legend never overlaps the
|
|
26
|
+
picture and never gets clipped by it. -->
|
|
27
|
+
<div data-graph-diagram-canvas>
|
|
28
|
+
{@render canvas()}
|
|
29
|
+
{#if overlay}
|
|
30
|
+
<div data-graph-diagram-overlay>{@render overlay()}</div>
|
|
31
|
+
{/if}
|
|
32
|
+
</div>
|
|
33
|
+
{#if footer}
|
|
34
|
+
<div data-graph-diagram-footer>{@render footer()}</div>
|
|
35
|
+
{/if}
|
|
36
|
+
</div>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The shell every named diagram shares: a canvas, controls over it, a legend under it.
|
|
3
|
+
*
|
|
4
|
+
* Internal on purpose. A consumer composes by picking a diagram — `ErDiagram`, `CallTree`
|
|
5
|
+
* — not by assembling a frame, exactly as they pick `BarChart` rather than assembling a
|
|
6
|
+
* `Plot` with a geom. This exists so the seven diagrams do not each re-declare the same
|
|
7
|
+
* positioned box.
|
|
8
|
+
*/
|
|
9
|
+
import type { Snippet } from 'svelte';
|
|
10
|
+
type Props = {
|
|
11
|
+
canvas: Snippet;
|
|
12
|
+
/** Controls drawn OVER the canvas, anchored to its corners. */
|
|
13
|
+
overlay?: Snippet;
|
|
14
|
+
/** A legend or caption under the canvas, in the layout flow rather than over it. */
|
|
15
|
+
footer?: Snippet;
|
|
16
|
+
class?: string;
|
|
17
|
+
};
|
|
18
|
+
declare const DiagramFrame: import("svelte").Component<Props, {}, "">;
|
|
19
|
+
type DiagramFrame = ReturnType<typeof DiagramFrame>;
|
|
20
|
+
export default DiagramFrame;
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* Table entities and their foreign keys, ranked so direction reads off the geometry.
|
|
4
|
+
*
|
|
5
|
+
* An ER diagram is table entities and their relationships — a view is a derived projection
|
|
6
|
+
* and a routine is behaviour, so neither belongs here. Pass `toGraphInput(model, 'er')`
|
|
7
|
+
* from `@rokkit/graph/schema` and the split is made for you.
|
|
8
|
+
*
|
|
9
|
+
* `flow` gives up group boxes to rank by reference direction, so `groupTint` is on by
|
|
10
|
+
* default: without it the schema a table belongs to becomes invisible, which is the one
|
|
11
|
+
* thing the older cluster arrangement did show.
|
|
12
|
+
*/
|
|
13
|
+
import Graph from '../Graph.svelte'
|
|
14
|
+
import GraphLegend from '../GraphLegend.svelte'
|
|
15
|
+
import DiagramFrame from './DiagramFrame.svelte'
|
|
16
|
+
import DensityControl from '../controls/DensityControl.svelte'
|
|
17
|
+
import EdgeStyleControl from '../controls/EdgeStyleControl.svelte'
|
|
18
|
+
import ZoomControl from '../controls/ZoomControl.svelte'
|
|
19
|
+
import { GraphState } from '../GraphState.svelte.js'
|
|
20
|
+
import type { Density, EdgeStyle } from '../layout/types.js'
|
|
21
|
+
import type { GraphFields } from '../types.js'
|
|
22
|
+
import type { GraphPreset } from '../preset.js'
|
|
23
|
+
|
|
24
|
+
type Props = {
|
|
25
|
+
/**
|
|
26
|
+
* Share one state across this diagram and, say, an entity table beside it. Given one,
|
|
27
|
+
* the caller owns it: this component reads it and never calls `update`, exactly as
|
|
28
|
+
* `Graph` does — two owners writing one state fight on every render.
|
|
29
|
+
*/
|
|
30
|
+
state?: GraphState
|
|
31
|
+
nodes?: unknown[]
|
|
32
|
+
edges?: unknown[]
|
|
33
|
+
fields?: GraphFields
|
|
34
|
+
density?: Density
|
|
35
|
+
edgeStyle?: EdgeStyle
|
|
36
|
+
/** Paint each card with its schema colour. On by default — `flow` has no group boxes. */
|
|
37
|
+
groupTint?: boolean
|
|
38
|
+
/** Show the on-canvas density, edge-style and zoom controls. */
|
|
39
|
+
controls?: boolean
|
|
40
|
+
/** Show a key under the canvas. Keyed by GROUP, which is what the tint encodes. */
|
|
41
|
+
legend?: boolean
|
|
42
|
+
value?: string | null
|
|
43
|
+
preset?: GraphPreset
|
|
44
|
+
mode?: 'light' | 'dark'
|
|
45
|
+
arrows?: boolean
|
|
46
|
+
label?: string
|
|
47
|
+
onselect?: (id: string | null) => void
|
|
48
|
+
class?: string
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
let {
|
|
52
|
+
state: provided,
|
|
53
|
+
nodes = [],
|
|
54
|
+
edges = [],
|
|
55
|
+
fields = {},
|
|
56
|
+
density = $bindable('keys'),
|
|
57
|
+
edgeStyle = $bindable('curved'),
|
|
58
|
+
groupTint = true,
|
|
59
|
+
controls = false,
|
|
60
|
+
legend = false,
|
|
61
|
+
value = $bindable(undefined),
|
|
62
|
+
preset = undefined,
|
|
63
|
+
mode = 'light',
|
|
64
|
+
arrows = true,
|
|
65
|
+
label = undefined,
|
|
66
|
+
onselect = undefined,
|
|
67
|
+
class: className = ''
|
|
68
|
+
}: Props = $props()
|
|
69
|
+
|
|
70
|
+
let zoom = $state(1)
|
|
71
|
+
|
|
72
|
+
const config = () => ({
|
|
73
|
+
nodes,
|
|
74
|
+
edges,
|
|
75
|
+
fields,
|
|
76
|
+
layout: 'flow',
|
|
77
|
+
density,
|
|
78
|
+
edgeStyle,
|
|
79
|
+
groupTint,
|
|
80
|
+
value,
|
|
81
|
+
preset,
|
|
82
|
+
mode,
|
|
83
|
+
label,
|
|
84
|
+
onselect
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
// svelte-ignore state_referenced_locally
|
|
88
|
+
const graph = provided ?? new GraphState(config())
|
|
89
|
+
|
|
90
|
+
$effect(() => {
|
|
91
|
+
if (!provided) {
|
|
92
|
+
graph.update(config())
|
|
93
|
+
|
|
94
|
+
return
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// A supplied state is the caller's: their data, preset, mode and selection stay theirs
|
|
98
|
+
// and are never reset here. What this component DOES own is the options its own
|
|
99
|
+
// controls drive — the layout above all, since choosing this component is choosing it.
|
|
100
|
+
// `apply` merges; `update` would revert everything it does not name.
|
|
101
|
+
const { nodes: _n, edges: _e, fields: _f, preset: _p, mode: _m, value: _v, ...mine } =
|
|
102
|
+
config()
|
|
103
|
+
graph.apply(mine)
|
|
104
|
+
})
|
|
105
|
+
</script>
|
|
106
|
+
|
|
107
|
+
<!-- Snippets are declared OUTSIDE the frame and passed conditionally: one declared
|
|
108
|
+
inside is always defined, so the frame would render an empty control bar over
|
|
109
|
+
every diagram that asked for none. -->
|
|
110
|
+
{#snippet controlBar()}
|
|
111
|
+
<DensityControl {density} onchange={(v) => (density = v)} />
|
|
112
|
+
<EdgeStyleControl {edgeStyle} onchange={(v) => (edgeStyle = v)} />
|
|
113
|
+
<ZoomControl {zoom} onchange={(v) => (zoom = v)} />
|
|
114
|
+
{/snippet}
|
|
115
|
+
|
|
116
|
+
{#snippet legendBar()}
|
|
117
|
+
<GraphLegend state={graph} groups />
|
|
118
|
+
{/snippet}
|
|
119
|
+
|
|
120
|
+
<DiagramFrame
|
|
121
|
+
class={className}
|
|
122
|
+
overlay={controls ? controlBar : undefined}
|
|
123
|
+
footer={legend ? legendBar : undefined}
|
|
124
|
+
>
|
|
125
|
+
{#snippet canvas()}
|
|
126
|
+
<Graph state={graph} bind:zoom {arrows} />
|
|
127
|
+
{/snippet}
|
|
128
|
+
</DiagramFrame>
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { GraphState } from '../GraphState.svelte.js';
|
|
2
|
+
import type { Density, EdgeStyle } from '../layout/types.js';
|
|
3
|
+
import type { GraphFields } from '../types.js';
|
|
4
|
+
import type { GraphPreset } from '../preset.js';
|
|
5
|
+
type Props = {
|
|
6
|
+
/**
|
|
7
|
+
* Share one state across this diagram and, say, an entity table beside it. Given one,
|
|
8
|
+
* the caller owns it: this component reads it and never calls `update`, exactly as
|
|
9
|
+
* `Graph` does — two owners writing one state fight on every render.
|
|
10
|
+
*/
|
|
11
|
+
state?: GraphState;
|
|
12
|
+
nodes?: unknown[];
|
|
13
|
+
edges?: unknown[];
|
|
14
|
+
fields?: GraphFields;
|
|
15
|
+
density?: Density;
|
|
16
|
+
edgeStyle?: EdgeStyle;
|
|
17
|
+
/** Paint each card with its schema colour. On by default — `flow` has no group boxes. */
|
|
18
|
+
groupTint?: boolean;
|
|
19
|
+
/** Show the on-canvas density, edge-style and zoom controls. */
|
|
20
|
+
controls?: boolean;
|
|
21
|
+
/** Show a key under the canvas. Keyed by GROUP, which is what the tint encodes. */
|
|
22
|
+
legend?: boolean;
|
|
23
|
+
value?: string | null;
|
|
24
|
+
preset?: GraphPreset;
|
|
25
|
+
mode?: 'light' | 'dark';
|
|
26
|
+
arrows?: boolean;
|
|
27
|
+
label?: string;
|
|
28
|
+
onselect?: (id: string | null) => void;
|
|
29
|
+
class?: string;
|
|
30
|
+
};
|
|
31
|
+
declare const ErDiagram: import("svelte").Component<Props, {}, "value" | "density" | "edgeStyle">;
|
|
32
|
+
type ErDiagram = ReturnType<typeof ErDiagram>;
|
|
33
|
+
export default ErDiagram;
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* One node and what reaches it, walked a hop at a time.
|
|
4
|
+
*
|
|
5
|
+
* The answer to "what touches this" — and at `depth: 2`, to "what does changing this
|
|
6
|
+
* reach", which is the question a reader has before editing something. Cards are built at
|
|
7
|
+
* full detail unconditionally: a portrait of one node's surroundings that hides the
|
|
8
|
+
* columns its edges land on defeats its own purpose, which is why there is no density
|
|
9
|
+
* control here.
|
|
10
|
+
*/
|
|
11
|
+
import Graph from '../Graph.svelte'
|
|
12
|
+
import GraphLegend from '../GraphLegend.svelte'
|
|
13
|
+
import DiagramFrame from './DiagramFrame.svelte'
|
|
14
|
+
import DepthControl from '../controls/DepthControl.svelte'
|
|
15
|
+
import EdgeStyleControl from '../controls/EdgeStyleControl.svelte'
|
|
16
|
+
import ZoomControl from '../controls/ZoomControl.svelte'
|
|
17
|
+
import { GraphState } from '../GraphState.svelte.js'
|
|
18
|
+
import type { EdgeStyle } from '../layout/types.js'
|
|
19
|
+
import type { GraphFields } from '../types.js'
|
|
20
|
+
import type { GraphPreset } from '../preset.js'
|
|
21
|
+
|
|
22
|
+
type Props = {
|
|
23
|
+
/**
|
|
24
|
+
* Share one state across this diagram and, say, an entity table beside it. Given one,
|
|
25
|
+
* the caller owns it: this component reads it and never calls `update`, exactly as
|
|
26
|
+
* `Graph` does — two owners writing one state fight on every render.
|
|
27
|
+
*/
|
|
28
|
+
state?: GraphState
|
|
29
|
+
nodes?: unknown[]
|
|
30
|
+
edges?: unknown[]
|
|
31
|
+
fields?: GraphFields
|
|
32
|
+
/** The node to centre. Defaults to the selection. */
|
|
33
|
+
focus?: string | null
|
|
34
|
+
/** How many hops out. One answers "what touches this"; two, "what does this reach". */
|
|
35
|
+
depth?: number
|
|
36
|
+
edgeStyle?: EdgeStyle
|
|
37
|
+
controls?: boolean
|
|
38
|
+
maxDepth?: number
|
|
39
|
+
legend?: boolean
|
|
40
|
+
value?: string | null
|
|
41
|
+
preset?: GraphPreset
|
|
42
|
+
mode?: 'light' | 'dark'
|
|
43
|
+
arrows?: boolean
|
|
44
|
+
label?: string
|
|
45
|
+
onselect?: (id: string | null) => void
|
|
46
|
+
class?: string
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
let {
|
|
50
|
+
state: provided,
|
|
51
|
+
nodes = [],
|
|
52
|
+
edges = [],
|
|
53
|
+
fields = {},
|
|
54
|
+
focus = $bindable(null),
|
|
55
|
+
depth = $bindable(1),
|
|
56
|
+
edgeStyle = $bindable('curved'),
|
|
57
|
+
controls = false,
|
|
58
|
+
maxDepth = 3,
|
|
59
|
+
legend = false,
|
|
60
|
+
value = $bindable(undefined),
|
|
61
|
+
preset = undefined,
|
|
62
|
+
mode = 'light',
|
|
63
|
+
arrows = true,
|
|
64
|
+
label = undefined,
|
|
65
|
+
onselect = undefined,
|
|
66
|
+
class: className = ''
|
|
67
|
+
}: Props = $props()
|
|
68
|
+
|
|
69
|
+
let zoom = $state(1)
|
|
70
|
+
|
|
71
|
+
const config = () => ({
|
|
72
|
+
nodes,
|
|
73
|
+
edges,
|
|
74
|
+
fields,
|
|
75
|
+
layout: 'neighborhood',
|
|
76
|
+
focus,
|
|
77
|
+
depth,
|
|
78
|
+
edgeStyle,
|
|
79
|
+
value,
|
|
80
|
+
preset,
|
|
81
|
+
mode,
|
|
82
|
+
label,
|
|
83
|
+
onselect
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
// svelte-ignore state_referenced_locally
|
|
87
|
+
const graph = provided ?? new GraphState(config())
|
|
88
|
+
|
|
89
|
+
$effect(() => {
|
|
90
|
+
if (!provided) {
|
|
91
|
+
graph.update(config())
|
|
92
|
+
|
|
93
|
+
return
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// A supplied state is the caller's: their data, preset, mode and selection stay theirs
|
|
97
|
+
// and are never reset here. What this component DOES own is the options its own
|
|
98
|
+
// controls drive — the layout above all, since choosing this component is choosing it.
|
|
99
|
+
// `apply` merges; `update` would revert everything it does not name.
|
|
100
|
+
const { nodes: _n, edges: _e, fields: _f, preset: _p, mode: _m, value: _v, ...mine } =
|
|
101
|
+
config()
|
|
102
|
+
graph.apply(mine)
|
|
103
|
+
})
|
|
104
|
+
</script>
|
|
105
|
+
|
|
106
|
+
<!-- Snippets are declared OUTSIDE the frame and passed conditionally: one declared
|
|
107
|
+
inside is always defined, so the frame would render an empty control bar over
|
|
108
|
+
every diagram that asked for none. -->
|
|
109
|
+
{#snippet controlBar()}
|
|
110
|
+
<DepthControl levels={depth} max={maxDepth} onchange={(v) => (depth = v)} />
|
|
111
|
+
<EdgeStyleControl {edgeStyle} onchange={(v) => (edgeStyle = v)} />
|
|
112
|
+
<ZoomControl {zoom} onchange={(v) => (zoom = v)} />
|
|
113
|
+
{/snippet}
|
|
114
|
+
|
|
115
|
+
{#snippet legendBar()}
|
|
116
|
+
<GraphLegend state={graph} kinds relations />
|
|
117
|
+
{/snippet}
|
|
118
|
+
|
|
119
|
+
<DiagramFrame
|
|
120
|
+
class={className}
|
|
121
|
+
overlay={controls ? controlBar : undefined}
|
|
122
|
+
footer={legend ? legendBar : undefined}
|
|
123
|
+
>
|
|
124
|
+
{#snippet canvas()}
|
|
125
|
+
<Graph state={graph} bind:zoom {arrows} />
|
|
126
|
+
{/snippet}
|
|
127
|
+
</DiagramFrame>
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { GraphState } from '../GraphState.svelte.js';
|
|
2
|
+
import type { EdgeStyle } from '../layout/types.js';
|
|
3
|
+
import type { GraphFields } from '../types.js';
|
|
4
|
+
import type { GraphPreset } from '../preset.js';
|
|
5
|
+
type Props = {
|
|
6
|
+
/**
|
|
7
|
+
* Share one state across this diagram and, say, an entity table beside it. Given one,
|
|
8
|
+
* the caller owns it: this component reads it and never calls `update`, exactly as
|
|
9
|
+
* `Graph` does — two owners writing one state fight on every render.
|
|
10
|
+
*/
|
|
11
|
+
state?: GraphState;
|
|
12
|
+
nodes?: unknown[];
|
|
13
|
+
edges?: unknown[];
|
|
14
|
+
fields?: GraphFields;
|
|
15
|
+
/** The node to centre. Defaults to the selection. */
|
|
16
|
+
focus?: string | null;
|
|
17
|
+
/** How many hops out. One answers "what touches this"; two, "what does this reach". */
|
|
18
|
+
depth?: number;
|
|
19
|
+
edgeStyle?: EdgeStyle;
|
|
20
|
+
controls?: boolean;
|
|
21
|
+
maxDepth?: number;
|
|
22
|
+
legend?: boolean;
|
|
23
|
+
value?: string | null;
|
|
24
|
+
preset?: GraphPreset;
|
|
25
|
+
mode?: 'light' | 'dark';
|
|
26
|
+
arrows?: boolean;
|
|
27
|
+
label?: string;
|
|
28
|
+
onselect?: (id: string | null) => void;
|
|
29
|
+
class?: string;
|
|
30
|
+
};
|
|
31
|
+
declare const Neighborhood: import("svelte").Component<Props, {}, "focus" | "value" | "edgeStyle" | "depth">;
|
|
32
|
+
type Neighborhood = ReturnType<typeof Neighborhood>;
|
|
33
|
+
export default Neighborhood;
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* A codebase as a radial dendrogram, with its calls bundled through the structure.
|
|
4
|
+
*
|
|
5
|
+
* What `CallTree` cannot be over a real repo. A call graph's own spanning tree has hundreds
|
|
6
|
+
* of roots, so capping its depth prunes almost nothing and the rim comes out a grey smear.
|
|
7
|
+
* Containment — crate, module, file — is a real tree with one root, which is what makes
|
|
8
|
+
* "show two levels, then drill" mean something.
|
|
9
|
+
*
|
|
10
|
+
* The calls are drawn ON that tree rather than defining it, bundled so edges travelling the
|
|
11
|
+
* same way through the hierarchy pull together. `bundleTension` is the Straight/Bundled
|
|
12
|
+
* toggle: 0 is a plain chord, 0.85 the full route.
|
|
13
|
+
*/
|
|
14
|
+
import Graph from '../Graph.svelte'
|
|
15
|
+
import GraphLegend from '../GraphLegend.svelte'
|
|
16
|
+
import DiagramFrame from './DiagramFrame.svelte'
|
|
17
|
+
import DepthControl from '../controls/DepthControl.svelte'
|
|
18
|
+
import BundleControl from '../controls/BundleControl.svelte'
|
|
19
|
+
import ZoomControl from '../controls/ZoomControl.svelte'
|
|
20
|
+
import { GraphState } from '../GraphState.svelte.js'
|
|
21
|
+
import type { GraphFields } from '../types.js'
|
|
22
|
+
import type { GraphPreset } from '../preset.js'
|
|
23
|
+
|
|
24
|
+
type Props = {
|
|
25
|
+
/** Share one state with views beside this one. See `ErDiagram` for the contract. */
|
|
26
|
+
state?: GraphState
|
|
27
|
+
nodes?: unknown[]
|
|
28
|
+
edges?: unknown[]
|
|
29
|
+
fields?: GraphFields
|
|
30
|
+
/** How many ancestor bands to draw, counting from the focus. */
|
|
31
|
+
levels?: number
|
|
32
|
+
/** The subtree to render as the whole canvas. `[]` is the root. Drilling, not zooming. */
|
|
33
|
+
focusPath?: string[]
|
|
34
|
+
/** How hard edges are pulled onto the tree, 0..1. 0 is a straight chord. */
|
|
35
|
+
bundleTension?: number
|
|
36
|
+
/** What a leaf's dot is sized by. */
|
|
37
|
+
sizeBy?: string
|
|
38
|
+
sizeScale?: 'linear' | 'log'
|
|
39
|
+
controls?: boolean
|
|
40
|
+
maxLevels?: number
|
|
41
|
+
legend?: boolean
|
|
42
|
+
value?: string | null
|
|
43
|
+
preset?: GraphPreset
|
|
44
|
+
mode?: 'light' | 'dark'
|
|
45
|
+
label?: string
|
|
46
|
+
onselect?: (id: string | null) => void
|
|
47
|
+
class?: string
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
let {
|
|
51
|
+
state: provided,
|
|
52
|
+
nodes = [],
|
|
53
|
+
edges = [],
|
|
54
|
+
fields = {},
|
|
55
|
+
levels = $bindable(2),
|
|
56
|
+
focusPath = $bindable([]),
|
|
57
|
+
bundleTension = $bindable(0.85),
|
|
58
|
+
sizeBy = 'degree',
|
|
59
|
+
sizeScale = 'linear',
|
|
60
|
+
controls = false,
|
|
61
|
+
maxLevels = 4,
|
|
62
|
+
legend = false,
|
|
63
|
+
value = $bindable(undefined),
|
|
64
|
+
preset = undefined,
|
|
65
|
+
mode = 'light',
|
|
66
|
+
label = undefined,
|
|
67
|
+
onselect = undefined,
|
|
68
|
+
class: className = ''
|
|
69
|
+
}: Props = $props()
|
|
70
|
+
|
|
71
|
+
let zoom = $state(1)
|
|
72
|
+
|
|
73
|
+
const config = () => ({
|
|
74
|
+
nodes,
|
|
75
|
+
edges,
|
|
76
|
+
fields,
|
|
77
|
+
layout: 'structure',
|
|
78
|
+
levels,
|
|
79
|
+
focusPath,
|
|
80
|
+
bundleTension,
|
|
81
|
+
sizeBy,
|
|
82
|
+
sizeScale,
|
|
83
|
+
value,
|
|
84
|
+
preset,
|
|
85
|
+
mode,
|
|
86
|
+
label,
|
|
87
|
+
onselect
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
// svelte-ignore state_referenced_locally
|
|
91
|
+
const graph = provided ?? new GraphState(config())
|
|
92
|
+
|
|
93
|
+
$effect(() => {
|
|
94
|
+
if (!provided) {
|
|
95
|
+
graph.update(config())
|
|
96
|
+
|
|
97
|
+
return
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// A supplied state is the caller's: their data, preset, mode and selection stay theirs
|
|
101
|
+
// and are never reset here. What this component DOES own is the options its own
|
|
102
|
+
// controls drive — the layout above all, since choosing this component is choosing it.
|
|
103
|
+
// `apply` merges; `update` would revert everything it does not name.
|
|
104
|
+
const { nodes: _n, edges: _e, fields: _f, preset: _p, mode: _m, value: _v, ...mine } =
|
|
105
|
+
config()
|
|
106
|
+
graph.apply(mine)
|
|
107
|
+
})
|
|
108
|
+
</script>
|
|
109
|
+
|
|
110
|
+
<!-- Snippets are declared OUTSIDE the frame and passed conditionally: one declared
|
|
111
|
+
inside is always defined, so the frame would render an empty control bar over
|
|
112
|
+
every diagram that asked for none. -->
|
|
113
|
+
{#snippet controlBar()}
|
|
114
|
+
<DepthControl {levels} max={maxLevels} onchange={(v) => (levels = v)} />
|
|
115
|
+
<BundleControl {bundleTension} onchange={(v) => (bundleTension = v)} />
|
|
116
|
+
<ZoomControl {zoom} onchange={(v) => (zoom = v)} />
|
|
117
|
+
{/snippet}
|
|
118
|
+
|
|
119
|
+
{#snippet legendBar()}
|
|
120
|
+
<GraphLegend state={graph} relations groups />
|
|
121
|
+
{/snippet}
|
|
122
|
+
|
|
123
|
+
<DiagramFrame
|
|
124
|
+
class={className}
|
|
125
|
+
overlay={controls ? controlBar : undefined}
|
|
126
|
+
footer={legend ? legendBar : undefined}
|
|
127
|
+
>
|
|
128
|
+
{#snippet canvas()}
|
|
129
|
+
<Graph state={graph} bind:zoom arrows={false} />
|
|
130
|
+
{/snippet}
|
|
131
|
+
</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 views beside this one. See `ErDiagram` for the contract. */
|
|
6
|
+
state?: GraphState;
|
|
7
|
+
nodes?: unknown[];
|
|
8
|
+
edges?: unknown[];
|
|
9
|
+
fields?: GraphFields;
|
|
10
|
+
/** How many ancestor bands to draw, counting from the focus. */
|
|
11
|
+
levels?: number;
|
|
12
|
+
/** The subtree to render as the whole canvas. `[]` is the root. Drilling, not zooming. */
|
|
13
|
+
focusPath?: string[];
|
|
14
|
+
/** How hard edges are pulled onto the tree, 0..1. 0 is a straight chord. */
|
|
15
|
+
bundleTension?: number;
|
|
16
|
+
/** What a leaf's dot is sized by. */
|
|
17
|
+
sizeBy?: string;
|
|
18
|
+
sizeScale?: 'linear' | 'log';
|
|
19
|
+
controls?: boolean;
|
|
20
|
+
maxLevels?: number;
|
|
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 StructureDiagram: import("svelte").Component<Props, {}, "value" | "focusPath" | "levels" | "bundleTension">;
|
|
30
|
+
type StructureDiagram = ReturnType<typeof StructureDiagram>;
|
|
31
|
+
export default StructureDiagram;
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* The same containment as a treemap, drawn as nested wedges.
|
|
4
|
+
*
|
|
5
|
+
* Angle carries the measure and RADIUS carries the depth, so how deep the tree goes is the
|
|
6
|
+
* first thing you read — which is exactly what a treemap makes you hunt for. A treemap
|
|
7
|
+
* spends its pixels better; neither is the right one, which is why both exist.
|
|
8
|
+
*
|
|
9
|
+
* Same tree, same measure, same captions as `Treemap`, so switching between them keeps
|
|
10
|
+
* your place.
|
|
11
|
+
*/
|
|
12
|
+
import Graph from '../Graph.svelte'
|
|
13
|
+
import GraphLegend from '../GraphLegend.svelte'
|
|
14
|
+
import DiagramFrame from './DiagramFrame.svelte'
|
|
15
|
+
import DepthControl from '../controls/DepthControl.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
|
+
/**
|
|
23
|
+
* Share one state across this diagram and, say, an entity table beside it. Given one,
|
|
24
|
+
* the caller owns it: this component reads it and never calls `update`, exactly as
|
|
25
|
+
* `Graph` does — two owners writing one state fight on every render.
|
|
26
|
+
*/
|
|
27
|
+
state?: GraphState
|
|
28
|
+
nodes?: unknown[]
|
|
29
|
+
edges?: unknown[]
|
|
30
|
+
fields?: GraphFields
|
|
31
|
+
/** What area encodes. A `measures` key, or `weight`, or `degree`. */
|
|
32
|
+
sizeBy?: string
|
|
33
|
+
/** How many levels below the focus to materialise. */
|
|
34
|
+
levels?: number
|
|
35
|
+
/** The subtree to render as the whole canvas. `[]` is the root. Drilling, not zooming. */
|
|
36
|
+
focusPath?: string[]
|
|
37
|
+
controls?: boolean
|
|
38
|
+
maxLevels?: number
|
|
39
|
+
legend?: boolean
|
|
40
|
+
value?: string | null
|
|
41
|
+
preset?: GraphPreset
|
|
42
|
+
mode?: 'light' | 'dark'
|
|
43
|
+
label?: string
|
|
44
|
+
onselect?: (id: string | null) => void
|
|
45
|
+
class?: string
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
let {
|
|
49
|
+
state: provided,
|
|
50
|
+
nodes = [],
|
|
51
|
+
edges = [],
|
|
52
|
+
fields = {},
|
|
53
|
+
sizeBy = 'weight',
|
|
54
|
+
levels = $bindable(2),
|
|
55
|
+
focusPath = $bindable([]),
|
|
56
|
+
controls = false,
|
|
57
|
+
maxLevels = 4,
|
|
58
|
+
legend = false,
|
|
59
|
+
value = $bindable(undefined),
|
|
60
|
+
preset = undefined,
|
|
61
|
+
mode = 'light',
|
|
62
|
+
label = undefined,
|
|
63
|
+
onselect = undefined,
|
|
64
|
+
class: className = ''
|
|
65
|
+
}: Props = $props()
|
|
66
|
+
|
|
67
|
+
let zoom = $state(1)
|
|
68
|
+
|
|
69
|
+
const config = () => ({
|
|
70
|
+
nodes,
|
|
71
|
+
edges,
|
|
72
|
+
fields,
|
|
73
|
+
layout: 'sunburst',
|
|
74
|
+
sizeBy,
|
|
75
|
+
levels,
|
|
76
|
+
focusPath,
|
|
77
|
+
value,
|
|
78
|
+
preset,
|
|
79
|
+
mode,
|
|
80
|
+
label,
|
|
81
|
+
onselect
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
// svelte-ignore state_referenced_locally
|
|
85
|
+
const graph = provided ?? new GraphState(config())
|
|
86
|
+
|
|
87
|
+
$effect(() => {
|
|
88
|
+
if (!provided) {
|
|
89
|
+
graph.update(config())
|
|
90
|
+
|
|
91
|
+
return
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// A supplied state is the caller's: their data, preset, mode and selection stay theirs
|
|
95
|
+
// and are never reset here. What this component DOES own is the options its own
|
|
96
|
+
// controls drive — the layout above all, since choosing this component is choosing it.
|
|
97
|
+
// `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()
|
|
100
|
+
graph.apply(mine)
|
|
101
|
+
})
|
|
102
|
+
</script>
|
|
103
|
+
|
|
104
|
+
<!-- Snippets are declared OUTSIDE the frame and passed conditionally: one declared
|
|
105
|
+
inside is always defined, so the frame would render an empty control bar over
|
|
106
|
+
every diagram that asked for none. -->
|
|
107
|
+
{#snippet controlBar()}
|
|
108
|
+
<DepthControl {levels} max={maxLevels} onchange={(v) => (levels = v)} />
|
|
109
|
+
<ZoomControl {zoom} onchange={(v) => (zoom = v)} />
|
|
110
|
+
{/snippet}
|
|
111
|
+
|
|
112
|
+
{#snippet legendBar()}
|
|
113
|
+
<GraphLegend state={graph} groups />
|
|
114
|
+
{/snippet}
|
|
115
|
+
|
|
116
|
+
<DiagramFrame
|
|
117
|
+
class={className}
|
|
118
|
+
overlay={controls ? controlBar : undefined}
|
|
119
|
+
footer={legend ? legendBar : undefined}
|
|
120
|
+
>
|
|
121
|
+
{#snippet canvas()}
|
|
122
|
+
<Graph state={graph} bind:zoom arrows={false} />
|
|
123
|
+
{/snippet}
|
|
124
|
+
</DiagramFrame>
|