@ankhorage/zora 22.1.2 → 22.1.3
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 +6 -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 +2 -2
- 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
|
@@ -122375,82 +122375,35 @@ var EVENT_TYPES = [
|
|
|
122375
122375
|
["mouseout", "pointer-leave"]
|
|
122376
122376
|
];
|
|
122377
122377
|
|
|
122378
|
-
//
|
|
122379
|
-
var DEFAULT_ITERATIONS = 16;
|
|
122380
|
-
var MAX_ITERATIONS = 64;
|
|
122381
|
-
function findMinimumAcceptedNumber(input) {
|
|
122382
|
-
validateInput(input);
|
|
122383
|
-
const { accepts, maximum, minimum } = input;
|
|
122384
|
-
const iterations = input.iterations ?? DEFAULT_ITERATIONS;
|
|
122385
|
-
if (accepts(minimum))
|
|
122386
|
-
return minimum;
|
|
122387
|
-
if (!accepts(maximum))
|
|
122388
|
-
return;
|
|
122389
|
-
return searchAcceptedBoundary(minimum, maximum, iterations, accepts);
|
|
122390
|
-
}
|
|
122391
|
-
function searchAcceptedBoundary(minimum, maximum, iterations, accepts) {
|
|
122392
|
-
let lower = minimum;
|
|
122393
|
-
let upper = maximum;
|
|
122394
|
-
for (let iteration = 0;iteration < iterations; iteration += 1) {
|
|
122395
|
-
const midpoint = lower + (upper - lower) / 2;
|
|
122396
|
-
if (midpoint === lower || midpoint === upper)
|
|
122397
|
-
break;
|
|
122398
|
-
if (accepts(midpoint))
|
|
122399
|
-
upper = midpoint;
|
|
122400
|
-
else
|
|
122401
|
-
lower = midpoint;
|
|
122402
|
-
}
|
|
122403
|
-
return upper;
|
|
122404
|
-
}
|
|
122405
|
-
function validateInput(input) {
|
|
122406
|
-
const iterations = input.iterations ?? DEFAULT_ITERATIONS;
|
|
122407
|
-
if (!Number.isFinite(input.minimum) || !Number.isFinite(input.maximum)) {
|
|
122408
|
-
throw new RangeError("Search bounds must be finite numbers.");
|
|
122409
|
-
}
|
|
122410
|
-
if (input.minimum > input.maximum) {
|
|
122411
|
-
throw new RangeError("Search minimum must not exceed maximum.");
|
|
122412
|
-
}
|
|
122413
|
-
if (!Number.isInteger(iterations) || iterations < 0 || iterations > MAX_ITERATIONS) {
|
|
122414
|
-
throw new RangeError(`Search iterations must be an integer from 0 to ${MAX_ITERATIONS}.`);
|
|
122415
|
-
}
|
|
122416
|
-
}
|
|
122417
|
-
// src/features/graph-view/adapters/inbound/web-artifact/compactGraphSpacing.ts
|
|
122418
|
-
var LABEL_OVERLAP_RATIO = 0.12;
|
|
122419
|
-
var MAX_EXPANSION_ATTEMPTS = 8;
|
|
122420
|
-
var MAX_NODE_COUNT = 2000;
|
|
122378
|
+
// src/features/graph-view/adapters/inbound/web-artifact/createGraphSpacingEvaluator.ts
|
|
122421
122379
|
var MAX_PAIR_CHECKS = 200000;
|
|
122422
|
-
|
|
122423
|
-
function compactGraphSpacing(cy, spacingFactor) {
|
|
122380
|
+
function createGraphSpacingEvaluator(cy, options = {}) {
|
|
122424
122381
|
const leaves = cy.nodes(":childless");
|
|
122425
|
-
|
|
122426
|
-
|
|
122427
|
-
|
|
122428
|
-
const
|
|
122382
|
+
const viewport = { width: cy.width(), height: cy.height() };
|
|
122383
|
+
if (leaves.length < 2 || viewport.width <= 0 || viewport.height <= 0)
|
|
122384
|
+
return null;
|
|
122385
|
+
const leafBounds = leaves.boundingBox();
|
|
122386
|
+
const center = {
|
|
122387
|
+
x: (leafBounds.x1 + leafBounds.x2) / 2,
|
|
122388
|
+
y: (leafBounds.y1 + leafBounds.y2) / 2
|
|
122389
|
+
};
|
|
122429
122390
|
const leafGeometry = leaves.map(readLeafGeometry);
|
|
122430
|
-
const
|
|
122431
|
-
|
|
122432
|
-
|
|
122433
|
-
|
|
122434
|
-
|
|
122435
|
-
|
|
122436
|
-
|
|
122437
|
-
|
|
122438
|
-
|
|
122439
|
-
|
|
122440
|
-
|
|
122441
|
-
|
|
122442
|
-
});
|
|
122443
|
-
if (factor === undefined)
|
|
122444
|
-
return spacingFactor;
|
|
122445
|
-
applyScale(cy, leafGeometry, center, factor);
|
|
122446
|
-
return spacingFactor * factor;
|
|
122391
|
+
const context = {
|
|
122392
|
+
center,
|
|
122393
|
+
fitPadding: Math.max(0, options.fitPadding ?? 50),
|
|
122394
|
+
leaves: leafGeometry,
|
|
122395
|
+
groups: cy.nodes(":parent").map((node) => readGroupGeometry(node, leafGeometry)),
|
|
122396
|
+
maxFitZoom: positiveNumber(options.maxFitZoom, Infinity),
|
|
122397
|
+
viewport
|
|
122398
|
+
};
|
|
122399
|
+
return {
|
|
122400
|
+
evaluate: (factor) => evaluateSpacingCandidate(context, factor),
|
|
122401
|
+
apply: (factor) => applyScale(cy, leafGeometry, center, factor)
|
|
122402
|
+
};
|
|
122447
122403
|
}
|
|
122448
122404
|
function readLeafGeometry(node) {
|
|
122449
|
-
const
|
|
122450
|
-
|
|
122451
|
-
includeOverlays: false,
|
|
122452
|
-
includeUnderlays: false
|
|
122453
|
-
});
|
|
122405
|
+
const fitBox = readNodeBox(node, true);
|
|
122406
|
+
const visualBox = readNodeBox(node, false);
|
|
122454
122407
|
const labelBox = node.boundingBox({
|
|
122455
122408
|
includeNodes: false,
|
|
122456
122409
|
includeEdges: false,
|
|
@@ -122462,17 +122415,28 @@ function readLeafGeometry(node) {
|
|
|
122462
122415
|
id: node.id(),
|
|
122463
122416
|
ancestors: new Set(node.ancestors().map((parent) => parent.id())),
|
|
122464
122417
|
position: { ...node.position() },
|
|
122465
|
-
|
|
122466
|
-
|
|
122418
|
+
fitBox,
|
|
122419
|
+
labelBox: isUsableBox(labelBox) ? labelBox : null,
|
|
122420
|
+
visualBox: isUsableBox(visualBox) ? visualBox : fitBox
|
|
122467
122421
|
};
|
|
122468
122422
|
}
|
|
122469
|
-
function
|
|
122470
|
-
|
|
122471
|
-
|
|
122423
|
+
function readNodeBox(node, includeLabels) {
|
|
122424
|
+
return node.boundingBox({
|
|
122425
|
+
includeNodes: true,
|
|
122426
|
+
includeEdges: false,
|
|
122427
|
+
includeLabels,
|
|
122428
|
+
includeOverlays: false,
|
|
122429
|
+
includeUnderlays: false
|
|
122430
|
+
});
|
|
122431
|
+
}
|
|
122432
|
+
function readGroupGeometry(node, leaves) {
|
|
122433
|
+
const descendants = leaves.filter((leaf) => leaf.ancestors.has(node.id()));
|
|
122434
|
+
const parentBox = readNodeBox(node, true);
|
|
122435
|
+
const descendantUnion = unionBounds(descendants.map((leaf) => leaf.fitBox)) ?? parentBox;
|
|
122472
122436
|
return {
|
|
122473
|
-
id,
|
|
122474
|
-
ancestors: new Set(
|
|
122475
|
-
descendants,
|
|
122437
|
+
id: node.id(),
|
|
122438
|
+
ancestors: new Set(node.ancestors().map((parent) => parent.id())),
|
|
122439
|
+
descendantIds: descendants.map((leaf) => leaf.id),
|
|
122476
122440
|
insets: {
|
|
122477
122441
|
left: Math.max(0, descendantUnion.x1 - parentBox.x1),
|
|
122478
122442
|
right: Math.max(0, parentBox.x2 - descendantUnion.x2),
|
|
@@ -122481,78 +122445,104 @@ function readGroupGeometry(id, ancestorIds, parentBox, leaves) {
|
|
|
122481
122445
|
}
|
|
122482
122446
|
};
|
|
122483
122447
|
}
|
|
122484
|
-
function
|
|
122485
|
-
const
|
|
122448
|
+
function evaluateSpacingCandidate(context, factor) {
|
|
122449
|
+
const leaves = context.leaves.map((leaf) => resolveLeafGeometry(leaf, context.center, factor));
|
|
122450
|
+
const byId = new Map(leaves.map((leaf) => [leaf.id, leaf]));
|
|
122451
|
+
const groups = context.groups.flatMap((group) => resolveGroupBox(group, byId));
|
|
122452
|
+
const fitBounds = unionBounds([
|
|
122453
|
+
...leaves.map((leaf) => leaf.fitBox),
|
|
122454
|
+
...groups.map((group) => group.box)
|
|
122455
|
+
]);
|
|
122456
|
+
const rawFitZoom = fitBounds === undefined ? 1 : getFitZoom(fitBounds, context);
|
|
122457
|
+
const effectiveFitZoom = Math.min(rawFitZoom, context.maxFitZoom);
|
|
122458
|
+
return {
|
|
122459
|
+
effectiveFitZoom,
|
|
122460
|
+
...readCollisionState(createCollisionBoxes(leaves, groups), effectiveFitZoom)
|
|
122461
|
+
};
|
|
122462
|
+
}
|
|
122463
|
+
function resolveLeafGeometry(leaf, center, factor) {
|
|
122464
|
+
return {
|
|
122486
122465
|
id: leaf.id,
|
|
122487
122466
|
ancestors: leaf.ancestors,
|
|
122488
|
-
|
|
122489
|
-
|
|
122490
|
-
|
|
122491
|
-
|
|
122492
|
-
|
|
122493
|
-
|
|
122494
|
-
|
|
122495
|
-
|
|
122496
|
-
return [
|
|
122497
|
-
{
|
|
122498
|
-
id: group.id,
|
|
122499
|
-
ancestors: group.ancestors,
|
|
122500
|
-
kind: "group",
|
|
122501
|
-
box: expandBounds(union, group.insets)
|
|
122502
|
-
}
|
|
122503
|
-
];
|
|
122467
|
+
fitBox: shiftBounds(leaf.fitBox, leaf.position, center, factor),
|
|
122468
|
+
labelBox: leaf.labelBox === null ? null : shiftBounds(leaf.labelBox, leaf.position, center, factor),
|
|
122469
|
+
visualBox: shiftBounds(leaf.visualBox, leaf.position, center, factor)
|
|
122470
|
+
};
|
|
122471
|
+
}
|
|
122472
|
+
function resolveGroupBox(group, leaves) {
|
|
122473
|
+
const descendantBounds = group.descendantIds.flatMap((id) => {
|
|
122474
|
+
const leaf = leaves.get(id);
|
|
122475
|
+
return leaf === undefined ? [] : [leaf.fitBox];
|
|
122504
122476
|
});
|
|
122505
|
-
|
|
122477
|
+
const union = unionBounds(descendantBounds);
|
|
122478
|
+
return union === undefined ? [] : [
|
|
122479
|
+
{
|
|
122480
|
+
id: group.id,
|
|
122481
|
+
ancestors: group.ancestors,
|
|
122482
|
+
kind: "group",
|
|
122483
|
+
box: expandBounds(union, group.insets)
|
|
122484
|
+
}
|
|
122485
|
+
];
|
|
122506
122486
|
}
|
|
122507
|
-
function
|
|
122508
|
-
return
|
|
122487
|
+
function createCollisionBoxes(leaves, groups) {
|
|
122488
|
+
return [
|
|
122489
|
+
...leaves.flatMap((leaf) => leaf.labelBox === null ? [] : [{ id: leaf.id, ancestors: leaf.ancestors, kind: "label", box: leaf.labelBox }]),
|
|
122490
|
+
...leaves.map((leaf) => ({
|
|
122491
|
+
id: leaf.id,
|
|
122492
|
+
ancestors: leaf.ancestors,
|
|
122493
|
+
kind: "visual",
|
|
122494
|
+
box: leaf.visualBox
|
|
122495
|
+
})),
|
|
122496
|
+
...groups
|
|
122497
|
+
];
|
|
122509
122498
|
}
|
|
122510
|
-
function
|
|
122499
|
+
function readCollisionState(boxes, zoom) {
|
|
122511
122500
|
const sorted = [...boxes].sort((first, second) => first.box.x1 - second.box.x1);
|
|
122512
122501
|
const budget = { remaining: MAX_PAIR_CHECKS };
|
|
122502
|
+
const overlap = { maximum: 0 };
|
|
122513
122503
|
for (const [index, first] of sorted.entries()) {
|
|
122514
122504
|
for (let next = index + 1;next < sorted.length; next += 1) {
|
|
122515
122505
|
const second = sorted.at(next);
|
|
122516
|
-
if (
|
|
122506
|
+
if (second === undefined)
|
|
122517
122507
|
break;
|
|
122518
122508
|
if (second.box.x1 >= first.box.x2)
|
|
122519
122509
|
break;
|
|
122510
|
+
if (first.id === second.id || isIntentionalContainment(first, second))
|
|
122511
|
+
continue;
|
|
122512
|
+
const penetration = getOverlapPenetration(first.box, second.box);
|
|
122513
|
+
if (penetration <= 0)
|
|
122514
|
+
continue;
|
|
122520
122515
|
budget.remaining -= 1;
|
|
122521
122516
|
if (budget.remaining < 0)
|
|
122522
|
-
return true;
|
|
122523
|
-
if (
|
|
122517
|
+
return { hardCollision: true, renderedBackgroundOverlap: Infinity };
|
|
122518
|
+
if (first.kind === "visual" && second.kind === "visual") {
|
|
122519
|
+
overlap.maximum = Math.max(overlap.maximum, penetration * zoom);
|
|
122524
122520
|
continue;
|
|
122525
|
-
|
|
122526
|
-
|
|
122521
|
+
}
|
|
122522
|
+
return { hardCollision: true, renderedBackgroundOverlap: overlap.maximum };
|
|
122527
122523
|
}
|
|
122528
122524
|
}
|
|
122529
|
-
return false;
|
|
122525
|
+
return { hardCollision: false, renderedBackgroundOverlap: overlap.maximum };
|
|
122530
122526
|
}
|
|
122531
122527
|
function isIntentionalContainment(first, second) {
|
|
122532
122528
|
return first.ancestors.has(second.id) || second.ancestors.has(first.id);
|
|
122533
122529
|
}
|
|
122534
|
-
function
|
|
122535
|
-
const
|
|
122536
|
-
const
|
|
122537
|
-
|
|
122538
|
-
|
|
122539
|
-
|
|
122540
|
-
|
|
122541
|
-
const
|
|
122542
|
-
|
|
122543
|
-
|
|
122530
|
+
function getOverlapPenetration(first, second) {
|
|
122531
|
+
const width = Math.min(first.x2, second.x2) - Math.max(first.x1, second.x1);
|
|
122532
|
+
const height = Math.min(first.y2, second.y2) - Math.max(first.y1, second.y1);
|
|
122533
|
+
return width > 0 && height > 0 ? Math.min(width, height) : 0;
|
|
122534
|
+
}
|
|
122535
|
+
function getFitZoom(bounds, context) {
|
|
122536
|
+
const width = context.viewport.width - 2 * context.fitPadding;
|
|
122537
|
+
const height = context.viewport.height - 2 * context.fitPadding;
|
|
122538
|
+
if (width <= 0 || height <= 0 || bounds.w <= 0 || bounds.h <= 0)
|
|
122539
|
+
return 1;
|
|
122540
|
+
return Math.min(width / bounds.w, height / bounds.h);
|
|
122544
122541
|
}
|
|
122545
122542
|
function shiftBounds(box, position, center, factor) {
|
|
122546
122543
|
const dx = (position.x - center.x) * (factor - 1);
|
|
122547
122544
|
const dy = (position.y - center.y) * (factor - 1);
|
|
122548
|
-
return {
|
|
122549
|
-
x1: box.x1 + dx,
|
|
122550
|
-
y1: box.y1 + dy,
|
|
122551
|
-
x2: box.x2 + dx,
|
|
122552
|
-
y2: box.y2 + dy,
|
|
122553
|
-
w: box.w,
|
|
122554
|
-
h: box.h
|
|
122555
|
-
};
|
|
122545
|
+
return { ...box, x1: box.x1 + dx, y1: box.y1 + dy, x2: box.x2 + dx, y2: box.y2 + dy };
|
|
122556
122546
|
}
|
|
122557
122547
|
function expandBounds(box, insets) {
|
|
122558
122548
|
const x1 = box.x1 - insets.left;
|
|
@@ -122573,6 +122563,9 @@ function unionBounds(boxes) {
|
|
|
122573
122563
|
function isUsableBox(box) {
|
|
122574
122564
|
return box.w > 0 && box.h > 0;
|
|
122575
122565
|
}
|
|
122566
|
+
function positiveNumber(value, fallback) {
|
|
122567
|
+
return value !== undefined && Number.isFinite(value) && value > 0 ? value : fallback;
|
|
122568
|
+
}
|
|
122576
122569
|
function applyScale(cy, leaves, center, factor) {
|
|
122577
122570
|
const positions = new Map(leaves.map((leaf) => [leaf.id, leaf.position]));
|
|
122578
122571
|
cy.batch(() => {
|
|
@@ -122586,6 +122579,114 @@ function applyScale(cy, leaves, center, factor) {
|
|
|
122586
122579
|
});
|
|
122587
122580
|
}
|
|
122588
122581
|
|
|
122582
|
+
// src/features/graph-view/adapters/inbound/web-artifact/compactGraphSpacing.ts
|
|
122583
|
+
var MAX_EXPANSION_ATTEMPTS = 8;
|
|
122584
|
+
var MAX_NODE_COUNT = 2000;
|
|
122585
|
+
var MAX_RENDERED_BACKGROUND_OVERLAP = 4;
|
|
122586
|
+
var SEARCH_ITERATIONS = 8;
|
|
122587
|
+
var ZERO_TOLERANCE = 0.001;
|
|
122588
|
+
function compactGraphSpacing(cy, spacingFactor, options = {}) {
|
|
122589
|
+
if (spacingFactor <= 0 || cy.nodes(":childless").length < 2 || cy.nodes().length > MAX_NODE_COUNT || cy.nodes(":locked").length > 0)
|
|
122590
|
+
return spacingFactor;
|
|
122591
|
+
const evaluator = createGraphSpacingEvaluator(cy, options);
|
|
122592
|
+
if (evaluator === null)
|
|
122593
|
+
return spacingFactor;
|
|
122594
|
+
const current = evaluator.evaluate(1);
|
|
122595
|
+
const maxFitZoom = options.maxFitZoom ?? Infinity;
|
|
122596
|
+
const minReadableZoom = options.minReadableZoom ?? 0;
|
|
122597
|
+
const factor = needsExpansion(current) ? chooseExpansionFactor(evaluator.evaluate, minReadableZoom) : chooseCompactionFactor(evaluator.evaluate, spacingFactor, maxFitZoom, minReadableZoom, current);
|
|
122598
|
+
if (factor === 1)
|
|
122599
|
+
return spacingFactor;
|
|
122600
|
+
evaluator.apply(factor);
|
|
122601
|
+
return spacingFactor * factor;
|
|
122602
|
+
}
|
|
122603
|
+
function chooseCompactionFactor(evaluate, spacingFactor, maxFitZoom, minReadableZoom, current) {
|
|
122604
|
+
if (reachesFitTarget(current, maxFitZoom))
|
|
122605
|
+
return 1;
|
|
122606
|
+
const minimum = Math.min(1, 0.1 / spacingFactor);
|
|
122607
|
+
if (current.renderedBackgroundOverlap > ZERO_TOLERANCE) {
|
|
122608
|
+
if (reachesReadableTarget(current, minReadableZoom))
|
|
122609
|
+
return 1;
|
|
122610
|
+
return chooseSoftCompactionFactor(evaluate, minimum, 1, minReadableZoom);
|
|
122611
|
+
}
|
|
122612
|
+
const strictMinimum = findMinimumAcceptedFactor(minimum, 1, (factor) => isStrictCandidate(evaluate(factor)));
|
|
122613
|
+
if (strictMinimum === undefined)
|
|
122614
|
+
return 1;
|
|
122615
|
+
const strict = evaluate(strictMinimum);
|
|
122616
|
+
if (reachesFitTarget(strict, maxFitZoom)) {
|
|
122617
|
+
return findMaximumAcceptedFactor(strictMinimum, 1, (factor) => {
|
|
122618
|
+
const candidate = evaluate(factor);
|
|
122619
|
+
return isStrictCandidate(candidate) && reachesFitTarget(candidate, maxFitZoom);
|
|
122620
|
+
}) ?? strictMinimum;
|
|
122621
|
+
}
|
|
122622
|
+
if (reachesReadableTarget(strict, minReadableZoom))
|
|
122623
|
+
return strictMinimum;
|
|
122624
|
+
return chooseSoftCompactionFactor(evaluate, minimum, strictMinimum, minReadableZoom);
|
|
122625
|
+
}
|
|
122626
|
+
function chooseSoftCompactionFactor(evaluate, minimum, maximum, minReadableZoom) {
|
|
122627
|
+
const softMinimum = findMinimumAcceptedFactor(minimum, maximum, (factor) => isSoftCandidate(evaluate(factor)));
|
|
122628
|
+
if (softMinimum === undefined)
|
|
122629
|
+
return maximum;
|
|
122630
|
+
const soft = evaluate(softMinimum);
|
|
122631
|
+
if (!reachesReadableTarget(soft, minReadableZoom))
|
|
122632
|
+
return softMinimum;
|
|
122633
|
+
return findMaximumAcceptedFactor(softMinimum, maximum, (factor) => {
|
|
122634
|
+
const candidate = evaluate(factor);
|
|
122635
|
+
return isSoftCandidate(candidate) && reachesReadableTarget(candidate, minReadableZoom);
|
|
122636
|
+
}) ?? softMinimum;
|
|
122637
|
+
}
|
|
122638
|
+
function chooseExpansionFactor(evaluate, minReadableZoom) {
|
|
122639
|
+
const strict = findExpansionFactor((factor) => isStrictCandidate(evaluate(factor)));
|
|
122640
|
+
if (strict !== undefined && reachesReadableTarget(evaluate(strict), minReadableZoom))
|
|
122641
|
+
return strict;
|
|
122642
|
+
return findExpansionFactor((factor) => isSoftCandidate(evaluate(factor))) ?? strict ?? 1;
|
|
122643
|
+
}
|
|
122644
|
+
function isStrictCandidate(candidate) {
|
|
122645
|
+
return !candidate.hardCollision && candidate.renderedBackgroundOverlap <= ZERO_TOLERANCE;
|
|
122646
|
+
}
|
|
122647
|
+
function isSoftCandidate(candidate) {
|
|
122648
|
+
return !candidate.hardCollision && candidate.renderedBackgroundOverlap <= MAX_RENDERED_BACKGROUND_OVERLAP + ZERO_TOLERANCE;
|
|
122649
|
+
}
|
|
122650
|
+
function needsExpansion(candidate) {
|
|
122651
|
+
return candidate.hardCollision || !isSoftCandidate(candidate);
|
|
122652
|
+
}
|
|
122653
|
+
function reachesFitTarget(candidate, maxFitZoom) {
|
|
122654
|
+
if (!Number.isFinite(maxFitZoom))
|
|
122655
|
+
return false;
|
|
122656
|
+
return candidate.effectiveFitZoom >= maxFitZoom - ZERO_TOLERANCE;
|
|
122657
|
+
}
|
|
122658
|
+
function reachesReadableTarget(candidate, minReadableZoom) {
|
|
122659
|
+
return minReadableZoom <= 0 || candidate.effectiveFitZoom >= minReadableZoom - ZERO_TOLERANCE;
|
|
122660
|
+
}
|
|
122661
|
+
function findMinimumAcceptedFactor(minimum, maximum, accepts) {
|
|
122662
|
+
if (!accepts(maximum))
|
|
122663
|
+
return;
|
|
122664
|
+
if (accepts(minimum))
|
|
122665
|
+
return minimum;
|
|
122666
|
+
return Array.from({ length: SEARCH_ITERATIONS }).reduce((range) => {
|
|
122667
|
+
const factor = (range.low + range.high) / 2;
|
|
122668
|
+
return accepts(factor) ? { low: range.low, high: factor } : { low: factor, high: range.high };
|
|
122669
|
+
}, { low: minimum, high: maximum }).high;
|
|
122670
|
+
}
|
|
122671
|
+
function findMaximumAcceptedFactor(minimum, maximum, accepts) {
|
|
122672
|
+
if (!accepts(minimum))
|
|
122673
|
+
return;
|
|
122674
|
+
if (accepts(maximum))
|
|
122675
|
+
return maximum;
|
|
122676
|
+
return Array.from({ length: SEARCH_ITERATIONS }).reduce((range) => {
|
|
122677
|
+
const factor = (range.low + range.high) / 2;
|
|
122678
|
+
return accepts(factor) ? { low: factor, high: range.high } : { low: range.low, high: factor };
|
|
122679
|
+
}, { low: minimum, high: maximum }).low;
|
|
122680
|
+
}
|
|
122681
|
+
function findExpansionFactor(accepts) {
|
|
122682
|
+
if (accepts(1))
|
|
122683
|
+
return 1;
|
|
122684
|
+
const maximum = Array.from({ length: MAX_EXPANSION_ATTEMPTS }, (_, index) => 2 ** (index + 1)).find(accepts);
|
|
122685
|
+
if (maximum === undefined)
|
|
122686
|
+
return;
|
|
122687
|
+
return findMinimumAcceptedFactor(maximum / 2, maximum, accepts);
|
|
122688
|
+
}
|
|
122689
|
+
|
|
122589
122690
|
// src/features/graph-view/adapters/inbound/web-artifact/fitGraphViewport.ts
|
|
122590
122691
|
function fitGraphViewport(cy, options = {}, maxZoom = Infinity) {
|
|
122591
122692
|
if (cy.destroyed())
|
|
@@ -122661,6 +122762,12 @@ function createGraphController(cy, fitPaddingRef, optimizeFit) {
|
|
|
122661
122762
|
state.scale = 1;
|
|
122662
122763
|
applyZoomLimits(cy, state);
|
|
122663
122764
|
},
|
|
122765
|
+
getFitZoomLimit() {
|
|
122766
|
+
return state.relative ? state.fitZoom : Math.min(state.fitZoom, state.max);
|
|
122767
|
+
},
|
|
122768
|
+
getReadableZoomTarget() {
|
|
122769
|
+
return state.readableZoom;
|
|
122770
|
+
},
|
|
122664
122771
|
settle(fit) {
|
|
122665
122772
|
const logicalZoom = cy.zoom() / state.scale;
|
|
122666
122773
|
const fonts = cy.nodes().filter((node) => !node.isParent() && Boolean(node.style("label"))).map((node) => Number.parseFloat(String(node.style("font-size")))).filter((size) => Number.isFinite(size) && size > 0);
|
|
@@ -123379,7 +123486,11 @@ function requestOptimizedFit(state, options) {
|
|
|
123379
123486
|
if (!input)
|
|
123380
123487
|
return;
|
|
123381
123488
|
const previous = state.optimizedSpacingRef.current ?? input.spacingFactor ?? 1;
|
|
123382
|
-
const spacing = compactGraphSpacing(state.cy, previous
|
|
123489
|
+
const spacing = compactGraphSpacing(state.cy, previous, {
|
|
123490
|
+
fitPadding: options.padding ?? state.fitPaddingRef.current,
|
|
123491
|
+
maxFitZoom: state.viewport.getFitZoomLimit(),
|
|
123492
|
+
minReadableZoom: state.viewport.getReadableZoomTarget()
|
|
123493
|
+
});
|
|
123383
123494
|
state.geometryRef.current = getGraphGeometryKey(state.cy);
|
|
123384
123495
|
state.loopSizer.update();
|
|
123385
123496
|
state.viewport.settle(false);
|
|
@@ -54,7 +54,10 @@ export interface GraphViewViewport {
|
|
|
54
54
|
readonly pan: GraphViewPoint;
|
|
55
55
|
}
|
|
56
56
|
export interface GraphViewFitOptions {
|
|
57
|
-
/**
|
|
57
|
+
/**
|
|
58
|
+
* Optimize effective rendered fit without rerunning the layout algorithm; whole-graph fits only.
|
|
59
|
+
* Stops at the configured fit-size target and keeps semantic labels collision-safe.
|
|
60
|
+
*/
|
|
58
61
|
readonly optimizeSpacing?: boolean;
|
|
59
62
|
readonly nodeIds?: readonly string[];
|
|
60
63
|
readonly padding?: number;
|