@ankhorage/zora 22.1.2 → 22.1.4
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/CHANGELOG.md +12 -0
- package/README.md +1 -1
- package/dist/features/graph-view/adapters/inbound/web-artifact/GraphView.d.ts +3 -3
- package/dist/features/graph-view/adapters/inbound/web-artifact/GraphView.js +3 -3
- package/dist/features/graph-view/adapters/inbound/web-artifact/GraphView.js.map +1 -1
- package/dist/features/graph-view/adapters/inbound/web-artifact/compactGraphSpacing.d.ts +6 -5
- package/dist/features/graph-view/adapters/inbound/web-artifact/compactGraphSpacing.d.ts.map +1 -1
- package/dist/features/graph-view/adapters/inbound/web-artifact/compactGraphSpacing.js +107 -171
- package/dist/features/graph-view/adapters/inbound/web-artifact/compactGraphSpacing.js.map +1 -1
- package/dist/features/graph-view/adapters/inbound/web-artifact/createGraphController.d.ts +4 -0
- package/dist/features/graph-view/adapters/inbound/web-artifact/createGraphController.d.ts.map +1 -1
- package/dist/features/graph-view/adapters/inbound/web-artifact/createGraphController.js +8 -0
- package/dist/features/graph-view/adapters/inbound/web-artifact/createGraphController.js.map +1 -1
- package/dist/features/graph-view/adapters/inbound/web-artifact/createGraphRuntime.js +5 -1
- package/dist/features/graph-view/adapters/inbound/web-artifact/createGraphRuntime.js.map +1 -1
- package/dist/features/graph-view/adapters/inbound/web-artifact/createGraphSpacingEvaluator.d.ts +9 -0
- package/dist/features/graph-view/adapters/inbound/web-artifact/createGraphSpacingEvaluator.d.ts.map +1 -0
- package/dist/features/graph-view/adapters/inbound/web-artifact/createGraphSpacingEvaluator.js +234 -0
- package/dist/features/graph-view/adapters/inbound/web-artifact/createGraphSpacingEvaluator.js.map +1 -0
- package/dist/types/graph-view-spacing.d.ts +15 -0
- package/dist/types/graph-view-spacing.d.ts.map +1 -0
- package/dist/types/graph-view-spacing.js +2 -0
- package/dist/types/graph-view-spacing.js.map +1 -0
- package/dist/types/graph-view.d.ts +4 -1
- package/dist/types/graph-view.d.ts.map +1 -1
- package/dist/types/graph-view.js.map +1 -1
- package/package.json +5 -5
- package/src/features/graph-view/adapters/inbound/web-artifact/GraphView.tsx +3 -3
- package/src/features/graph-view/adapters/inbound/web-artifact/compactGraphSpacing.test.ts +195 -69
- package/src/features/graph-view/adapters/inbound/web-artifact/compactGraphSpacing.ts +162 -241
- package/src/features/graph-view/adapters/inbound/web-artifact/createGraphController.test.ts +2 -0
- package/src/features/graph-view/adapters/inbound/web-artifact/createGraphController.ts +8 -0
- package/src/features/graph-view/adapters/inbound/web-artifact/createGraphRuntime.ts +5 -1
- package/src/features/graph-view/adapters/inbound/web-artifact/createGraphSpacingEvaluator.ts +338 -0
- package/src/types/graph-view-spacing.ts +16 -0
- package/src/types/graph-view.ts +4 -1
- package/web-dist/components/graph-view/GraphView.d.ts +3 -3
- package/web-dist/components/graph-view/GraphView.js +237 -126
- package/web-dist/components/graph-view/GraphView.types-0.d.ts +4 -1
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
import type { Core, NodeSingular } from 'cytoscape';
|
|
2
|
+
|
|
3
|
+
import type {
|
|
4
|
+
GraphSpacingCandidate,
|
|
5
|
+
GraphSpacingEvaluator,
|
|
6
|
+
GraphSpacingFitOptions,
|
|
7
|
+
} from '../../../../../types/graph-view-spacing';
|
|
8
|
+
|
|
9
|
+
const MAX_PAIR_CHECKS = 200000;
|
|
10
|
+
|
|
11
|
+
interface Point {
|
|
12
|
+
readonly x: number;
|
|
13
|
+
readonly y: number;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface Bounds {
|
|
17
|
+
readonly x1: number;
|
|
18
|
+
readonly y1: number;
|
|
19
|
+
readonly x2: number;
|
|
20
|
+
readonly y2: number;
|
|
21
|
+
readonly w: number;
|
|
22
|
+
readonly h: number;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface Insets {
|
|
26
|
+
readonly left: number;
|
|
27
|
+
readonly right: number;
|
|
28
|
+
readonly top: number;
|
|
29
|
+
readonly bottom: number;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
interface LeafGeometry {
|
|
33
|
+
readonly id: string;
|
|
34
|
+
readonly ancestors: ReadonlySet<string>;
|
|
35
|
+
readonly position: Point;
|
|
36
|
+
readonly fitBox: Bounds;
|
|
37
|
+
readonly labelBox: Bounds | null;
|
|
38
|
+
readonly visualBox: Bounds;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
interface GroupGeometry {
|
|
42
|
+
readonly id: string;
|
|
43
|
+
readonly ancestors: ReadonlySet<string>;
|
|
44
|
+
readonly descendantIds: readonly string[];
|
|
45
|
+
readonly insets: Insets;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
interface ResolvedLeafGeometry {
|
|
49
|
+
readonly id: string;
|
|
50
|
+
readonly ancestors: ReadonlySet<string>;
|
|
51
|
+
readonly fitBox: Bounds;
|
|
52
|
+
readonly labelBox: Bounds | null;
|
|
53
|
+
readonly visualBox: Bounds;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
interface CollisionBox {
|
|
57
|
+
readonly id: string;
|
|
58
|
+
readonly ancestors: ReadonlySet<string>;
|
|
59
|
+
readonly kind: 'group' | 'label' | 'visual';
|
|
60
|
+
readonly box: Bounds;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
interface SpacingContext {
|
|
64
|
+
readonly center: Point;
|
|
65
|
+
readonly fitPadding: number;
|
|
66
|
+
readonly leaves: readonly LeafGeometry[];
|
|
67
|
+
readonly groups: readonly GroupGeometry[];
|
|
68
|
+
readonly maxFitZoom: number;
|
|
69
|
+
readonly viewport: { readonly width: number; readonly height: number };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/***
|
|
73
|
+
* Snapshot renderer geometry once and expose pure candidate evaluation plus one accepted mutation.
|
|
74
|
+
* Labels and unrelated compound boundaries are hard constraints. Leaf backgrounds report rendered
|
|
75
|
+
* overlap separately so the caller can spend a small visual-overlap budget only for dense graphs.
|
|
76
|
+
*/
|
|
77
|
+
export function createGraphSpacingEvaluator(
|
|
78
|
+
cy: Core,
|
|
79
|
+
options: GraphSpacingFitOptions = {},
|
|
80
|
+
): GraphSpacingEvaluator | null {
|
|
81
|
+
const leaves = cy.nodes(':childless');
|
|
82
|
+
const viewport = { width: cy.width(), height: cy.height() };
|
|
83
|
+
if (leaves.length < 2 || viewport.width <= 0 || viewport.height <= 0) return null;
|
|
84
|
+
|
|
85
|
+
const leafBounds = leaves.boundingBox();
|
|
86
|
+
const center = {
|
|
87
|
+
x: (leafBounds.x1 + leafBounds.x2) / 2,
|
|
88
|
+
y: (leafBounds.y1 + leafBounds.y2) / 2,
|
|
89
|
+
};
|
|
90
|
+
const leafGeometry = leaves.map(readLeafGeometry);
|
|
91
|
+
const context: SpacingContext = {
|
|
92
|
+
center,
|
|
93
|
+
fitPadding: Math.max(0, options.fitPadding ?? 50),
|
|
94
|
+
leaves: leafGeometry,
|
|
95
|
+
groups: cy.nodes(':parent').map((node) => readGroupGeometry(node, leafGeometry)),
|
|
96
|
+
maxFitZoom: positiveNumber(options.maxFitZoom, Infinity),
|
|
97
|
+
viewport,
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
return {
|
|
101
|
+
evaluate: (factor) => evaluateSpacingCandidate(context, factor),
|
|
102
|
+
apply: (factor) => applyScale(cy, leafGeometry, center, factor),
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/*** Read immutable leaf geometry while separating label content from decorative node bounds. */
|
|
107
|
+
function readLeafGeometry(node: NodeSingular): LeafGeometry {
|
|
108
|
+
const fitBox = readNodeBox(node, true);
|
|
109
|
+
const visualBox = readNodeBox(node, false);
|
|
110
|
+
const labelBox = node.boundingBox({
|
|
111
|
+
includeNodes: false,
|
|
112
|
+
includeEdges: false,
|
|
113
|
+
includeLabels: true,
|
|
114
|
+
includeOverlays: false,
|
|
115
|
+
includeUnderlays: false,
|
|
116
|
+
});
|
|
117
|
+
return {
|
|
118
|
+
id: node.id(),
|
|
119
|
+
ancestors: new Set(node.ancestors().map((parent) => parent.id())),
|
|
120
|
+
position: { ...node.position() },
|
|
121
|
+
fitBox,
|
|
122
|
+
labelBox: isUsableBox(labelBox) ? labelBox : null,
|
|
123
|
+
visualBox: isUsableBox(visualBox) ? visualBox : fitBox,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/*** Read a leaf node box with explicit label participation and no edge/overlay geometry. */
|
|
128
|
+
function readNodeBox(node: NodeSingular, includeLabels: boolean): Bounds {
|
|
129
|
+
return node.boundingBox({
|
|
130
|
+
includeNodes: true,
|
|
131
|
+
includeEdges: false,
|
|
132
|
+
includeLabels,
|
|
133
|
+
includeOverlays: false,
|
|
134
|
+
includeUnderlays: false,
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/*** Capture compound insets so candidate parent bounds follow uniformly shifted descendants. */
|
|
139
|
+
function readGroupGeometry(node: NodeSingular, leaves: readonly LeafGeometry[]): GroupGeometry {
|
|
140
|
+
const descendants = leaves.filter((leaf) => leaf.ancestors.has(node.id()));
|
|
141
|
+
const parentBox = readNodeBox(node, true);
|
|
142
|
+
const descendantUnion = unionBounds(descendants.map((leaf) => leaf.fitBox)) ?? parentBox;
|
|
143
|
+
return {
|
|
144
|
+
id: node.id(),
|
|
145
|
+
ancestors: new Set(node.ancestors().map((parent) => parent.id())),
|
|
146
|
+
descendantIds: descendants.map((leaf) => leaf.id),
|
|
147
|
+
insets: {
|
|
148
|
+
left: Math.max(0, descendantUnion.x1 - parentBox.x1),
|
|
149
|
+
right: Math.max(0, parentBox.x2 - descendantUnion.x2),
|
|
150
|
+
top: Math.max(0, descendantUnion.y1 - parentBox.y1),
|
|
151
|
+
bottom: Math.max(0, parentBox.y2 - descendantUnion.y2),
|
|
152
|
+
},
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/*** Evaluate one uniform spacing factor without mutating Cytoscape or painting intermediate frames. */
|
|
157
|
+
function evaluateSpacingCandidate(context: SpacingContext, factor: number): GraphSpacingCandidate {
|
|
158
|
+
const leaves = context.leaves.map((leaf) => resolveLeafGeometry(leaf, context.center, factor));
|
|
159
|
+
const byId = new Map(leaves.map((leaf) => [leaf.id, leaf]));
|
|
160
|
+
const groups = context.groups.flatMap((group) => resolveGroupBox(group, byId));
|
|
161
|
+
const fitBounds = unionBounds([
|
|
162
|
+
...leaves.map((leaf) => leaf.fitBox),
|
|
163
|
+
...groups.map((group) => group.box),
|
|
164
|
+
]);
|
|
165
|
+
const rawFitZoom = fitBounds === undefined ? 1 : getFitZoom(fitBounds, context);
|
|
166
|
+
const effectiveFitZoom = Math.min(rawFitZoom, context.maxFitZoom);
|
|
167
|
+
return {
|
|
168
|
+
effectiveFitZoom,
|
|
169
|
+
...readCollisionState(createCollisionBoxes(leaves, groups), effectiveFitZoom),
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/*** Shift one leaf's fixed renderer boxes with its center under uniform radial scaling. */
|
|
174
|
+
function resolveLeafGeometry(
|
|
175
|
+
leaf: LeafGeometry,
|
|
176
|
+
center: Point,
|
|
177
|
+
factor: number,
|
|
178
|
+
): ResolvedLeafGeometry {
|
|
179
|
+
return {
|
|
180
|
+
id: leaf.id,
|
|
181
|
+
ancestors: leaf.ancestors,
|
|
182
|
+
fitBox: shiftBounds(leaf.fitBox, leaf.position, center, factor),
|
|
183
|
+
labelBox:
|
|
184
|
+
leaf.labelBox === null ? null : shiftBounds(leaf.labelBox, leaf.position, center, factor),
|
|
185
|
+
visualBox: shiftBounds(leaf.visualBox, leaf.position, center, factor),
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/*** Resolve one compound boundary from shifted descendants plus measured outer insets. */
|
|
190
|
+
function resolveGroupBox(
|
|
191
|
+
group: GroupGeometry,
|
|
192
|
+
leaves: ReadonlyMap<string, ResolvedLeafGeometry>,
|
|
193
|
+
): readonly CollisionBox[] {
|
|
194
|
+
const descendantBounds = group.descendantIds.flatMap((id) => {
|
|
195
|
+
const leaf = leaves.get(id);
|
|
196
|
+
return leaf === undefined ? [] : [leaf.fitBox];
|
|
197
|
+
});
|
|
198
|
+
const union = unionBounds(descendantBounds);
|
|
199
|
+
return union === undefined
|
|
200
|
+
? []
|
|
201
|
+
: [
|
|
202
|
+
{
|
|
203
|
+
id: group.id,
|
|
204
|
+
ancestors: group.ancestors,
|
|
205
|
+
kind: 'group',
|
|
206
|
+
box: expandBounds(union, group.insets),
|
|
207
|
+
},
|
|
208
|
+
];
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/*** Build collision boxes with semantic labels separate from decorative leaf backgrounds. */
|
|
212
|
+
function createCollisionBoxes(
|
|
213
|
+
leaves: readonly ResolvedLeafGeometry[],
|
|
214
|
+
groups: readonly CollisionBox[],
|
|
215
|
+
): readonly CollisionBox[] {
|
|
216
|
+
return [
|
|
217
|
+
...leaves.flatMap((leaf) =>
|
|
218
|
+
leaf.labelBox === null
|
|
219
|
+
? []
|
|
220
|
+
: [{ id: leaf.id, ancestors: leaf.ancestors, kind: 'label' as const, box: leaf.labelBox }],
|
|
221
|
+
),
|
|
222
|
+
...leaves.map((leaf) => ({
|
|
223
|
+
id: leaf.id,
|
|
224
|
+
ancestors: leaf.ancestors,
|
|
225
|
+
kind: 'visual' as const,
|
|
226
|
+
box: leaf.visualBox,
|
|
227
|
+
})),
|
|
228
|
+
...groups,
|
|
229
|
+
];
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/***
|
|
233
|
+
* Sweep collision boxes and separate semantic collisions from decorative background penetration.
|
|
234
|
+
* Pair-budget exhaustion is conservative and therefore treated as a hard collision.
|
|
235
|
+
*/
|
|
236
|
+
function readCollisionState(
|
|
237
|
+
boxes: readonly CollisionBox[],
|
|
238
|
+
zoom: number,
|
|
239
|
+
): Pick<GraphSpacingCandidate, 'hardCollision' | 'renderedBackgroundOverlap'> {
|
|
240
|
+
const sorted = [...boxes].sort((first, second) => first.box.x1 - second.box.x1);
|
|
241
|
+
const budget = { remaining: MAX_PAIR_CHECKS };
|
|
242
|
+
const overlap = { maximum: 0 };
|
|
243
|
+
for (const [index, first] of sorted.entries()) {
|
|
244
|
+
for (let next = index + 1; next < sorted.length; next += 1) {
|
|
245
|
+
const second = sorted.at(next);
|
|
246
|
+
if (second === undefined) break;
|
|
247
|
+
if (second.box.x1 >= first.box.x2) break;
|
|
248
|
+
if (first.id === second.id || isIntentionalContainment(first, second)) continue;
|
|
249
|
+
const penetration = getOverlapPenetration(first.box, second.box);
|
|
250
|
+
if (penetration <= 0) continue;
|
|
251
|
+
budget.remaining -= 1;
|
|
252
|
+
if (budget.remaining < 0) return { hardCollision: true, renderedBackgroundOverlap: Infinity };
|
|
253
|
+
if (first.kind === 'visual' && second.kind === 'visual') {
|
|
254
|
+
overlap.maximum = Math.max(overlap.maximum, penetration * zoom);
|
|
255
|
+
continue;
|
|
256
|
+
}
|
|
257
|
+
return { hardCollision: true, renderedBackgroundOverlap: overlap.maximum };
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
return { hardCollision: false, renderedBackgroundOverlap: overlap.maximum };
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/*** Ignore only true ancestor/descendant containment; siblings remain collision candidates. */
|
|
264
|
+
function isIntentionalContainment(first: CollisionBox, second: CollisionBox): boolean {
|
|
265
|
+
return first.ancestors.has(second.id) || second.ancestors.has(first.id);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/*** Return the smaller-axis penetration for intersecting rectangles, or zero when disjoint. */
|
|
269
|
+
function getOverlapPenetration(first: Bounds, second: Bounds): number {
|
|
270
|
+
const width = Math.min(first.x2, second.x2) - Math.max(first.x1, second.x1);
|
|
271
|
+
const height = Math.min(first.y2, second.y2) - Math.max(first.y1, second.y1);
|
|
272
|
+
return width > 0 && height > 0 ? Math.min(width, height) : 0;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/*** Calculate native zoom for fitting candidate node geometry inside the padded viewport. */
|
|
276
|
+
function getFitZoom(bounds: Bounds, context: SpacingContext): number {
|
|
277
|
+
const width = context.viewport.width - 2 * context.fitPadding;
|
|
278
|
+
const height = context.viewport.height - 2 * context.fitPadding;
|
|
279
|
+
if (width <= 0 || height <= 0 || bounds.w <= 0 || bounds.h <= 0) return 1;
|
|
280
|
+
return Math.min(width / bounds.w, height / bounds.h);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/*** Shift one fixed renderer box with its leaf center under uniform radial scaling. */
|
|
284
|
+
function shiftBounds(box: Bounds, position: Point, center: Point, factor: number): Bounds {
|
|
285
|
+
const dx = (position.x - center.x) * (factor - 1);
|
|
286
|
+
const dy = (position.y - center.y) * (factor - 1);
|
|
287
|
+
return { ...box, x1: box.x1 + dx, y1: box.y1 + dy, x2: box.x2 + dx, y2: box.y2 + dy };
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/*** Expand a descendant union by the compound renderer's measured outer insets. */
|
|
291
|
+
function expandBounds(box: Bounds, insets: Insets): Bounds {
|
|
292
|
+
const x1 = box.x1 - insets.left;
|
|
293
|
+
const y1 = box.y1 - insets.top;
|
|
294
|
+
const x2 = box.x2 + insets.right;
|
|
295
|
+
const y2 = box.y2 + insets.bottom;
|
|
296
|
+
return { x1, y1, x2, y2, w: x2 - x1, h: y2 - y1 };
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/*** Return the immutable union of measured boxes, or undefined for an empty collection. */
|
|
300
|
+
function unionBounds(boxes: readonly Bounds[]): Bounds | undefined {
|
|
301
|
+
if (boxes.length === 0) return undefined;
|
|
302
|
+
const x1 = Math.min(...boxes.map((box) => box.x1));
|
|
303
|
+
const y1 = Math.min(...boxes.map((box) => box.y1));
|
|
304
|
+
const x2 = Math.max(...boxes.map((box) => box.x2));
|
|
305
|
+
const y2 = Math.max(...boxes.map((box) => box.y2));
|
|
306
|
+
return { x1, y1, x2, y2, w: x2 - x1, h: y2 - y1 };
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/*** Return whether renderer measurement produced a non-empty box. */
|
|
310
|
+
function isUsableBox(box: Bounds): boolean {
|
|
311
|
+
return box.w > 0 && box.h > 0;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/*** Normalize an optional positive numeric input without allowing NaN or non-positive values. */
|
|
315
|
+
function positiveNumber(value: number | undefined, fallback: number): number {
|
|
316
|
+
return value !== undefined && Number.isFinite(value) && value > 0 ? value : fallback;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/*** Apply the accepted factor once, preserving dimensions, hierarchy, ordering and edge data. */
|
|
320
|
+
function applyScale(
|
|
321
|
+
cy: Core,
|
|
322
|
+
leaves: readonly LeafGeometry[],
|
|
323
|
+
center: Point,
|
|
324
|
+
factor: number,
|
|
325
|
+
): void {
|
|
326
|
+
const positions = new Map(leaves.map((leaf) => [leaf.id, leaf.position]));
|
|
327
|
+
cy.batch(() => {
|
|
328
|
+
cy.nodes()
|
|
329
|
+
.not(':parent')
|
|
330
|
+
.positions((node) => {
|
|
331
|
+
const original = positions.get(node.id()) ?? node.position();
|
|
332
|
+
return {
|
|
333
|
+
x: center.x + (original.x - center.x) * factor,
|
|
334
|
+
y: center.y + (original.y - center.y) * factor,
|
|
335
|
+
};
|
|
336
|
+
});
|
|
337
|
+
});
|
|
338
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export interface GraphSpacingFitOptions {
|
|
2
|
+
readonly fitPadding?: number;
|
|
3
|
+
readonly maxFitZoom?: number;
|
|
4
|
+
readonly minReadableZoom?: number;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export interface GraphSpacingCandidate {
|
|
8
|
+
readonly effectiveFitZoom: number;
|
|
9
|
+
readonly hardCollision: boolean;
|
|
10
|
+
readonly renderedBackgroundOverlap: number;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface GraphSpacingEvaluator {
|
|
14
|
+
readonly evaluate: (factor: number) => GraphSpacingCandidate;
|
|
15
|
+
readonly apply: (factor: number) => void;
|
|
16
|
+
}
|
package/src/types/graph-view.ts
CHANGED
|
@@ -67,7 +67,10 @@ export interface GraphViewViewport {
|
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
export interface GraphViewFitOptions {
|
|
70
|
-
/**
|
|
70
|
+
/**
|
|
71
|
+
* Optimize effective rendered fit without rerunning the layout algorithm; whole-graph fits only.
|
|
72
|
+
* Stops at the configured fit-size target and keeps semantic labels collision-safe.
|
|
73
|
+
*/
|
|
71
74
|
readonly optimizeSpacing?: boolean;
|
|
72
75
|
readonly nodeIds?: readonly string[];
|
|
73
76
|
readonly padding?: number;
|
|
@@ -11,9 +11,9 @@ import type { GraphViewProps } from './GraphView.types-0';
|
|
|
11
11
|
* minReadableLabelSize Expand the manual zoom ceiling until plain labels reach this CSS-pixel size.
|
|
12
12
|
* maxFitLabelSize Cap automatic fitting at this plain-label CSS-pixel size, leaving whitespace for tiny graphs.
|
|
13
13
|
* onSpacingFactorChange Receive the spacing accepted by explicit fit({ optimizeSpacing: true }).
|
|
14
|
-
* Ordinary zoom never adjusts spacing or relayouts. Explicit optimization preserves layout ordering
|
|
15
|
-
*
|
|
16
|
-
*
|
|
14
|
+
* Ordinary zoom never adjusts spacing or relayouts. Explicit optimization preserves layout ordering,
|
|
15
|
+
* stops once effective fit reaches maxFitLabelSize, keeps labels and unrelated compound boundaries
|
|
16
|
+
* collision-safe, and permits only a small rendered background-overlap fallback for dense graphs.
|
|
17
17
|
*/
|
|
18
18
|
export declare function GraphView(props: GraphViewProps): React.JSX.Element;
|
|
19
19
|
export type { GraphViewCallbacks, GraphViewController, GraphViewEdge, GraphViewElementEvent, GraphViewElementEventType, GraphViewFitOptions, GraphViewLayoutName, GraphViewMeasurement, GraphViewNode, GraphViewNodeMeasurement, GraphViewNodeRenderContext, GraphViewPoint, GraphViewProps, GraphViewRenderedNode, GraphViewSize, GraphViewStyleRule, GraphViewViewport } from './GraphView.types-0';
|