@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.
- package/README.md +67 -1
- package/dist/Graph.svelte +234 -259
- 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 +168 -51
- package/dist/GraphState.svelte.js +406 -305
- 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 +156 -0
- package/dist/diagrams/DependencyMatrix.svelte.d.ts +34 -0
- 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 +13 -3
- package/dist/index.js +11 -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 +35 -0
- 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 +50 -0
- package/dist/layout/matrix.js +110 -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 +83 -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 +83 -5
- 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 +92 -1
- package/package.json +5 -4
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { SvelteSet } from 'svelte/reactivity';
|
|
2
|
+
import type { Relationship } from '../model/relationships.js';
|
|
3
|
+
import type { RoutedEdge } from '../layout/types.js';
|
|
4
|
+
import type { GraphModel, GraphNode } from '../types.js';
|
|
5
|
+
type Sources = {
|
|
6
|
+
/** The canonical model. */
|
|
7
|
+
model: () => GraphModel;
|
|
8
|
+
/** The structural edges the active layout routed — geometry for relationships. */
|
|
9
|
+
routedEdges: () => RoutedEdge[];
|
|
10
|
+
/** The consumer's selection callback, read at call time (it is config, and can change). */
|
|
11
|
+
onselect: () => ((id: string | null) => void) | undefined;
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* What the reader has picked and opened: the selected node, what it touches, and which cards
|
|
15
|
+
* are expanded — plus the transitions that change them.
|
|
16
|
+
*/
|
|
17
|
+
export declare class GraphSelection {
|
|
18
|
+
#private;
|
|
19
|
+
constructor(sources: Sources);
|
|
20
|
+
get value(): string | null;
|
|
21
|
+
get related(): SvelteSet<string>;
|
|
22
|
+
get entity(): GraphNode | null;
|
|
23
|
+
get relationships(): Relationship[];
|
|
24
|
+
/** Take a value the CALLER supplied — no callback, it already knows. */
|
|
25
|
+
adopt(id: string | null): void;
|
|
26
|
+
select(id: string): void;
|
|
27
|
+
/**
|
|
28
|
+
* Drop the selection, and SAY SO.
|
|
29
|
+
*
|
|
30
|
+
* Found by dbd consuming the package: it owns `selected` in its route and branches on it
|
|
31
|
+
* to show the entity panel, so a silent clear left that panel open over nothing. A
|
|
32
|
+
* controlled consumer cannot observe an internal value — the callback is the only
|
|
33
|
+
* channel, which is why it carries `null` rather than being skipped.
|
|
34
|
+
*
|
|
35
|
+
* Guarded on an actual change, or a background click on an already-empty canvas
|
|
36
|
+
* round-trips through the consumer's setter on every stray click.
|
|
37
|
+
*/
|
|
38
|
+
clear(): void;
|
|
39
|
+
/**
|
|
40
|
+
* Show one node's rows in full, whatever the density says.
|
|
41
|
+
*
|
|
42
|
+
* Per NODE rather than a global density change: a reader who clicks "+3 more" on one
|
|
43
|
+
* card is asking about that card, and expanding all of them answers a question they did
|
|
44
|
+
* not ask — on a large diagram it also relayouts everything under them.
|
|
45
|
+
*/
|
|
46
|
+
toggleExpanded(id: string): void;
|
|
47
|
+
isExpanded(id: string): boolean;
|
|
48
|
+
/** The set the layouts read to size expanded cards. */
|
|
49
|
+
get expanded(): SvelteSet<string>;
|
|
50
|
+
nodeState(id: string): 'selected' | 'related' | 'dim' | null;
|
|
51
|
+
edgeState(edge: RoutedEdge): 'highlight' | 'dim' | null;
|
|
52
|
+
}
|
|
53
|
+
export {};
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { SvelteSet } from 'svelte/reactivity';
|
|
2
|
+
import { relationshipsOf } from '../model/relationships.js';
|
|
3
|
+
/**
|
|
4
|
+
* What the reader has picked and opened: the selected node, what it touches, and which cards
|
|
5
|
+
* are expanded — plus the transitions that change them.
|
|
6
|
+
*/
|
|
7
|
+
export class GraphSelection {
|
|
8
|
+
#sources;
|
|
9
|
+
#value = $state(null);
|
|
10
|
+
#expanded = new SvelteSet();
|
|
11
|
+
#related;
|
|
12
|
+
#entity;
|
|
13
|
+
/** From the canonical model, not the layout's filtered edges — see `relationshipsOf`. */
|
|
14
|
+
#relationships;
|
|
15
|
+
constructor(sources) {
|
|
16
|
+
this.#sources = sources;
|
|
17
|
+
// Derived in the constructor, after `#sources` exists — a field initializer would read
|
|
18
|
+
// it before it is assigned.
|
|
19
|
+
this.#related = $derived(this.#value
|
|
20
|
+
? new SvelteSet(sources.model().neighbors.get(this.#value) ?? [])
|
|
21
|
+
: new SvelteSet());
|
|
22
|
+
this.#entity = $derived(this.#value ? (sources.model().byId.get(this.#value) ?? null) : null);
|
|
23
|
+
this.#relationships = $derived(relationshipsOf(sources.model(), this.#value, sources.routedEdges()));
|
|
24
|
+
}
|
|
25
|
+
get value() {
|
|
26
|
+
return this.#value;
|
|
27
|
+
}
|
|
28
|
+
get related() {
|
|
29
|
+
return this.#related;
|
|
30
|
+
}
|
|
31
|
+
get entity() {
|
|
32
|
+
return this.#entity;
|
|
33
|
+
}
|
|
34
|
+
get relationships() {
|
|
35
|
+
return this.#relationships;
|
|
36
|
+
}
|
|
37
|
+
/** Take a value the CALLER supplied — no callback, it already knows. */
|
|
38
|
+
adopt(id) {
|
|
39
|
+
this.#value = id;
|
|
40
|
+
}
|
|
41
|
+
select(id) {
|
|
42
|
+
this.#value = id;
|
|
43
|
+
this.#sources.onselect()?.(id);
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Drop the selection, and SAY SO.
|
|
47
|
+
*
|
|
48
|
+
* Found by dbd consuming the package: it owns `selected` in its route and branches on it
|
|
49
|
+
* to show the entity panel, so a silent clear left that panel open over nothing. A
|
|
50
|
+
* controlled consumer cannot observe an internal value — the callback is the only
|
|
51
|
+
* channel, which is why it carries `null` rather than being skipped.
|
|
52
|
+
*
|
|
53
|
+
* Guarded on an actual change, or a background click on an already-empty canvas
|
|
54
|
+
* round-trips through the consumer's setter on every stray click.
|
|
55
|
+
*/
|
|
56
|
+
clear() {
|
|
57
|
+
if (this.#value === null)
|
|
58
|
+
return;
|
|
59
|
+
this.#value = null;
|
|
60
|
+
this.#sources.onselect()?.(null);
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Show one node's rows in full, whatever the density says.
|
|
64
|
+
*
|
|
65
|
+
* Per NODE rather than a global density change: a reader who clicks "+3 more" on one
|
|
66
|
+
* card is asking about that card, and expanding all of them answers a question they did
|
|
67
|
+
* not ask — on a large diagram it also relayouts everything under them.
|
|
68
|
+
*/
|
|
69
|
+
toggleExpanded(id) {
|
|
70
|
+
if (this.#expanded.has(id))
|
|
71
|
+
this.#expanded.delete(id);
|
|
72
|
+
else
|
|
73
|
+
this.#expanded.add(id);
|
|
74
|
+
}
|
|
75
|
+
isExpanded(id) {
|
|
76
|
+
return this.#expanded.has(id);
|
|
77
|
+
}
|
|
78
|
+
/** The set the layouts read to size expanded cards. */
|
|
79
|
+
get expanded() {
|
|
80
|
+
return this.#expanded;
|
|
81
|
+
}
|
|
82
|
+
nodeState(id) {
|
|
83
|
+
if (!this.#value)
|
|
84
|
+
return null;
|
|
85
|
+
if (id === this.#value)
|
|
86
|
+
return 'selected';
|
|
87
|
+
return this.#related.has(id) ? 'related' : 'dim';
|
|
88
|
+
}
|
|
89
|
+
edgeState(edge) {
|
|
90
|
+
if (!this.#value)
|
|
91
|
+
return null;
|
|
92
|
+
return edge.fromKey === this.#value || edge.toKey === this.#value ? 'highlight' : 'dim';
|
|
93
|
+
}
|
|
94
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a press or a double-click on the graph does, as data.
|
|
3
|
+
*
|
|
4
|
+
* The state writes an intent onto each element (`data-graph-press`, `data-graph-open`, with
|
|
5
|
+
* `data-graph-key`); the `interactions` action reads it back and calls `GraphState.act`, which
|
|
6
|
+
* looks it up here. So the meaning of a click lives in one table — not in a handler on every
|
|
7
|
+
* element — and each entry is testable on a bare state.
|
|
8
|
+
*/
|
|
9
|
+
import type { Breadcrumb } from './GraphDrill.svelte.js';
|
|
10
|
+
import type { GroupAction } from './GraphGroups.svelte.js';
|
|
11
|
+
import type { Cluster } from '../layout/types.js';
|
|
12
|
+
export type Intent = 'select' | 'toggle' | 'drill' | 'group' | 'expand' | 'crumb' | 'open-selected' | 'group-selected' | 'clear';
|
|
13
|
+
/** What an intent may call — the slice of `GraphState` the table needs. */
|
|
14
|
+
export type Actor = {
|
|
15
|
+
select(id: string): void;
|
|
16
|
+
clear(): void;
|
|
17
|
+
readonly value: string | null;
|
|
18
|
+
drillInto(cluster: Cluster): unknown;
|
|
19
|
+
drillTo(path: string[]): unknown;
|
|
20
|
+
toggleGroup(id: string): void;
|
|
21
|
+
toggleExpanded(id: string): void;
|
|
22
|
+
isGroup(id: string): boolean;
|
|
23
|
+
groupOf(id: string): string | null;
|
|
24
|
+
boxFor(key: string): Cluster | undefined;
|
|
25
|
+
readonly breadcrumbs: Breadcrumb[];
|
|
26
|
+
readonly drillTarget: Cluster | null;
|
|
27
|
+
readonly groupAction: GroupAction | null;
|
|
28
|
+
};
|
|
29
|
+
type Perform = (graph: Actor, key: string) => void;
|
|
30
|
+
export declare const INTENTS: Record<Intent, Perform>;
|
|
31
|
+
/** Whether `value` names an intent — the action reads it from the DOM, where anything goes. */
|
|
32
|
+
export declare const isIntent: (value: string | null | undefined) => value is Intent;
|
|
33
|
+
export {};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/** A group toggles itself; a member of an open group folds that group back. */
|
|
2
|
+
const groupFor = (graph, key) => (graph.isGroup(key) ? key : (graph.groupOf(key) ?? key));
|
|
3
|
+
export const INTENTS = {
|
|
4
|
+
select: (graph, key) => graph.select(key),
|
|
5
|
+
/** Select, or clear when it is already the selection — a matrix row's press. */
|
|
6
|
+
toggle: (graph, key) => (graph.value === key ? graph.clear() : graph.select(key)),
|
|
7
|
+
drill: (graph, key) => {
|
|
8
|
+
const cluster = graph.boxFor(key);
|
|
9
|
+
if (cluster)
|
|
10
|
+
graph.drillInto(cluster);
|
|
11
|
+
},
|
|
12
|
+
group: (graph, key) => graph.toggleGroup(groupFor(graph, key)),
|
|
13
|
+
expand: (graph, key) => graph.toggleExpanded(key),
|
|
14
|
+
crumb: (graph, key) => {
|
|
15
|
+
const crumb = graph.breadcrumbs[Number(key)];
|
|
16
|
+
if (crumb)
|
|
17
|
+
graph.drillTo(crumb.path);
|
|
18
|
+
},
|
|
19
|
+
'open-selected': (graph) => {
|
|
20
|
+
if (graph.drillTarget)
|
|
21
|
+
graph.drillInto(graph.drillTarget);
|
|
22
|
+
},
|
|
23
|
+
'group-selected': (graph) => {
|
|
24
|
+
if (graph.groupAction)
|
|
25
|
+
graph.toggleGroup(graph.groupAction.group);
|
|
26
|
+
},
|
|
27
|
+
clear: (graph) => graph.clear()
|
|
28
|
+
};
|
|
29
|
+
/** Whether `value` names an intent — the action reads it from the DOM, where anything goes. */
|
|
30
|
+
export const isIntent = (value) => value !== null && value !== undefined && Object.hasOwn(INTENTS, value);
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { Card, ChannelScale, Density, LayoutResult, PolymetricChannel } from '../layout/types.js';
|
|
2
|
+
import type { GraphEdge } from '../types.js';
|
|
3
|
+
/** The default accessible name: what the diagram holds. */
|
|
4
|
+
export declare function diagramLabel(nodes: number, edges: number): string;
|
|
5
|
+
/**
|
|
6
|
+
* The "more rows" control on a card: `show less` when expanded, `+ N more` when rows are
|
|
7
|
+
* hidden, and `no keys · N rows` when a key filter matched nothing (see `GraphState.moreLabel`).
|
|
8
|
+
*/
|
|
9
|
+
export declare function moreRowsLabel(expanded: boolean, card: Pick<Card, 'more' | 'vis'> | undefined, density: Density): string | null;
|
|
10
|
+
export type ChannelRow = {
|
|
11
|
+
key: PolymetricChannel;
|
|
12
|
+
label: string;
|
|
13
|
+
scale: ChannelScale;
|
|
14
|
+
};
|
|
15
|
+
/** One legend row per polymetric channel present, labelled from the locale (#168). */
|
|
16
|
+
export declare function channelRows(channels: LayoutResult['channels']): ChannelRow[];
|
|
17
|
+
/**
|
|
18
|
+
* What each side of an arc diagram shows (#169): the relations drawn there, from the data, or
|
|
19
|
+
* Declared / Observed from the locale when a side's edges name none.
|
|
20
|
+
*/
|
|
21
|
+
export declare function sideNames(edges: GraphEdge[], above: string | undefined): {
|
|
22
|
+
below: string;
|
|
23
|
+
above: string;
|
|
24
|
+
};
|
|
25
|
+
/** What a failed drill says: the error's own message, in the locale's sentence. */
|
|
26
|
+
export declare function drillErrorText(error: unknown): string | null;
|
|
27
|
+
/** Underscores are the WIRE format (dbd sends `materialized_view`), not something to read. */
|
|
28
|
+
export declare const readable: (value: string) => string;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The words the state produces, from `messages.graph` (see `messages.ts`). Pure functions of
|
|
3
|
+
* their inputs and the active locale, so the canvas has no English of its own and each is
|
|
4
|
+
* testable without a renderer.
|
|
5
|
+
*/
|
|
6
|
+
import { counted, say } from '../messages.js';
|
|
7
|
+
import { sideOf } from '../layout/arcs.js';
|
|
8
|
+
/** The default accessible name: what the diagram holds. */
|
|
9
|
+
export function diagramLabel(nodes, edges) {
|
|
10
|
+
return say('diagram', {
|
|
11
|
+
nodes: counted(nodes, 'nodeOne', 'nodeMany'),
|
|
12
|
+
edges: counted(edges, 'relationshipOne', 'relationshipMany')
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* The "more rows" control on a card: `show less` when expanded, `+ N more` when rows are
|
|
17
|
+
* hidden, and `no keys · N rows` when a key filter matched nothing (see `GraphState.moreLabel`).
|
|
18
|
+
*/
|
|
19
|
+
export function moreRowsLabel(expanded, card, density) {
|
|
20
|
+
if (expanded)
|
|
21
|
+
return say('less');
|
|
22
|
+
if (!card || card.more <= 0)
|
|
23
|
+
return null;
|
|
24
|
+
// Only `keys` filters rows by a property — at `names` an empty card is what was asked for.
|
|
25
|
+
if (density !== 'keys' || card.vis.length > 0)
|
|
26
|
+
return say('more', { n: card.more });
|
|
27
|
+
return say('noKeys', { rows: counted(card.more, 'rowOne', 'rowMany') });
|
|
28
|
+
}
|
|
29
|
+
const CHANNEL_LABEL = { width: 'width', height: 'height', color: 'shade' };
|
|
30
|
+
/** One legend row per polymetric channel present, labelled from the locale (#168). */
|
|
31
|
+
export function channelRows(channels) {
|
|
32
|
+
if (!channels)
|
|
33
|
+
return [];
|
|
34
|
+
const keys = channels.color ? ['width', 'height', 'color'] : ['width', 'height'];
|
|
35
|
+
return keys.map((key) => ({ key, label: say(CHANNEL_LABEL[key]), scale: channels[key] }));
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* What each side of an arc diagram shows (#169): the relations drawn there, from the data, or
|
|
39
|
+
* Declared / Observed from the locale when a side's edges name none.
|
|
40
|
+
*/
|
|
41
|
+
export function sideNames(edges, above) {
|
|
42
|
+
const named = { below: new Set(), above: new Set() };
|
|
43
|
+
for (const edge of edges)
|
|
44
|
+
if (edge.relation)
|
|
45
|
+
named[sideOf(edge, above)].add(edge.relation);
|
|
46
|
+
const name = (side, fallback) => named[side].size > 0 ? [...named[side]].join(' · ') : say(fallback);
|
|
47
|
+
return { below: name('below', 'sideBelow'), above: name('above', 'sideAbove') };
|
|
48
|
+
}
|
|
49
|
+
/** What a failed drill says: the error's own message, in the locale's sentence. */
|
|
50
|
+
export function drillErrorText(error) {
|
|
51
|
+
if (error === null || error === undefined)
|
|
52
|
+
return null;
|
|
53
|
+
return say('drillError', { message: error instanceof Error ? error.message : String(error) });
|
|
54
|
+
}
|
|
55
|
+
/** Underscores are the WIRE format (dbd sends `materialized_view`), not something to read. */
|
|
56
|
+
export const readable = (value) => value.replace(/_/g, ' ');
|
package/dist/types.d.ts
CHANGED
|
@@ -12,7 +12,11 @@ export type GraphRow = {
|
|
|
12
12
|
* (a view reading a table, a procedure calling a function). Layouts branch on
|
|
13
13
|
* this rather than on any schema-specific notion.
|
|
14
14
|
*/
|
|
15
|
-
|
|
15
|
+
/**
|
|
16
|
+
* `reference` (a foreign key) and `dependency` come from the data. `containment` is drawn by a
|
|
17
|
+
* tree layout (`polymetric`) between a container and what it holds — the tree's own links.
|
|
18
|
+
*/
|
|
19
|
+
export type EdgeKind = 'reference' | 'dependency' | 'containment';
|
|
16
20
|
export type GraphNode = {
|
|
17
21
|
/** Stable key. `${group}.${label}` when a group is present, else `label`. */
|
|
18
22
|
id: string;
|
|
@@ -54,8 +58,25 @@ export type GraphNode = {
|
|
|
54
58
|
*
|
|
55
59
|
* Accepts a delimited STRING too (`'dbd/core/lexer/parse'`), since that is how a file path
|
|
56
60
|
* arrives. See `GraphFields.pathDelimiter`.
|
|
61
|
+
*
|
|
62
|
+
* A host that gives containment as `parent` ids instead (#168) need not convert: a node with
|
|
63
|
+
* no path but a parent chain gets one, as ids (`GraphFields.parent`). The path is still what
|
|
64
|
+
* the model holds — the chain is read once, at normalisation.
|
|
57
65
|
*/
|
|
58
66
|
path?: string[];
|
|
67
|
+
/**
|
|
68
|
+
* The node ids this node STANDS FOR — a group, typically one strongly-connected component
|
|
69
|
+
* the host condensed (#166). rokkit computes no SCCs: it draws what the host grouped. While
|
|
70
|
+
* the group is collapsed its members are hidden and their edges reroute to it.
|
|
71
|
+
*/
|
|
72
|
+
members?: string[];
|
|
73
|
+
/** Whether a group starts collapsed. A node with `members` and no flag is collapsed. */
|
|
74
|
+
collapsed?: boolean;
|
|
75
|
+
/**
|
|
76
|
+
* The layer the HOST assigned (#167) — 0 at the top, foundations at the bottom. rokkit
|
|
77
|
+
* computes no depth; the `layers` layout ranks by this.
|
|
78
|
+
*/
|
|
79
|
+
layer?: number;
|
|
59
80
|
rows: GraphRow[];
|
|
60
81
|
note?: string;
|
|
61
82
|
/** Source fields the map did not claim, passed through untouched. */
|
|
@@ -94,10 +115,46 @@ export type GraphEdge = {
|
|
|
94
115
|
* edge is kept and marked, and it is the renderer's job to dim it.
|
|
95
116
|
*/
|
|
96
117
|
unplaced?: 'source' | 'target' | 'both';
|
|
118
|
+
/**
|
|
119
|
+
* Drawn over the picture but never allowed to shape it — a co-change pair, a layering
|
|
120
|
+
* rule broken, a suggested dependency.
|
|
121
|
+
*
|
|
122
|
+
* Such an edge is exactly what the reader wants to see and exactly what must not re-rank a
|
|
123
|
+
* layered layout or pull a cluster ordering: files that change together with no import
|
|
124
|
+
* between them would otherwise be drawn next to each other, hiding the very coupling the
|
|
125
|
+
* overlay exists to expose. So they never enter `GraphModel.edges`; they live in
|
|
126
|
+
* `GraphModel.overlays` and are routed after the layout has run.
|
|
127
|
+
*/
|
|
128
|
+
overlay?: boolean;
|
|
129
|
+
/** A strength this edge carries — co-change count, call frequency. Drives stroke width. */
|
|
130
|
+
weight?: number;
|
|
131
|
+
/**
|
|
132
|
+
* The cheapest link to cut to break a cycle — the edge with the fewest occurrences (#166).
|
|
133
|
+
* Naming it is the actionable output of a cycles view, so it is styleable on its own
|
|
134
|
+
* (`data-edge-weakest`).
|
|
135
|
+
*/
|
|
136
|
+
weakest?: boolean;
|
|
137
|
+
/**
|
|
138
|
+
* Coupling nothing in the code explains — a pair that changes together with no import
|
|
139
|
+
* between them (#169). The host computes it; the graph only styles it (`data-edge-hidden`).
|
|
140
|
+
*/
|
|
141
|
+
hidden?: boolean;
|
|
142
|
+
/** How many model edges this one stands for, when it aggregates a collapsed group's edges. */
|
|
143
|
+
count?: number;
|
|
144
|
+
/**
|
|
145
|
+
* How the edge sits against the layering (#167): `down` one layer (conformant), `skip` more
|
|
146
|
+
* than one down (legal, worth seeing), `up` climbing (the violation), `level` within a layer.
|
|
147
|
+
* The host says; when it does not, the `layers` layout derives it from the layers.
|
|
148
|
+
*/
|
|
149
|
+
conformance?: Conformance;
|
|
97
150
|
};
|
|
151
|
+
export type Conformance = 'down' | 'skip' | 'up' | 'level';
|
|
98
152
|
export type GraphModel = {
|
|
99
153
|
nodes: GraphNode[];
|
|
154
|
+
/** Structural edges — the ones layouts, neighbours and relationships see. */
|
|
100
155
|
edges: GraphEdge[];
|
|
156
|
+
/** Overlay edges (`GraphEdge.overlay`) — routed after layout, never shaping it. */
|
|
157
|
+
overlays: GraphEdge[];
|
|
101
158
|
byId: Map<string, GraphNode>;
|
|
102
159
|
/** Undirected adjacency, self-edges excluded. */
|
|
103
160
|
neighbors: Map<string, Set<string>>;
|
|
@@ -153,6 +210,19 @@ export type GraphProps = {
|
|
|
153
210
|
label?: string;
|
|
154
211
|
/** Fires on selection AND on clear, where it receives `null`. */
|
|
155
212
|
onselect?: (id: string | null) => void;
|
|
213
|
+
/**
|
|
214
|
+
* The containment subtree drawn as the whole canvas (`world`, `sunburst`, `structure`).
|
|
215
|
+
* Bindable: drilling moves it. `[]` is the root.
|
|
216
|
+
*/
|
|
217
|
+
focusPath?: string[];
|
|
218
|
+
/** The reader drilled into a box — see `GraphStateConfig.ondrill`. */
|
|
219
|
+
ondrill?: (path: string[], node: GraphNode | null) => void | Promise<void>;
|
|
220
|
+
/** The reader drilled out — see `GraphStateConfig.ondrillup`. */
|
|
221
|
+
ondrillup?: (path: string[]) => void | Promise<void>;
|
|
222
|
+
/** The reader expanded a group — see `GraphStateConfig.onexpand`. */
|
|
223
|
+
onexpand?: (id: string, node: GraphNode) => void;
|
|
224
|
+
/** The reader collapsed a group — see `GraphStateConfig.oncollapse`. */
|
|
225
|
+
oncollapse?: (id: string, node: GraphNode) => void;
|
|
156
226
|
/** Icon class per node kind and row badge, merged over the built-in map. */
|
|
157
227
|
icons?: Record<string, string>;
|
|
158
228
|
class?: string;
|
|
@@ -207,6 +277,27 @@ export type GraphFields = {
|
|
|
207
277
|
defaultEdgeKind?: EdgeKind;
|
|
208
278
|
/** Path to the consumer's own relationship word — see `GraphEdge.relation`. */
|
|
209
279
|
relation?: string;
|
|
280
|
+
/** Path to a truthy flag marking an overlay edge — see `GraphEdge.overlay`. */
|
|
281
|
+
overlay?: string;
|
|
282
|
+
/** Path to a group's member ids — see `GraphNode.members`. */
|
|
283
|
+
members?: string;
|
|
284
|
+
/** Path to a group's starting state — see `GraphNode.collapsed`. */
|
|
285
|
+
collapsed?: string;
|
|
286
|
+
/** Path to a truthy flag marking the weakest edge of a cycle — see `GraphEdge.weakest`. */
|
|
287
|
+
weakest?: string;
|
|
288
|
+
/** Path to a truthy flag marking hidden coupling — see `GraphEdge.hidden`. */
|
|
289
|
+
hidden?: string;
|
|
290
|
+
/** Path to a node's host-assigned layer — see `GraphNode.layer`. */
|
|
291
|
+
layer?: string;
|
|
292
|
+
/**
|
|
293
|
+
* Path to a node's PARENT id — containment as a parent chain rather than a `path` (#168).
|
|
294
|
+
* Used only for a node with no `path`; it becomes one, as ids.
|
|
295
|
+
*/
|
|
296
|
+
parent?: string;
|
|
297
|
+
/** Path to an edge's conformance to the layering — see `GraphEdge.conformance`. */
|
|
298
|
+
conformance?: string;
|
|
299
|
+
/** Path to an edge's strength — see `GraphEdge.weight`. */
|
|
300
|
+
edgeWeight?: string;
|
|
210
301
|
cardinality?: string;
|
|
211
302
|
action?: string;
|
|
212
303
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rokkit/graph",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.8.1",
|
|
4
4
|
"description": "Node-link diagram components — pluggable layouts, ER/schema views",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -46,10 +46,11 @@
|
|
|
46
46
|
"check:types": "tsc --noEmit"
|
|
47
47
|
},
|
|
48
48
|
"dependencies": {
|
|
49
|
-
"@rokkit/core": "1.
|
|
49
|
+
"@rokkit/core": "1.8.0",
|
|
50
|
+
"@rokkit/states": "1.8.0"
|
|
50
51
|
},
|
|
51
52
|
"peerDependencies": {
|
|
52
|
-
"@rokkit/ui": "1.
|
|
53
|
+
"@rokkit/ui": "1.8.0",
|
|
53
54
|
"svelte": "^5.0.0"
|
|
54
55
|
},
|
|
55
56
|
"peerDependenciesMeta": {
|
|
@@ -58,7 +59,7 @@
|
|
|
58
59
|
}
|
|
59
60
|
},
|
|
60
61
|
"devDependencies": {
|
|
61
|
-
"@rokkit/ui": "1.
|
|
62
|
+
"@rokkit/ui": "1.8.0",
|
|
62
63
|
"@sveltejs/package": "^2.5.8",
|
|
63
64
|
"@sveltejs/vite-plugin-svelte": "^6.2.4",
|
|
64
65
|
"svelte": "^5.55.7",
|