@almadar/ui 5.118.0 → 5.120.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/avl/index.js CHANGED
@@ -37191,11 +37191,11 @@ var init_DocumentViewer = __esm({
37191
37191
  DocumentViewer.displayName = "DocumentViewer";
37192
37192
  }
37193
37193
  });
37194
- function measureLabelWidth(text) {
37194
+ function measureLabelWidth(text, fontFamily = "system-ui") {
37195
37195
  if (typeof document === "undefined") return text.length * 6;
37196
37196
  if (!labelMeasureCtx) labelMeasureCtx = document.createElement("canvas").getContext("2d");
37197
37197
  if (!labelMeasureCtx) return text.length * 6;
37198
- labelMeasureCtx.font = "12px system-ui";
37198
+ labelMeasureCtx.font = `12px ${fontFamily}`;
37199
37199
  return labelMeasureCtx.measureText(text).width + 4;
37200
37200
  }
37201
37201
  function getGroupColor(group, groups) {
@@ -37220,6 +37220,9 @@ function mulberry32(a) {
37220
37220
  return ((t ^ t >>> 14) >>> 0) / 4294967296;
37221
37221
  };
37222
37222
  }
37223
+ function edgeKeyOf(a, b) {
37224
+ return a < b ? `${a}\0${b}` : `${b}\0${a}`;
37225
+ }
37223
37226
  function resolveColor3(color, el) {
37224
37227
  const m = /^var\((--[^,)]+)(?:,\s*([^)]+))?\)$/.exec(color.trim());
37225
37228
  if (!m) return color;
@@ -37351,8 +37354,12 @@ var init_GraphCanvas = __esm({
37351
37354
  useEffect(() => {
37352
37355
  const canvas = canvasRef.current;
37353
37356
  if (!canvas || propNodes.length === 0) return;
37354
- const w = logicalW;
37355
- const h = height;
37357
+ const viewW = logicalW;
37358
+ const viewH = height;
37359
+ const layoutScale = Math.max(1, Math.min(3, Math.sqrt(propNodes.length / 12)));
37360
+ const w = viewW * layoutScale;
37361
+ const h = viewH * layoutScale;
37362
+ const labelFont = resolveColor3("var(--font-family)", canvas) || "system-ui";
37356
37363
  const prevPos = /* @__PURE__ */ new Map();
37357
37364
  for (const pn of nodesRef.current) if (pn.x != null && pn.y != null) prevPos.set(pn.id, { x: pn.x, y: pn.y });
37358
37365
  const simNodes = propNodes.map((n, idx) => {
@@ -37387,133 +37394,192 @@ var init_GraphCanvas = __esm({
37387
37394
  const fullRelayout = !laidOutRef.current || overlap < 0.5;
37388
37395
  if (layout === "force") {
37389
37396
  const maxIterations = 300;
37390
- const tick = () => {
37397
+ const COOL = 0.12;
37398
+ const COLLIDE_PASSES = 6;
37399
+ const LABEL_GAP = 12;
37400
+ const LABEL_H = 16;
37401
+ const SIMILARITY_NEIGHBORS = 5;
37402
+ const SIMILARITY_MIN_WEIGHT = 0.25;
37403
+ const drawnPairs = /* @__PURE__ */ new Set();
37404
+ for (const edge of propEdges) drawnPairs.add(edgeKeyOf(edge.source, edge.target));
37405
+ const similarityNeighbors = /* @__PURE__ */ new Map();
37406
+ {
37407
+ const bySource = /* @__PURE__ */ new Map();
37408
+ for (const pair of propSimilarity) {
37409
+ if (pair.weight < SIMILARITY_MIN_WEIGHT) continue;
37410
+ if (drawnPairs.has(edgeKeyOf(pair.source, pair.target))) continue;
37411
+ const arr = bySource.get(pair.source) ?? [];
37412
+ arr.push(pair);
37413
+ bySource.set(pair.source, arr);
37414
+ }
37415
+ for (const [id, arr] of bySource) {
37416
+ arr.sort((a, b) => b.weight - a.weight);
37417
+ const keep = new Set(arr.slice(0, SIMILARITY_NEIGHBORS).map((p) => p.target));
37418
+ similarityNeighbors.set(id, keep);
37419
+ }
37420
+ }
37421
+ const activeSimilarity = propSimilarity.filter((pair) => {
37422
+ if (drawnPairs.has(edgeKeyOf(pair.source, pair.target))) return false;
37423
+ const a = similarityNeighbors.get(pair.source);
37424
+ const b = similarityNeighbors.get(pair.target);
37425
+ return (a?.has(pair.target) ?? false) || (b?.has(pair.source) ?? false);
37426
+ });
37427
+ const tick = (iter, options) => {
37428
+ const skipForces = options?.skipForces ?? false;
37391
37429
  const nodes = nodesRef.current;
37392
37430
  const centerX = w / 2;
37393
37431
  const centerY = h / 2;
37432
+ const temp = skipForces ? 0 : Math.max(COOL, 1 - iter / maxIterations);
37394
37433
  for (const node of nodes) {
37395
37434
  node.fx = 0;
37396
37435
  node.fy = 0;
37397
37436
  }
37398
- for (let i = 0; i < nodes.length; i++) {
37399
- for (let j = i + 1; j < nodes.length; j++) {
37400
- const dx = nodes[j].x - nodes[i].x;
37401
- const dy = nodes[j].y - nodes[i].y;
37402
- const dist = Math.sqrt(dx * dx + dy * dy) || 1;
37403
- const force = repulsion / (dist * dist);
37404
- const fx = dx / dist * force;
37405
- const fy = dy / dist * force;
37406
- nodes[i].fx -= fx;
37407
- nodes[i].fy -= fy;
37408
- nodes[j].fx += fx;
37409
- nodes[j].fy += fy;
37437
+ if (!skipForces) {
37438
+ for (let i = 0; i < nodes.length; i++) {
37439
+ for (let j = i + 1; j < nodes.length; j++) {
37440
+ const dx = nodes[j].x - nodes[i].x;
37441
+ const dy = nodes[j].y - nodes[i].y;
37442
+ const dist = Math.sqrt(dx * dx + dy * dy) || 1;
37443
+ const force = repulsion / (dist * dist) * temp;
37444
+ const fx = dx / dist * force;
37445
+ const fy = dy / dist * force;
37446
+ nodes[i].fx -= fx;
37447
+ nodes[i].fy -= fy;
37448
+ nodes[j].fx += fx;
37449
+ nodes[j].fy += fy;
37450
+ }
37410
37451
  }
37411
- }
37412
- const nodeById = new Map(nodes.map((n) => [n.id, n]));
37413
- if (propSimilarity.length > 0) {
37414
- for (const pair of propSimilarity) {
37415
- const source = nodeById.get(pair.source);
37416
- const target = nodeById.get(pair.target);
37417
- if (!source || !target) continue;
37418
- const dx = target.x - source.x;
37419
- const dy = target.y - source.y;
37452
+ const nodeById = new Map(nodes.map((n) => [n.id, n]));
37453
+ const spring = (a, b, rest, k) => {
37454
+ const dx = b.x - a.x;
37455
+ const dy = b.y - a.y;
37420
37456
  const dist = Math.sqrt(dx * dx + dy * dy) || 1;
37421
- const w2 = Math.min(1, Math.max(0, pair.weight));
37422
- const linkTarget = linkDistance * (1 - w2);
37423
- const force = (dist - linkTarget) * (0.04 + 0.1 * w2);
37457
+ const force = (dist - rest) * k * temp;
37424
37458
  const fx = dx / dist * force;
37425
37459
  const fy = dy / dist * force;
37426
- source.fx += fx;
37427
- source.fy += fy;
37428
- target.fx -= fx;
37429
- target.fy -= fy;
37430
- }
37431
- } else {
37460
+ a.fx += fx;
37461
+ a.fy += fy;
37462
+ b.fx -= fx;
37463
+ b.fy -= fy;
37464
+ };
37432
37465
  for (const edge of propEdges) {
37433
37466
  const source = nodeById.get(edge.source);
37434
37467
  const target = nodeById.get(edge.target);
37435
37468
  if (!source || !target) continue;
37436
- const dx = target.x - source.x;
37437
- const dy = target.y - source.y;
37438
- const dist = Math.sqrt(dx * dx + dy * dy) || 1;
37469
+ drawnPairs.add(edgeKeyOf(edge.source, edge.target));
37439
37470
  const w2 = Math.min(1, Math.max(0, edge.weight ?? 1));
37440
- const linkTarget = linkDistance * (0.5 + (1 - w2) * 1.5);
37441
- const force = (dist - linkTarget) * (0.04 + 0.1 * w2);
37442
- const fx = dx / dist * force;
37443
- const fy = dy / dist * force;
37444
- source.fx += fx;
37445
- source.fy += fy;
37446
- target.fx -= fx;
37447
- target.fy -= fy;
37471
+ spring(source, target, linkDistance * (0.35 + (1 - w2) * 0.3), 0.14);
37448
37472
  }
37449
- }
37450
- const centroids = /* @__PURE__ */ new Map();
37451
- for (const node of nodes) {
37452
- const g = node.group ?? "__none__";
37453
- let c = centroids.get(g);
37454
- if (!c) {
37455
- c = { x: 0, y: 0, n: 0 };
37456
- centroids.set(g, c);
37473
+ for (const pair of activeSimilarity) {
37474
+ const source = nodeById.get(pair.source);
37475
+ const target = nodeById.get(pair.target);
37476
+ if (!source || !target) continue;
37477
+ const w2 = Math.min(1, Math.max(0, pair.weight));
37478
+ spring(source, target, linkDistance * (1.35 - 0.55 * w2), 0.015);
37457
37479
  }
37458
- c.x += node.x;
37459
- c.y += node.y;
37460
- c.n += 1;
37461
- }
37462
- for (const node of nodes) {
37463
- const c = centroids.get(node.group ?? "__none__");
37464
- if (c && c.n > 1) {
37465
- node.fx += (c.x / c.n - node.x) * 0.04;
37466
- node.fy += (c.y / c.n - node.y) * 0.04;
37467
- } else {
37468
- node.fx += (centerX - node.x) * 0.01;
37469
- node.fy += (centerY - node.y) * 0.01;
37480
+ const centroids = /* @__PURE__ */ new Map();
37481
+ for (const node of nodes) {
37482
+ const g = node.group ?? "__none__";
37483
+ let c = centroids.get(g);
37484
+ if (!c) {
37485
+ c = { x: 0, y: 0, n: 0 };
37486
+ centroids.set(g, c);
37487
+ }
37488
+ c.x += node.x;
37489
+ c.y += node.y;
37490
+ c.n += 1;
37491
+ }
37492
+ const multiCluster = centroids.size > 1;
37493
+ for (const node of nodes) {
37494
+ if (multiCluster) {
37495
+ const c = centroids.get(node.group ?? "__none__");
37496
+ if (c && c.n > 1) {
37497
+ node.fx += (c.x / c.n - node.x) * 0.06 * temp;
37498
+ node.fy += (c.y / c.n - node.y) * 0.06 * temp;
37499
+ } else {
37500
+ node.fx += (centerX - node.x) * 0.01 * temp;
37501
+ node.fy += (centerY - node.y) * 0.01 * temp;
37502
+ }
37503
+ } else {
37504
+ node.fx += (centerX - node.x) * 8e-3 * temp;
37505
+ node.fy += (centerY - node.y) * 8e-3 * temp;
37506
+ }
37507
+ }
37508
+ const damping = 0.9;
37509
+ for (const node of nodes) {
37510
+ node.vx = (node.vx + node.fx) * damping;
37511
+ node.vy = (node.vy + node.fy) * damping;
37512
+ node.x += node.vx;
37513
+ node.y += node.vy;
37470
37514
  }
37471
37515
  }
37472
- const damping = 0.9;
37473
37516
  for (const node of nodes) {
37474
- node.vx = (node.vx + node.fx) * damping;
37475
- node.vy = (node.vy + node.fy) * damping;
37476
- node.x += node.vx;
37477
- node.y += node.vy;
37478
37517
  node.x = Math.max(30, Math.min(w - 30, node.x));
37479
37518
  node.y = Math.max(30, Math.min(h - 30, node.y));
37480
37519
  }
37481
- const LABEL_GAP = 12;
37482
- const LABEL_H = 16;
37483
37520
  const pad = nodeSpacing / 2;
37484
- for (let i = 0; i < nodes.length; i++) {
37485
- for (let j = i + 1; j < nodes.length; j++) {
37486
- const a = nodes[i];
37487
- const b = nodes[j];
37488
- const ar = a.size || 8;
37489
- const br = b.size || 8;
37490
- if (showLabels) {
37491
- if (a.labelW === void 0) a.labelW = a.label ? measureLabelWidth(a.label) : 0;
37492
- if (b.labelW === void 0) b.labelW = b.label ? measureLabelWidth(b.label) : 0;
37493
- }
37494
- const labelBelow = showLabels ? LABEL_GAP + LABEL_H : 0;
37495
- const aHalfW = Math.max(ar, (a.labelW ?? 0) / 2) + pad;
37496
- const bHalfW = Math.max(br, (b.labelW ?? 0) / 2) + pad;
37497
- const aTop = a.y - ar - pad, aBot = a.y + ar + labelBelow + pad;
37498
- const bTop = b.y - br - pad, bBot = b.y + br + labelBelow + pad;
37499
- const overlapX = Math.min(a.x + aHalfW, b.x + bHalfW) - Math.max(a.x - aHalfW, b.x - bHalfW);
37500
- const overlapY = Math.min(aBot, bBot) - Math.max(aTop, bTop);
37501
- if (overlapX > 0 && overlapY > 0) {
37502
- if (overlapX <= overlapY) {
37503
- const push = overlapX / 2 * (b.x >= a.x ? 1 : -1);
37504
- a.x = Math.max(30, Math.min(w - 30, a.x - push));
37505
- b.x = Math.max(30, Math.min(w - 30, b.x + push));
37506
- } else {
37507
- const push = overlapY / 2 * (bTop + bBot >= aTop + aBot ? 1 : -1);
37508
- a.y = Math.max(30, Math.min(h - 30, a.y - push));
37509
- b.y = Math.max(30, Math.min(h - 30, b.y + push));
37521
+ const rectsOf = (n) => {
37522
+ const r2 = n.size || 8;
37523
+ const lw = showLabels ? n.labelW ?? (n.labelW = n.label ? measureLabelWidth(n.label, labelFont) : 0) : 0;
37524
+ return {
37525
+ circle: [n.x - r2 - pad, n.y - r2 - pad, n.x + r2 + pad, n.y + r2 + pad],
37526
+ label: [n.x - lw / 2 - pad, n.y + r2 + LABEL_GAP - 8, n.x + lw / 2 + pad, n.y + r2 + LABEL_GAP + LABEL_H]
37527
+ };
37528
+ };
37529
+ const sep = (r1, r2) => {
37530
+ const ox = Math.min(r1[2], r2[2]) - Math.max(r1[0], r2[0]);
37531
+ const oy = Math.min(r1[3], r2[3]) - Math.max(r1[1], r2[1]);
37532
+ if (ox <= 0 || oy <= 0) return null;
37533
+ return ox <= oy ? { axis: "x", depth: ox, sign: r1[0] + r1[2] < r2[0] + r2[2] ? 1 : -1 } : { axis: "y", depth: oy, sign: r1[1] + r1[3] < r2[1] + r2[3] ? 1 : -1 };
37534
+ };
37535
+ for (let pass = 0; pass < COLLIDE_PASSES; pass++) {
37536
+ for (let i = 0; i < nodes.length; i++) {
37537
+ for (let j = i + 1; j < nodes.length; j++) {
37538
+ const a = nodes[i];
37539
+ const b = nodes[j];
37540
+ const ra = rectsOf(a);
37541
+ const rb = rectsOf(b);
37542
+ let best = null;
37543
+ for (const [r1, r2] of [[ra.circle, rb.circle], [ra.circle, rb.label], [ra.label, rb.circle], [ra.label, rb.label]]) {
37544
+ const s = sep(r1, r2);
37545
+ if (s && (!best || s.depth < best.depth)) best = s;
37546
+ }
37547
+ if (best) {
37548
+ const push = best.depth / 2;
37549
+ if (best.axis === "x") {
37550
+ a.x = Math.max(30, Math.min(w - 30, a.x - push * best.sign));
37551
+ b.x = Math.max(30, Math.min(w - 30, b.x + push * best.sign));
37552
+ a.vx = 0;
37553
+ b.vx = 0;
37554
+ } else {
37555
+ a.y = Math.max(30, Math.min(h - 30, a.y - push * best.sign));
37556
+ b.y = Math.max(30, Math.min(h - 30, b.y + push * best.sign));
37557
+ a.vy = 0;
37558
+ b.vy = 0;
37559
+ }
37510
37560
  }
37511
37561
  }
37512
37562
  }
37513
37563
  }
37514
37564
  };
37515
37565
  if (fullRelayout) {
37516
- for (let i = 0; i < maxIterations; i++) tick();
37566
+ for (let i = 0; i < maxIterations; i++) tick(i);
37567
+ for (let i = 0; i < 4; i++) tick(maxIterations, { skipForces: true });
37568
+ const fitPad = 40;
37569
+ let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
37570
+ for (const node of nodesRef.current) {
37571
+ const r2 = node.size || 8;
37572
+ const lw = showLabels ? node.labelW ?? (node.labelW = node.label ? measureLabelWidth(node.label, labelFont) : 0) : 0;
37573
+ minX = Math.min(minX, node.x - r2, node.x - lw / 2);
37574
+ minY = Math.min(minY, node.y - r2);
37575
+ maxX = Math.max(maxX, node.x + r2, node.x + lw / 2);
37576
+ maxY = Math.max(maxY, node.y + r2 + LABEL_GAP + LABEL_H);
37577
+ }
37578
+ const bboxW = Math.max(1, maxX - minX + fitPad * 2);
37579
+ const bboxH = Math.max(1, maxY - minY + fitPad * 2);
37580
+ const nextZoom = Math.min(1, viewW / bboxW, viewH / bboxH);
37581
+ setZoom(nextZoom);
37582
+ setOffset({ x: fitPad - minX * nextZoom, y: fitPad - minY * nextZoom });
37517
37583
  laidOutRef.current = true;
37518
37584
  } else {
37519
37585
  const centroids = /* @__PURE__ */ new Map();