@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.
@@ -37237,11 +37237,11 @@ var init_DocumentViewer = __esm({
37237
37237
  DocumentViewer.displayName = "DocumentViewer";
37238
37238
  }
37239
37239
  });
37240
- function measureLabelWidth(text) {
37240
+ function measureLabelWidth(text, fontFamily = "system-ui") {
37241
37241
  if (typeof document === "undefined") return text.length * 6;
37242
37242
  if (!labelMeasureCtx) labelMeasureCtx = document.createElement("canvas").getContext("2d");
37243
37243
  if (!labelMeasureCtx) return text.length * 6;
37244
- labelMeasureCtx.font = "12px system-ui";
37244
+ labelMeasureCtx.font = `12px ${fontFamily}`;
37245
37245
  return labelMeasureCtx.measureText(text).width + 4;
37246
37246
  }
37247
37247
  function getGroupColor(group, groups) {
@@ -37266,6 +37266,9 @@ function mulberry32(a) {
37266
37266
  return ((t ^ t >>> 14) >>> 0) / 4294967296;
37267
37267
  };
37268
37268
  }
37269
+ function edgeKeyOf(a, b) {
37270
+ return a < b ? `${a}\0${b}` : `${b}\0${a}`;
37271
+ }
37269
37272
  function resolveColor3(color, el) {
37270
37273
  const m = /^var\((--[^,)]+)(?:,\s*([^)]+))?\)$/.exec(color.trim());
37271
37274
  if (!m) return color;
@@ -37397,8 +37400,12 @@ var init_GraphCanvas = __esm({
37397
37400
  React90.useEffect(() => {
37398
37401
  const canvas = canvasRef.current;
37399
37402
  if (!canvas || propNodes.length === 0) return;
37400
- const w = logicalW;
37401
- const h = height;
37403
+ const viewW = logicalW;
37404
+ const viewH = height;
37405
+ const layoutScale = Math.max(1, Math.min(3, Math.sqrt(propNodes.length / 12)));
37406
+ const w = viewW * layoutScale;
37407
+ const h = viewH * layoutScale;
37408
+ const labelFont = resolveColor3("var(--font-family)", canvas) || "system-ui";
37402
37409
  const prevPos = /* @__PURE__ */ new Map();
37403
37410
  for (const pn of nodesRef.current) if (pn.x != null && pn.y != null) prevPos.set(pn.id, { x: pn.x, y: pn.y });
37404
37411
  const simNodes = propNodes.map((n, idx) => {
@@ -37433,133 +37440,192 @@ var init_GraphCanvas = __esm({
37433
37440
  const fullRelayout = !laidOutRef.current || overlap < 0.5;
37434
37441
  if (layout === "force") {
37435
37442
  const maxIterations = 300;
37436
- const tick = () => {
37443
+ const COOL = 0.12;
37444
+ const COLLIDE_PASSES = 6;
37445
+ const LABEL_GAP = 12;
37446
+ const LABEL_H = 16;
37447
+ const SIMILARITY_NEIGHBORS = 5;
37448
+ const SIMILARITY_MIN_WEIGHT = 0.25;
37449
+ const drawnPairs = /* @__PURE__ */ new Set();
37450
+ for (const edge of propEdges) drawnPairs.add(edgeKeyOf(edge.source, edge.target));
37451
+ const similarityNeighbors = /* @__PURE__ */ new Map();
37452
+ {
37453
+ const bySource = /* @__PURE__ */ new Map();
37454
+ for (const pair of propSimilarity) {
37455
+ if (pair.weight < SIMILARITY_MIN_WEIGHT) continue;
37456
+ if (drawnPairs.has(edgeKeyOf(pair.source, pair.target))) continue;
37457
+ const arr = bySource.get(pair.source) ?? [];
37458
+ arr.push(pair);
37459
+ bySource.set(pair.source, arr);
37460
+ }
37461
+ for (const [id, arr] of bySource) {
37462
+ arr.sort((a, b) => b.weight - a.weight);
37463
+ const keep = new Set(arr.slice(0, SIMILARITY_NEIGHBORS).map((p) => p.target));
37464
+ similarityNeighbors.set(id, keep);
37465
+ }
37466
+ }
37467
+ const activeSimilarity = propSimilarity.filter((pair) => {
37468
+ if (drawnPairs.has(edgeKeyOf(pair.source, pair.target))) return false;
37469
+ const a = similarityNeighbors.get(pair.source);
37470
+ const b = similarityNeighbors.get(pair.target);
37471
+ return (a?.has(pair.target) ?? false) || (b?.has(pair.source) ?? false);
37472
+ });
37473
+ const tick = (iter, options) => {
37474
+ const skipForces = options?.skipForces ?? false;
37437
37475
  const nodes = nodesRef.current;
37438
37476
  const centerX = w / 2;
37439
37477
  const centerY = h / 2;
37478
+ const temp = skipForces ? 0 : Math.max(COOL, 1 - iter / maxIterations);
37440
37479
  for (const node of nodes) {
37441
37480
  node.fx = 0;
37442
37481
  node.fy = 0;
37443
37482
  }
37444
- for (let i = 0; i < nodes.length; i++) {
37445
- for (let j = i + 1; j < nodes.length; j++) {
37446
- const dx = nodes[j].x - nodes[i].x;
37447
- const dy = nodes[j].y - nodes[i].y;
37448
- const dist = Math.sqrt(dx * dx + dy * dy) || 1;
37449
- const force = repulsion / (dist * dist);
37450
- const fx = dx / dist * force;
37451
- const fy = dy / dist * force;
37452
- nodes[i].fx -= fx;
37453
- nodes[i].fy -= fy;
37454
- nodes[j].fx += fx;
37455
- nodes[j].fy += fy;
37483
+ if (!skipForces) {
37484
+ for (let i = 0; i < nodes.length; i++) {
37485
+ for (let j = i + 1; j < nodes.length; j++) {
37486
+ const dx = nodes[j].x - nodes[i].x;
37487
+ const dy = nodes[j].y - nodes[i].y;
37488
+ const dist = Math.sqrt(dx * dx + dy * dy) || 1;
37489
+ const force = repulsion / (dist * dist) * temp;
37490
+ const fx = dx / dist * force;
37491
+ const fy = dy / dist * force;
37492
+ nodes[i].fx -= fx;
37493
+ nodes[i].fy -= fy;
37494
+ nodes[j].fx += fx;
37495
+ nodes[j].fy += fy;
37496
+ }
37456
37497
  }
37457
- }
37458
- const nodeById = new Map(nodes.map((n) => [n.id, n]));
37459
- if (propSimilarity.length > 0) {
37460
- for (const pair of propSimilarity) {
37461
- const source = nodeById.get(pair.source);
37462
- const target = nodeById.get(pair.target);
37463
- if (!source || !target) continue;
37464
- const dx = target.x - source.x;
37465
- const dy = target.y - source.y;
37498
+ const nodeById = new Map(nodes.map((n) => [n.id, n]));
37499
+ const spring = (a, b, rest, k) => {
37500
+ const dx = b.x - a.x;
37501
+ const dy = b.y - a.y;
37466
37502
  const dist = Math.sqrt(dx * dx + dy * dy) || 1;
37467
- const w2 = Math.min(1, Math.max(0, pair.weight));
37468
- const linkTarget = linkDistance * (1 - w2);
37469
- const force = (dist - linkTarget) * (0.04 + 0.1 * w2);
37503
+ const force = (dist - rest) * k * temp;
37470
37504
  const fx = dx / dist * force;
37471
37505
  const fy = dy / dist * force;
37472
- source.fx += fx;
37473
- source.fy += fy;
37474
- target.fx -= fx;
37475
- target.fy -= fy;
37476
- }
37477
- } else {
37506
+ a.fx += fx;
37507
+ a.fy += fy;
37508
+ b.fx -= fx;
37509
+ b.fy -= fy;
37510
+ };
37478
37511
  for (const edge of propEdges) {
37479
37512
  const source = nodeById.get(edge.source);
37480
37513
  const target = nodeById.get(edge.target);
37481
37514
  if (!source || !target) continue;
37482
- const dx = target.x - source.x;
37483
- const dy = target.y - source.y;
37484
- const dist = Math.sqrt(dx * dx + dy * dy) || 1;
37515
+ drawnPairs.add(edgeKeyOf(edge.source, edge.target));
37485
37516
  const w2 = Math.min(1, Math.max(0, edge.weight ?? 1));
37486
- const linkTarget = linkDistance * (0.5 + (1 - w2) * 1.5);
37487
- const force = (dist - linkTarget) * (0.04 + 0.1 * w2);
37488
- const fx = dx / dist * force;
37489
- const fy = dy / dist * force;
37490
- source.fx += fx;
37491
- source.fy += fy;
37492
- target.fx -= fx;
37493
- target.fy -= fy;
37517
+ spring(source, target, linkDistance * (0.35 + (1 - w2) * 0.3), 0.14);
37494
37518
  }
37495
- }
37496
- const centroids = /* @__PURE__ */ new Map();
37497
- for (const node of nodes) {
37498
- const g = node.group ?? "__none__";
37499
- let c = centroids.get(g);
37500
- if (!c) {
37501
- c = { x: 0, y: 0, n: 0 };
37502
- centroids.set(g, c);
37519
+ for (const pair of activeSimilarity) {
37520
+ const source = nodeById.get(pair.source);
37521
+ const target = nodeById.get(pair.target);
37522
+ if (!source || !target) continue;
37523
+ const w2 = Math.min(1, Math.max(0, pair.weight));
37524
+ spring(source, target, linkDistance * (1.35 - 0.55 * w2), 0.015);
37503
37525
  }
37504
- c.x += node.x;
37505
- c.y += node.y;
37506
- c.n += 1;
37507
- }
37508
- for (const node of nodes) {
37509
- const c = centroids.get(node.group ?? "__none__");
37510
- if (c && c.n > 1) {
37511
- node.fx += (c.x / c.n - node.x) * 0.04;
37512
- node.fy += (c.y / c.n - node.y) * 0.04;
37513
- } else {
37514
- node.fx += (centerX - node.x) * 0.01;
37515
- node.fy += (centerY - node.y) * 0.01;
37526
+ const centroids = /* @__PURE__ */ new Map();
37527
+ for (const node of nodes) {
37528
+ const g = node.group ?? "__none__";
37529
+ let c = centroids.get(g);
37530
+ if (!c) {
37531
+ c = { x: 0, y: 0, n: 0 };
37532
+ centroids.set(g, c);
37533
+ }
37534
+ c.x += node.x;
37535
+ c.y += node.y;
37536
+ c.n += 1;
37537
+ }
37538
+ const multiCluster = centroids.size > 1;
37539
+ for (const node of nodes) {
37540
+ if (multiCluster) {
37541
+ const c = centroids.get(node.group ?? "__none__");
37542
+ if (c && c.n > 1) {
37543
+ node.fx += (c.x / c.n - node.x) * 0.06 * temp;
37544
+ node.fy += (c.y / c.n - node.y) * 0.06 * temp;
37545
+ } else {
37546
+ node.fx += (centerX - node.x) * 0.01 * temp;
37547
+ node.fy += (centerY - node.y) * 0.01 * temp;
37548
+ }
37549
+ } else {
37550
+ node.fx += (centerX - node.x) * 8e-3 * temp;
37551
+ node.fy += (centerY - node.y) * 8e-3 * temp;
37552
+ }
37553
+ }
37554
+ const damping = 0.9;
37555
+ for (const node of nodes) {
37556
+ node.vx = (node.vx + node.fx) * damping;
37557
+ node.vy = (node.vy + node.fy) * damping;
37558
+ node.x += node.vx;
37559
+ node.y += node.vy;
37516
37560
  }
37517
37561
  }
37518
- const damping = 0.9;
37519
37562
  for (const node of nodes) {
37520
- node.vx = (node.vx + node.fx) * damping;
37521
- node.vy = (node.vy + node.fy) * damping;
37522
- node.x += node.vx;
37523
- node.y += node.vy;
37524
37563
  node.x = Math.max(30, Math.min(w - 30, node.x));
37525
37564
  node.y = Math.max(30, Math.min(h - 30, node.y));
37526
37565
  }
37527
- const LABEL_GAP = 12;
37528
- const LABEL_H = 16;
37529
37566
  const pad = nodeSpacing / 2;
37530
- for (let i = 0; i < nodes.length; i++) {
37531
- for (let j = i + 1; j < nodes.length; j++) {
37532
- const a = nodes[i];
37533
- const b = nodes[j];
37534
- const ar = a.size || 8;
37535
- const br = b.size || 8;
37536
- if (showLabels) {
37537
- if (a.labelW === void 0) a.labelW = a.label ? measureLabelWidth(a.label) : 0;
37538
- if (b.labelW === void 0) b.labelW = b.label ? measureLabelWidth(b.label) : 0;
37539
- }
37540
- const labelBelow = showLabels ? LABEL_GAP + LABEL_H : 0;
37541
- const aHalfW = Math.max(ar, (a.labelW ?? 0) / 2) + pad;
37542
- const bHalfW = Math.max(br, (b.labelW ?? 0) / 2) + pad;
37543
- const aTop = a.y - ar - pad, aBot = a.y + ar + labelBelow + pad;
37544
- const bTop = b.y - br - pad, bBot = b.y + br + labelBelow + pad;
37545
- const overlapX = Math.min(a.x + aHalfW, b.x + bHalfW) - Math.max(a.x - aHalfW, b.x - bHalfW);
37546
- const overlapY = Math.min(aBot, bBot) - Math.max(aTop, bTop);
37547
- if (overlapX > 0 && overlapY > 0) {
37548
- if (overlapX <= overlapY) {
37549
- const push = overlapX / 2 * (b.x >= a.x ? 1 : -1);
37550
- a.x = Math.max(30, Math.min(w - 30, a.x - push));
37551
- b.x = Math.max(30, Math.min(w - 30, b.x + push));
37552
- } else {
37553
- const push = overlapY / 2 * (bTop + bBot >= aTop + aBot ? 1 : -1);
37554
- a.y = Math.max(30, Math.min(h - 30, a.y - push));
37555
- b.y = Math.max(30, Math.min(h - 30, b.y + push));
37567
+ const rectsOf = (n) => {
37568
+ const r2 = n.size || 8;
37569
+ const lw = showLabels ? n.labelW ?? (n.labelW = n.label ? measureLabelWidth(n.label, labelFont) : 0) : 0;
37570
+ return {
37571
+ circle: [n.x - r2 - pad, n.y - r2 - pad, n.x + r2 + pad, n.y + r2 + pad],
37572
+ label: [n.x - lw / 2 - pad, n.y + r2 + LABEL_GAP - 8, n.x + lw / 2 + pad, n.y + r2 + LABEL_GAP + LABEL_H]
37573
+ };
37574
+ };
37575
+ const sep = (r1, r2) => {
37576
+ const ox = Math.min(r1[2], r2[2]) - Math.max(r1[0], r2[0]);
37577
+ const oy = Math.min(r1[3], r2[3]) - Math.max(r1[1], r2[1]);
37578
+ if (ox <= 0 || oy <= 0) return null;
37579
+ 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 };
37580
+ };
37581
+ for (let pass = 0; pass < COLLIDE_PASSES; pass++) {
37582
+ for (let i = 0; i < nodes.length; i++) {
37583
+ for (let j = i + 1; j < nodes.length; j++) {
37584
+ const a = nodes[i];
37585
+ const b = nodes[j];
37586
+ const ra = rectsOf(a);
37587
+ const rb = rectsOf(b);
37588
+ let best = null;
37589
+ for (const [r1, r2] of [[ra.circle, rb.circle], [ra.circle, rb.label], [ra.label, rb.circle], [ra.label, rb.label]]) {
37590
+ const s = sep(r1, r2);
37591
+ if (s && (!best || s.depth < best.depth)) best = s;
37592
+ }
37593
+ if (best) {
37594
+ const push = best.depth / 2;
37595
+ if (best.axis === "x") {
37596
+ a.x = Math.max(30, Math.min(w - 30, a.x - push * best.sign));
37597
+ b.x = Math.max(30, Math.min(w - 30, b.x + push * best.sign));
37598
+ a.vx = 0;
37599
+ b.vx = 0;
37600
+ } else {
37601
+ a.y = Math.max(30, Math.min(h - 30, a.y - push * best.sign));
37602
+ b.y = Math.max(30, Math.min(h - 30, b.y + push * best.sign));
37603
+ a.vy = 0;
37604
+ b.vy = 0;
37605
+ }
37556
37606
  }
37557
37607
  }
37558
37608
  }
37559
37609
  }
37560
37610
  };
37561
37611
  if (fullRelayout) {
37562
- for (let i = 0; i < maxIterations; i++) tick();
37612
+ for (let i = 0; i < maxIterations; i++) tick(i);
37613
+ for (let i = 0; i < 4; i++) tick(maxIterations, { skipForces: true });
37614
+ const fitPad = 40;
37615
+ let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
37616
+ for (const node of nodesRef.current) {
37617
+ const r2 = node.size || 8;
37618
+ const lw = showLabels ? node.labelW ?? (node.labelW = node.label ? measureLabelWidth(node.label, labelFont) : 0) : 0;
37619
+ minX = Math.min(minX, node.x - r2, node.x - lw / 2);
37620
+ minY = Math.min(minY, node.y - r2);
37621
+ maxX = Math.max(maxX, node.x + r2, node.x + lw / 2);
37622
+ maxY = Math.max(maxY, node.y + r2 + LABEL_GAP + LABEL_H);
37623
+ }
37624
+ const bboxW = Math.max(1, maxX - minX + fitPad * 2);
37625
+ const bboxH = Math.max(1, maxY - minY + fitPad * 2);
37626
+ const nextZoom = Math.min(1, viewW / bboxW, viewH / bboxH);
37627
+ setZoom(nextZoom);
37628
+ setOffset({ x: fitPad - minX * nextZoom, y: fitPad - minY * nextZoom });
37563
37629
  laidOutRef.current = true;
37564
37630
  } else {
37565
37631
  const centroids = /* @__PURE__ */ new Map();