@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.
@@ -35322,11 +35322,11 @@ var init_DocumentViewer = __esm({
35322
35322
  DocumentViewer.displayName = "DocumentViewer";
35323
35323
  }
35324
35324
  });
35325
- function measureLabelWidth(text) {
35325
+ function measureLabelWidth(text, fontFamily = "system-ui") {
35326
35326
  if (typeof document === "undefined") return text.length * 6;
35327
35327
  if (!labelMeasureCtx) labelMeasureCtx = document.createElement("canvas").getContext("2d");
35328
35328
  if (!labelMeasureCtx) return text.length * 6;
35329
- labelMeasureCtx.font = "12px system-ui";
35329
+ labelMeasureCtx.font = `12px ${fontFamily}`;
35330
35330
  return labelMeasureCtx.measureText(text).width + 4;
35331
35331
  }
35332
35332
  function getGroupColor(group, groups) {
@@ -35351,6 +35351,9 @@ function mulberry32(a) {
35351
35351
  return ((t ^ t >>> 14) >>> 0) / 4294967296;
35352
35352
  };
35353
35353
  }
35354
+ function edgeKeyOf(a, b) {
35355
+ return a < b ? `${a}\0${b}` : `${b}\0${a}`;
35356
+ }
35354
35357
  function resolveColor3(color, el) {
35355
35358
  const m = /^var\((--[^,)]+)(?:,\s*([^)]+))?\)$/.exec(color.trim());
35356
35359
  if (!m) return color;
@@ -35482,8 +35485,12 @@ var init_GraphCanvas = __esm({
35482
35485
  useEffect(() => {
35483
35486
  const canvas = canvasRef.current;
35484
35487
  if (!canvas || propNodes.length === 0) return;
35485
- const w = logicalW;
35486
- const h = height;
35488
+ const viewW = logicalW;
35489
+ const viewH = height;
35490
+ const layoutScale = Math.max(1, Math.min(3, Math.sqrt(propNodes.length / 12)));
35491
+ const w = viewW * layoutScale;
35492
+ const h = viewH * layoutScale;
35493
+ const labelFont = resolveColor3("var(--font-family)", canvas) || "system-ui";
35487
35494
  const prevPos = /* @__PURE__ */ new Map();
35488
35495
  for (const pn of nodesRef.current) if (pn.x != null && pn.y != null) prevPos.set(pn.id, { x: pn.x, y: pn.y });
35489
35496
  const simNodes = propNodes.map((n, idx) => {
@@ -35518,133 +35525,192 @@ var init_GraphCanvas = __esm({
35518
35525
  const fullRelayout = !laidOutRef.current || overlap < 0.5;
35519
35526
  if (layout === "force") {
35520
35527
  const maxIterations = 300;
35521
- const tick = () => {
35528
+ const COOL = 0.12;
35529
+ const COLLIDE_PASSES = 6;
35530
+ const LABEL_GAP = 12;
35531
+ const LABEL_H = 16;
35532
+ const SIMILARITY_NEIGHBORS = 5;
35533
+ const SIMILARITY_MIN_WEIGHT = 0.25;
35534
+ const drawnPairs = /* @__PURE__ */ new Set();
35535
+ for (const edge of propEdges) drawnPairs.add(edgeKeyOf(edge.source, edge.target));
35536
+ const similarityNeighbors = /* @__PURE__ */ new Map();
35537
+ {
35538
+ const bySource = /* @__PURE__ */ new Map();
35539
+ for (const pair of propSimilarity) {
35540
+ if (pair.weight < SIMILARITY_MIN_WEIGHT) continue;
35541
+ if (drawnPairs.has(edgeKeyOf(pair.source, pair.target))) continue;
35542
+ const arr = bySource.get(pair.source) ?? [];
35543
+ arr.push(pair);
35544
+ bySource.set(pair.source, arr);
35545
+ }
35546
+ for (const [id, arr] of bySource) {
35547
+ arr.sort((a, b) => b.weight - a.weight);
35548
+ const keep = new Set(arr.slice(0, SIMILARITY_NEIGHBORS).map((p) => p.target));
35549
+ similarityNeighbors.set(id, keep);
35550
+ }
35551
+ }
35552
+ const activeSimilarity = propSimilarity.filter((pair) => {
35553
+ if (drawnPairs.has(edgeKeyOf(pair.source, pair.target))) return false;
35554
+ const a = similarityNeighbors.get(pair.source);
35555
+ const b = similarityNeighbors.get(pair.target);
35556
+ return (a?.has(pair.target) ?? false) || (b?.has(pair.source) ?? false);
35557
+ });
35558
+ const tick = (iter, options) => {
35559
+ const skipForces = options?.skipForces ?? false;
35522
35560
  const nodes = nodesRef.current;
35523
35561
  const centerX = w / 2;
35524
35562
  const centerY = h / 2;
35563
+ const temp = skipForces ? 0 : Math.max(COOL, 1 - iter / maxIterations);
35525
35564
  for (const node of nodes) {
35526
35565
  node.fx = 0;
35527
35566
  node.fy = 0;
35528
35567
  }
35529
- for (let i = 0; i < nodes.length; i++) {
35530
- for (let j = i + 1; j < nodes.length; j++) {
35531
- const dx = nodes[j].x - nodes[i].x;
35532
- const dy = nodes[j].y - nodes[i].y;
35533
- const dist = Math.sqrt(dx * dx + dy * dy) || 1;
35534
- const force = repulsion / (dist * dist);
35535
- const fx = dx / dist * force;
35536
- const fy = dy / dist * force;
35537
- nodes[i].fx -= fx;
35538
- nodes[i].fy -= fy;
35539
- nodes[j].fx += fx;
35540
- nodes[j].fy += fy;
35568
+ if (!skipForces) {
35569
+ for (let i = 0; i < nodes.length; i++) {
35570
+ for (let j = i + 1; j < nodes.length; j++) {
35571
+ const dx = nodes[j].x - nodes[i].x;
35572
+ const dy = nodes[j].y - nodes[i].y;
35573
+ const dist = Math.sqrt(dx * dx + dy * dy) || 1;
35574
+ const force = repulsion / (dist * dist) * temp;
35575
+ const fx = dx / dist * force;
35576
+ const fy = dy / dist * force;
35577
+ nodes[i].fx -= fx;
35578
+ nodes[i].fy -= fy;
35579
+ nodes[j].fx += fx;
35580
+ nodes[j].fy += fy;
35581
+ }
35541
35582
  }
35542
- }
35543
- const nodeById = new Map(nodes.map((n) => [n.id, n]));
35544
- if (propSimilarity.length > 0) {
35545
- for (const pair of propSimilarity) {
35546
- const source = nodeById.get(pair.source);
35547
- const target = nodeById.get(pair.target);
35548
- if (!source || !target) continue;
35549
- const dx = target.x - source.x;
35550
- const dy = target.y - source.y;
35583
+ const nodeById = new Map(nodes.map((n) => [n.id, n]));
35584
+ const spring = (a, b, rest, k) => {
35585
+ const dx = b.x - a.x;
35586
+ const dy = b.y - a.y;
35551
35587
  const dist = Math.sqrt(dx * dx + dy * dy) || 1;
35552
- const w2 = Math.min(1, Math.max(0, pair.weight));
35553
- const linkTarget = linkDistance * (1 - w2);
35554
- const force = (dist - linkTarget) * (0.04 + 0.1 * w2);
35588
+ const force = (dist - rest) * k * temp;
35555
35589
  const fx = dx / dist * force;
35556
35590
  const fy = dy / dist * force;
35557
- source.fx += fx;
35558
- source.fy += fy;
35559
- target.fx -= fx;
35560
- target.fy -= fy;
35561
- }
35562
- } else {
35591
+ a.fx += fx;
35592
+ a.fy += fy;
35593
+ b.fx -= fx;
35594
+ b.fy -= fy;
35595
+ };
35563
35596
  for (const edge of propEdges) {
35564
35597
  const source = nodeById.get(edge.source);
35565
35598
  const target = nodeById.get(edge.target);
35566
35599
  if (!source || !target) continue;
35567
- const dx = target.x - source.x;
35568
- const dy = target.y - source.y;
35569
- const dist = Math.sqrt(dx * dx + dy * dy) || 1;
35600
+ drawnPairs.add(edgeKeyOf(edge.source, edge.target));
35570
35601
  const w2 = Math.min(1, Math.max(0, edge.weight ?? 1));
35571
- const linkTarget = linkDistance * (0.5 + (1 - w2) * 1.5);
35572
- const force = (dist - linkTarget) * (0.04 + 0.1 * w2);
35573
- const fx = dx / dist * force;
35574
- const fy = dy / dist * force;
35575
- source.fx += fx;
35576
- source.fy += fy;
35577
- target.fx -= fx;
35578
- target.fy -= fy;
35602
+ spring(source, target, linkDistance * (0.35 + (1 - w2) * 0.3), 0.14);
35579
35603
  }
35580
- }
35581
- const centroids = /* @__PURE__ */ new Map();
35582
- for (const node of nodes) {
35583
- const g = node.group ?? "__none__";
35584
- let c = centroids.get(g);
35585
- if (!c) {
35586
- c = { x: 0, y: 0, n: 0 };
35587
- centroids.set(g, c);
35604
+ for (const pair of activeSimilarity) {
35605
+ const source = nodeById.get(pair.source);
35606
+ const target = nodeById.get(pair.target);
35607
+ if (!source || !target) continue;
35608
+ const w2 = Math.min(1, Math.max(0, pair.weight));
35609
+ spring(source, target, linkDistance * (1.35 - 0.55 * w2), 0.015);
35588
35610
  }
35589
- c.x += node.x;
35590
- c.y += node.y;
35591
- c.n += 1;
35592
- }
35593
- for (const node of nodes) {
35594
- const c = centroids.get(node.group ?? "__none__");
35595
- if (c && c.n > 1) {
35596
- node.fx += (c.x / c.n - node.x) * 0.04;
35597
- node.fy += (c.y / c.n - node.y) * 0.04;
35598
- } else {
35599
- node.fx += (centerX - node.x) * 0.01;
35600
- node.fy += (centerY - node.y) * 0.01;
35611
+ const centroids = /* @__PURE__ */ new Map();
35612
+ for (const node of nodes) {
35613
+ const g = node.group ?? "__none__";
35614
+ let c = centroids.get(g);
35615
+ if (!c) {
35616
+ c = { x: 0, y: 0, n: 0 };
35617
+ centroids.set(g, c);
35618
+ }
35619
+ c.x += node.x;
35620
+ c.y += node.y;
35621
+ c.n += 1;
35622
+ }
35623
+ const multiCluster = centroids.size > 1;
35624
+ for (const node of nodes) {
35625
+ if (multiCluster) {
35626
+ const c = centroids.get(node.group ?? "__none__");
35627
+ if (c && c.n > 1) {
35628
+ node.fx += (c.x / c.n - node.x) * 0.06 * temp;
35629
+ node.fy += (c.y / c.n - node.y) * 0.06 * temp;
35630
+ } else {
35631
+ node.fx += (centerX - node.x) * 0.01 * temp;
35632
+ node.fy += (centerY - node.y) * 0.01 * temp;
35633
+ }
35634
+ } else {
35635
+ node.fx += (centerX - node.x) * 8e-3 * temp;
35636
+ node.fy += (centerY - node.y) * 8e-3 * temp;
35637
+ }
35638
+ }
35639
+ const damping = 0.9;
35640
+ for (const node of nodes) {
35641
+ node.vx = (node.vx + node.fx) * damping;
35642
+ node.vy = (node.vy + node.fy) * damping;
35643
+ node.x += node.vx;
35644
+ node.y += node.vy;
35601
35645
  }
35602
35646
  }
35603
- const damping = 0.9;
35604
35647
  for (const node of nodes) {
35605
- node.vx = (node.vx + node.fx) * damping;
35606
- node.vy = (node.vy + node.fy) * damping;
35607
- node.x += node.vx;
35608
- node.y += node.vy;
35609
35648
  node.x = Math.max(30, Math.min(w - 30, node.x));
35610
35649
  node.y = Math.max(30, Math.min(h - 30, node.y));
35611
35650
  }
35612
- const LABEL_GAP = 12;
35613
- const LABEL_H = 16;
35614
35651
  const pad = nodeSpacing / 2;
35615
- for (let i = 0; i < nodes.length; i++) {
35616
- for (let j = i + 1; j < nodes.length; j++) {
35617
- const a = nodes[i];
35618
- const b = nodes[j];
35619
- const ar = a.size || 8;
35620
- const br = b.size || 8;
35621
- if (showLabels) {
35622
- if (a.labelW === void 0) a.labelW = a.label ? measureLabelWidth(a.label) : 0;
35623
- if (b.labelW === void 0) b.labelW = b.label ? measureLabelWidth(b.label) : 0;
35624
- }
35625
- const labelBelow = showLabels ? LABEL_GAP + LABEL_H : 0;
35626
- const aHalfW = Math.max(ar, (a.labelW ?? 0) / 2) + pad;
35627
- const bHalfW = Math.max(br, (b.labelW ?? 0) / 2) + pad;
35628
- const aTop = a.y - ar - pad, aBot = a.y + ar + labelBelow + pad;
35629
- const bTop = b.y - br - pad, bBot = b.y + br + labelBelow + pad;
35630
- const overlapX = Math.min(a.x + aHalfW, b.x + bHalfW) - Math.max(a.x - aHalfW, b.x - bHalfW);
35631
- const overlapY = Math.min(aBot, bBot) - Math.max(aTop, bTop);
35632
- if (overlapX > 0 && overlapY > 0) {
35633
- if (overlapX <= overlapY) {
35634
- const push = overlapX / 2 * (b.x >= a.x ? 1 : -1);
35635
- a.x = Math.max(30, Math.min(w - 30, a.x - push));
35636
- b.x = Math.max(30, Math.min(w - 30, b.x + push));
35637
- } else {
35638
- const push = overlapY / 2 * (bTop + bBot >= aTop + aBot ? 1 : -1);
35639
- a.y = Math.max(30, Math.min(h - 30, a.y - push));
35640
- b.y = Math.max(30, Math.min(h - 30, b.y + push));
35652
+ const rectsOf = (n) => {
35653
+ const r = n.size || 8;
35654
+ const lw = showLabels ? n.labelW ?? (n.labelW = n.label ? measureLabelWidth(n.label, labelFont) : 0) : 0;
35655
+ return {
35656
+ circle: [n.x - r - pad, n.y - r - pad, n.x + r + pad, n.y + r + pad],
35657
+ label: [n.x - lw / 2 - pad, n.y + r + LABEL_GAP - 8, n.x + lw / 2 + pad, n.y + r + LABEL_GAP + LABEL_H]
35658
+ };
35659
+ };
35660
+ const sep = (r1, r2) => {
35661
+ const ox = Math.min(r1[2], r2[2]) - Math.max(r1[0], r2[0]);
35662
+ const oy = Math.min(r1[3], r2[3]) - Math.max(r1[1], r2[1]);
35663
+ if (ox <= 0 || oy <= 0) return null;
35664
+ 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 };
35665
+ };
35666
+ for (let pass = 0; pass < COLLIDE_PASSES; pass++) {
35667
+ for (let i = 0; i < nodes.length; i++) {
35668
+ for (let j = i + 1; j < nodes.length; j++) {
35669
+ const a = nodes[i];
35670
+ const b = nodes[j];
35671
+ const ra = rectsOf(a);
35672
+ const rb = rectsOf(b);
35673
+ let best = null;
35674
+ for (const [r1, r2] of [[ra.circle, rb.circle], [ra.circle, rb.label], [ra.label, rb.circle], [ra.label, rb.label]]) {
35675
+ const s = sep(r1, r2);
35676
+ if (s && (!best || s.depth < best.depth)) best = s;
35677
+ }
35678
+ if (best) {
35679
+ const push = best.depth / 2;
35680
+ if (best.axis === "x") {
35681
+ a.x = Math.max(30, Math.min(w - 30, a.x - push * best.sign));
35682
+ b.x = Math.max(30, Math.min(w - 30, b.x + push * best.sign));
35683
+ a.vx = 0;
35684
+ b.vx = 0;
35685
+ } else {
35686
+ a.y = Math.max(30, Math.min(h - 30, a.y - push * best.sign));
35687
+ b.y = Math.max(30, Math.min(h - 30, b.y + push * best.sign));
35688
+ a.vy = 0;
35689
+ b.vy = 0;
35690
+ }
35641
35691
  }
35642
35692
  }
35643
35693
  }
35644
35694
  }
35645
35695
  };
35646
35696
  if (fullRelayout) {
35647
- for (let i = 0; i < maxIterations; i++) tick();
35697
+ for (let i = 0; i < maxIterations; i++) tick(i);
35698
+ for (let i = 0; i < 4; i++) tick(maxIterations, { skipForces: true });
35699
+ const fitPad = 40;
35700
+ let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
35701
+ for (const node of nodesRef.current) {
35702
+ const r = node.size || 8;
35703
+ const lw = showLabels ? node.labelW ?? (node.labelW = node.label ? measureLabelWidth(node.label, labelFont) : 0) : 0;
35704
+ minX = Math.min(minX, node.x - r, node.x - lw / 2);
35705
+ minY = Math.min(minY, node.y - r);
35706
+ maxX = Math.max(maxX, node.x + r, node.x + lw / 2);
35707
+ maxY = Math.max(maxY, node.y + r + LABEL_GAP + LABEL_H);
35708
+ }
35709
+ const bboxW = Math.max(1, maxX - minX + fitPad * 2);
35710
+ const bboxH = Math.max(1, maxY - minY + fitPad * 2);
35711
+ const nextZoom = Math.min(1, viewW / bboxW, viewH / bboxH);
35712
+ setZoom(nextZoom);
35713
+ setOffset({ x: fitPad - minX * nextZoom, y: fitPad - minY * nextZoom });
35648
35714
  laidOutRef.current = true;
35649
35715
  } else {
35650
35716
  const centroids = /* @__PURE__ */ new Map();