@rokkit/graph 1.8.1 → 1.9.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/dist/GraphState.svelte.d.ts +2 -0
- package/dist/GraphState.svelte.js +2 -1
- package/dist/diagrams/Neighborhood.svelte +7 -0
- package/dist/diagrams/Neighborhood.svelte.d.ts +5 -0
- package/dist/layout/extent.d.ts +1 -1
- package/dist/layout/extent.js +4 -1
- package/dist/layout/neighborhood.js +29 -14
- package/dist/layout/options.d.ts +1 -1
- package/dist/layout/options.js +3 -2
- package/dist/layout/types.d.ts +12 -0
- package/dist/state/GraphConfig.svelte.d.ts +2 -0
- package/dist/state/GraphConfig.svelte.js +5 -1
- package/package.json +5 -5
|
@@ -94,6 +94,8 @@ export type GraphStateConfig = {
|
|
|
94
94
|
above?: string;
|
|
95
95
|
/** `world` / `sunburst` — the measure (a 0..1 share) each box is shaded by (#164). */
|
|
96
96
|
shadeBy?: string;
|
|
97
|
+
/** `neighborhood` — centre the drawn cards (default) or the focus card (#170). */
|
|
98
|
+
centre?: 'content' | 'focus';
|
|
97
99
|
};
|
|
98
100
|
/**
|
|
99
101
|
* The store. Turns raw `nodes`/`edges`/`fields` into the reactive shape the visuals
|
|
@@ -88,6 +88,7 @@ export class GraphState {
|
|
|
88
88
|
colorBy: this.config.colorBy,
|
|
89
89
|
above: this.config.above,
|
|
90
90
|
shadeBy: this.config.shadeBy,
|
|
91
|
+
centre: this.config.centre,
|
|
91
92
|
edgeStyle: this.config.edgeStyle,
|
|
92
93
|
focus: this.config.focus ?? this.selection.value,
|
|
93
94
|
expanded: this.selection.expanded
|
|
@@ -396,7 +397,7 @@ export class GraphState {
|
|
|
396
397
|
* stay in the component. Keeping this here is what lets it be tested without a renderer.
|
|
397
398
|
*/
|
|
398
399
|
get contentSize() {
|
|
399
|
-
return contentExtent(this.#result.clusters, this.#result.cards, this.#result.size);
|
|
400
|
+
return contentExtent(this.#result.clusters, this.#result.cards, this.#result.size, this.#result.extent);
|
|
400
401
|
}
|
|
401
402
|
get related() {
|
|
402
403
|
return this.selection.related;
|
|
@@ -41,6 +41,11 @@
|
|
|
41
41
|
* the root and invisible one click later.
|
|
42
42
|
*/
|
|
43
43
|
groupTint?: boolean
|
|
44
|
+
/**
|
|
45
|
+
* What the canvas centres (#170): the drawn cards (default), or the focus card — which
|
|
46
|
+
* leaves a blank column when one side of the focus is empty.
|
|
47
|
+
*/
|
|
48
|
+
centre?: 'content' | 'focus'
|
|
44
49
|
controls?: boolean
|
|
45
50
|
maxDepth?: number
|
|
46
51
|
legend?: boolean
|
|
@@ -66,6 +71,7 @@
|
|
|
66
71
|
depth = $bindable(1),
|
|
67
72
|
edgeStyle = $bindable('curved'),
|
|
68
73
|
groupTint = true,
|
|
74
|
+
centre = 'content',
|
|
69
75
|
controls = false,
|
|
70
76
|
maxDepth = 3,
|
|
71
77
|
legend = false,
|
|
@@ -91,6 +97,7 @@
|
|
|
91
97
|
depth,
|
|
92
98
|
edgeStyle,
|
|
93
99
|
groupTint,
|
|
100
|
+
centre,
|
|
94
101
|
value,
|
|
95
102
|
preset,
|
|
96
103
|
mode,
|
|
@@ -23,6 +23,11 @@ type Props = {
|
|
|
23
23
|
* the root and invisible one click later.
|
|
24
24
|
*/
|
|
25
25
|
groupTint?: boolean;
|
|
26
|
+
/**
|
|
27
|
+
* What the canvas centres (#170): the drawn cards (default), or the focus card — which
|
|
28
|
+
* leaves a blank column when one side of the focus is empty.
|
|
29
|
+
*/
|
|
30
|
+
centre?: 'content' | 'focus';
|
|
26
31
|
controls?: boolean;
|
|
27
32
|
maxDepth?: number;
|
|
28
33
|
legend?: boolean;
|
package/dist/layout/extent.d.ts
CHANGED
|
@@ -13,5 +13,5 @@ type Box = {
|
|
|
13
13
|
* clusters', or — for an ungrouped layout (neighborhood), which reports none — the cards', and
|
|
14
14
|
* the layout size only when both are empty.
|
|
15
15
|
*/
|
|
16
|
-
export declare function contentExtent(clusters: Box[], cards: Record<string, Box>, size: Size): Size;
|
|
16
|
+
export declare function contentExtent(clusters: Box[], cards: Record<string, Box>, size: Size, declared?: Size): Size;
|
|
17
17
|
export {};
|
package/dist/layout/extent.js
CHANGED
|
@@ -16,7 +16,10 @@ function extentOf(boxes) {
|
|
|
16
16
|
* clusters', or — for an ungrouped layout (neighborhood), which reports none — the cards', and
|
|
17
17
|
* the layout size only when both are empty.
|
|
18
18
|
*/
|
|
19
|
-
export function contentExtent(clusters, cards, size) {
|
|
19
|
+
export function contentExtent(clusters, cards, size, declared) {
|
|
20
|
+
// A layout that reserved blank space on purpose says what to frame (#170, `centre: 'focus'`).
|
|
21
|
+
if (declared)
|
|
22
|
+
return declared;
|
|
20
23
|
let { w, h } = extentOf(clusters);
|
|
21
24
|
if (!w || !h) {
|
|
22
25
|
const fromCards = extentOf(Object.values(cards));
|
|
@@ -122,10 +122,8 @@ function buildNeighbourCards(model, neighbours, expanded) {
|
|
|
122
122
|
*/
|
|
123
123
|
function arrange(built, cards, ctx) {
|
|
124
124
|
const occupied = built.filter((r) => r.ids.length > 0);
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
const edge = (ctx.hasLoop ? LOOP_W : 0) + 2;
|
|
128
|
-
const { focusX, xFor } = geometry(occupied, edge);
|
|
125
|
+
const loop = ctx.hasLoop ? LOOP_W : 0;
|
|
126
|
+
const { focusX, xFor } = geometry(occupied, ctx.centre, loop);
|
|
129
127
|
const focusCard = cards[ctx.focus.id];
|
|
130
128
|
const height = placeRings(occupied, cards, { focusCard, focusX, xFor });
|
|
131
129
|
const columns = buildColumns(occupied, {
|
|
@@ -134,9 +132,11 @@ function arrange(built, cards, ctx) {
|
|
|
134
132
|
xFor,
|
|
135
133
|
dependency: ctx.dependency
|
|
136
134
|
});
|
|
137
|
-
//
|
|
138
|
-
//
|
|
139
|
-
const width =
|
|
135
|
+
// `focus`: symmetric about the focus, so its centre IS the canvas's centre (#170).
|
|
136
|
+
// `content`: ends at the deepest column drawn, so the canvas centres the cards themselves.
|
|
137
|
+
const width = ctx.centre === 'focus'
|
|
138
|
+
? focusX * 2 + CARD_W
|
|
139
|
+
: xFor('out', furthest(occupied, 'out')) + CARD_W + loop + 4;
|
|
140
140
|
return { columns, size: { w: width, h: height } };
|
|
141
141
|
}
|
|
142
142
|
/**
|
|
@@ -168,13 +168,18 @@ function drawableIn(model, cards) {
|
|
|
168
168
|
}
|
|
169
169
|
/**
|
|
170
170
|
* Where each ring sits. Ring d on the `in` side is d steps left of the focus; on the `out`
|
|
171
|
-
* side, d steps right.
|
|
172
|
-
*
|
|
171
|
+
* side, d steps right.
|
|
172
|
+
*
|
|
173
|
+
* `content` (the default): the focus is pushed right only by how deep the inbound side goes,
|
|
174
|
+
* so an empty side costs nothing and the canvas centres the cards that are drawn.
|
|
175
|
+
* `focus`: the focus sits past the DEEPER side's reach, with the same edge room on both sides,
|
|
176
|
+
* so the focus is the centre (#170). Opt-in — with one side empty it leaves a blank column.
|
|
173
177
|
*/
|
|
174
|
-
function geometry(occupied,
|
|
178
|
+
function geometry(occupied, centre, loop) {
|
|
175
179
|
const columnStep = CARD_W + COL_GAP;
|
|
176
|
-
const
|
|
177
|
-
|
|
180
|
+
const focusX = centre === 'focus'
|
|
181
|
+
? loop + 2 + Math.max(furthest(occupied, 'in'), furthest(occupied, 'out')) * columnStep
|
|
182
|
+
: furthest(occupied, 'in') * columnStep;
|
|
178
183
|
return {
|
|
179
184
|
focusX,
|
|
180
185
|
xFor: (side, d) => (side === 'in' ? focusX - d * columnStep : focusX + d * columnStep)
|
|
@@ -236,6 +241,14 @@ function buildColumns(occupied, ctx) {
|
|
|
236
241
|
...side('out')
|
|
237
242
|
];
|
|
238
243
|
}
|
|
244
|
+
/**
|
|
245
|
+
* The size, and — when centring on the focus — the extent the canvas must frame: focus-centring
|
|
246
|
+
* reserves blank columns the cards do not reach, and a canvas that framed only the cards would
|
|
247
|
+
* centre them and undo the reservation (#170).
|
|
248
|
+
*/
|
|
249
|
+
function framed(size, centre) {
|
|
250
|
+
return centre === 'focus' ? { size, extent: { w: size.w, h: size.h } } : { size };
|
|
251
|
+
}
|
|
239
252
|
/**
|
|
240
253
|
* Entity-centric layout. `options.focus` names the node to centre; without it, or with
|
|
241
254
|
* an id the model does not know, the result is empty.
|
|
@@ -259,10 +272,12 @@ export const neighborhood = (model, options) => {
|
|
|
259
272
|
focus,
|
|
260
273
|
expanded: options.expanded
|
|
261
274
|
});
|
|
275
|
+
const centre = options.centre ?? 'content';
|
|
262
276
|
const { columns, size } = arrange(built, cards, {
|
|
263
277
|
focus,
|
|
264
278
|
hasLoop: selfEdges.length > 0,
|
|
265
|
-
dependency: model.edges.some((e) => e.kind === 'dependency')
|
|
279
|
+
dependency: model.edges.some((e) => e.kind === 'dependency'),
|
|
280
|
+
centre
|
|
266
281
|
});
|
|
267
|
-
return { clusters: [], cards, edges: buildEdges(drawableIn(model, cards), cards),
|
|
282
|
+
return { ...framed(size, centre), clusters: [], cards, edges: buildEdges(drawableIn(model, cards), cards), columns };
|
|
268
283
|
};
|
package/dist/layout/options.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ import type { LayoutOptions } from './types.js';
|
|
|
4
4
|
* missed here would warn about a legitimate option, which is worse than the silence this
|
|
5
5
|
* exists to fix. `spec/options.spec.ts` compares the two.
|
|
6
6
|
*/
|
|
7
|
-
export declare const LAYOUT_OPTION_KEYS: readonly ["density", "arrange", "edgeStyle", "focus", "expanded", "groupBy", "nestBy", "sizeBy", "sizeScale", "depth", "focusPath", "levels", "radialMode", "root", "bundleTension", "showEdges", "layerLabels", "widthBy", "heightBy", "colorBy", "above", "shadeBy"];
|
|
7
|
+
export declare const LAYOUT_OPTION_KEYS: readonly ["density", "arrange", "edgeStyle", "focus", "expanded", "groupBy", "nestBy", "sizeBy", "sizeScale", "depth", "focusPath", "levels", "radialMode", "root", "bundleTension", "showEdges", "layerLabels", "widthBy", "heightBy", "colorBy", "above", "shadeBy", "centre"];
|
|
8
8
|
/**
|
|
9
9
|
* Which options have an EFFECT under each built-in layout.
|
|
10
10
|
*
|
package/dist/layout/options.js
CHANGED
|
@@ -25,7 +25,8 @@ export const LAYOUT_OPTION_KEYS = [
|
|
|
25
25
|
'heightBy',
|
|
26
26
|
'colorBy',
|
|
27
27
|
'above',
|
|
28
|
-
'shadeBy'
|
|
28
|
+
'shadeBy',
|
|
29
|
+
'centre'
|
|
29
30
|
];
|
|
30
31
|
/**
|
|
31
32
|
* Which options have an EFFECT under each built-in layout.
|
|
@@ -52,7 +53,7 @@ export const LAYOUT_OPTIONS = {
|
|
|
52
53
|
flow: ['density', 'edgeStyle', 'expanded'],
|
|
53
54
|
// No edgeStyle: a layered edge is always a vertical S, built by the layout itself.
|
|
54
55
|
layers: ['density', 'expanded', 'showEdges', 'layerLabels'],
|
|
55
|
-
neighborhood: ['edgeStyle', 'expanded', 'focus', 'depth'],
|
|
56
|
+
neighborhood: ['edgeStyle', 'expanded', 'focus', 'depth', 'centre'],
|
|
56
57
|
points: ['edgeStyle', 'sizeBy', 'sizeScale'],
|
|
57
58
|
// No edgeStyle: the tree's links are elbows the layout draws itself.
|
|
58
59
|
polymetric: ['widthBy', 'heightBy', 'colorBy'],
|
package/dist/layout/types.d.ts
CHANGED
|
@@ -307,6 +307,12 @@ export type LayoutOptions = {
|
|
|
307
307
|
* container takes the size-weighted mean of what it holds unless it declares its own.
|
|
308
308
|
*/
|
|
309
309
|
shadeBy?: string;
|
|
310
|
+
/**
|
|
311
|
+
* `neighborhood` only — what the canvas centres (#170). `content` (default): the cards that
|
|
312
|
+
* are drawn, so an empty side costs nothing. `focus`: the focus card, with the deeper side's
|
|
313
|
+
* reach reserved on both sides — a blank column when one side is empty.
|
|
314
|
+
*/
|
|
315
|
+
centre?: 'content' | 'focus';
|
|
310
316
|
};
|
|
311
317
|
/** Every edge, or only the ones a view is about. */
|
|
312
318
|
export type ShowEdges = 'all' | 'violations' | 'hidden';
|
|
@@ -318,6 +324,12 @@ export type ChannelScale = {
|
|
|
318
324
|
cap: number;
|
|
319
325
|
};
|
|
320
326
|
export type LayoutResult = {
|
|
327
|
+
/**
|
|
328
|
+
* What the canvas should frame, when it is not the drawn content — a layout that reserves
|
|
329
|
+
* blank space on purpose (`neighborhood` with `centre: 'focus'`). Absent: the canvas frames
|
|
330
|
+
* the clusters' or cards' extent.
|
|
331
|
+
*/
|
|
332
|
+
extent?: Size;
|
|
321
333
|
/** `polymetric` — what each channel encodes, for the legend. */
|
|
322
334
|
channels?: {
|
|
323
335
|
width: ChannelScale;
|
|
@@ -38,6 +38,7 @@ export type GraphConfigValues = {
|
|
|
38
38
|
colorBy: string | undefined;
|
|
39
39
|
above: string | undefined;
|
|
40
40
|
shadeBy: string | undefined;
|
|
41
|
+
centre: 'content' | 'focus' | undefined;
|
|
41
42
|
};
|
|
42
43
|
type Key = keyof GraphConfigValues;
|
|
43
44
|
type Field = {
|
|
@@ -120,5 +121,6 @@ export declare class GraphConfig {
|
|
|
120
121
|
get colorBy(): string | undefined;
|
|
121
122
|
get above(): string | undefined;
|
|
122
123
|
get shadeBy(): string | undefined;
|
|
124
|
+
get centre(): "focus" | "content" | undefined;
|
|
123
125
|
}
|
|
124
126
|
export {};
|
|
@@ -58,7 +58,8 @@ export const CONFIG_FIELDS = Object.freeze([
|
|
|
58
58
|
{ key: 'heightBy', raw: true },
|
|
59
59
|
{ key: 'colorBy', raw: true },
|
|
60
60
|
{ key: 'above', raw: true },
|
|
61
|
-
{ key: 'shadeBy', raw: true }
|
|
61
|
+
{ key: 'shadeBy', raw: true },
|
|
62
|
+
{ key: 'centre', raw: true }
|
|
62
63
|
]);
|
|
63
64
|
function resolve(config) {
|
|
64
65
|
const values = {};
|
|
@@ -250,4 +251,7 @@ export class GraphConfig {
|
|
|
250
251
|
get shadeBy() {
|
|
251
252
|
return this.#v.shadeBy;
|
|
252
253
|
}
|
|
254
|
+
get centre() {
|
|
255
|
+
return this.#v.centre;
|
|
256
|
+
}
|
|
253
257
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rokkit/graph",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.9.0",
|
|
4
4
|
"description": "Node-link diagram components — pluggable layouts, ER/schema views",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -46,11 +46,11 @@
|
|
|
46
46
|
"check:types": "tsc --noEmit"
|
|
47
47
|
},
|
|
48
48
|
"dependencies": {
|
|
49
|
-
"@rokkit/core": "1.
|
|
50
|
-
"@rokkit/states": "1.
|
|
49
|
+
"@rokkit/core": "1.9.0",
|
|
50
|
+
"@rokkit/states": "1.9.0"
|
|
51
51
|
},
|
|
52
52
|
"peerDependencies": {
|
|
53
|
-
"@rokkit/ui": "1.
|
|
53
|
+
"@rokkit/ui": "1.9.0",
|
|
54
54
|
"svelte": "^5.0.0"
|
|
55
55
|
},
|
|
56
56
|
"peerDependenciesMeta": {
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
}
|
|
60
60
|
},
|
|
61
61
|
"devDependencies": {
|
|
62
|
-
"@rokkit/ui": "1.
|
|
62
|
+
"@rokkit/ui": "1.9.0",
|
|
63
63
|
"@sveltejs/package": "^2.5.8",
|
|
64
64
|
"@sveltejs/vite-plugin-svelte": "^6.2.4",
|
|
65
65
|
"svelte": "^5.55.7",
|