@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.
@@ -36581,11 +36581,11 @@ var init_DocumentViewer = __esm({
36581
36581
  DocumentViewer.displayName = "DocumentViewer";
36582
36582
  }
36583
36583
  });
36584
- function measureLabelWidth(text) {
36584
+ function measureLabelWidth(text, fontFamily = "system-ui") {
36585
36585
  if (typeof document === "undefined") return text.length * 6;
36586
36586
  if (!labelMeasureCtx) labelMeasureCtx = document.createElement("canvas").getContext("2d");
36587
36587
  if (!labelMeasureCtx) return text.length * 6;
36588
- labelMeasureCtx.font = "12px system-ui";
36588
+ labelMeasureCtx.font = `12px ${fontFamily}`;
36589
36589
  return labelMeasureCtx.measureText(text).width + 4;
36590
36590
  }
36591
36591
  function getGroupColor(group, groups) {
@@ -36610,6 +36610,9 @@ function mulberry32(a) {
36610
36610
  return ((t ^ t >>> 14) >>> 0) / 4294967296;
36611
36611
  };
36612
36612
  }
36613
+ function edgeKeyOf(a, b) {
36614
+ return a < b ? `${a}\0${b}` : `${b}\0${a}`;
36615
+ }
36613
36616
  function resolveColor3(color, el) {
36614
36617
  const m = /^var\((--[^,)]+)(?:,\s*([^)]+))?\)$/.exec(color.trim());
36615
36618
  if (!m) return color;
@@ -36741,8 +36744,12 @@ var init_GraphCanvas = __esm({
36741
36744
  useEffect(() => {
36742
36745
  const canvas = canvasRef.current;
36743
36746
  if (!canvas || propNodes.length === 0) return;
36744
- const w = logicalW;
36745
- const h = height;
36747
+ const viewW = logicalW;
36748
+ const viewH = height;
36749
+ const layoutScale = Math.max(1, Math.min(3, Math.sqrt(propNodes.length / 12)));
36750
+ const w = viewW * layoutScale;
36751
+ const h = viewH * layoutScale;
36752
+ const labelFont = resolveColor3("var(--font-family)", canvas) || "system-ui";
36746
36753
  const prevPos = /* @__PURE__ */ new Map();
36747
36754
  for (const pn of nodesRef.current) if (pn.x != null && pn.y != null) prevPos.set(pn.id, { x: pn.x, y: pn.y });
36748
36755
  const simNodes = propNodes.map((n, idx) => {
@@ -36777,133 +36784,192 @@ var init_GraphCanvas = __esm({
36777
36784
  const fullRelayout = !laidOutRef.current || overlap < 0.5;
36778
36785
  if (layout === "force") {
36779
36786
  const maxIterations = 300;
36780
- const tick = () => {
36787
+ const COOL = 0.12;
36788
+ const COLLIDE_PASSES = 6;
36789
+ const LABEL_GAP = 12;
36790
+ const LABEL_H = 16;
36791
+ const SIMILARITY_NEIGHBORS = 5;
36792
+ const SIMILARITY_MIN_WEIGHT = 0.25;
36793
+ const drawnPairs = /* @__PURE__ */ new Set();
36794
+ for (const edge of propEdges) drawnPairs.add(edgeKeyOf(edge.source, edge.target));
36795
+ const similarityNeighbors = /* @__PURE__ */ new Map();
36796
+ {
36797
+ const bySource = /* @__PURE__ */ new Map();
36798
+ for (const pair of propSimilarity) {
36799
+ if (pair.weight < SIMILARITY_MIN_WEIGHT) continue;
36800
+ if (drawnPairs.has(edgeKeyOf(pair.source, pair.target))) continue;
36801
+ const arr = bySource.get(pair.source) ?? [];
36802
+ arr.push(pair);
36803
+ bySource.set(pair.source, arr);
36804
+ }
36805
+ for (const [id, arr] of bySource) {
36806
+ arr.sort((a, b) => b.weight - a.weight);
36807
+ const keep = new Set(arr.slice(0, SIMILARITY_NEIGHBORS).map((p) => p.target));
36808
+ similarityNeighbors.set(id, keep);
36809
+ }
36810
+ }
36811
+ const activeSimilarity = propSimilarity.filter((pair) => {
36812
+ if (drawnPairs.has(edgeKeyOf(pair.source, pair.target))) return false;
36813
+ const a = similarityNeighbors.get(pair.source);
36814
+ const b = similarityNeighbors.get(pair.target);
36815
+ return (a?.has(pair.target) ?? false) || (b?.has(pair.source) ?? false);
36816
+ });
36817
+ const tick = (iter, options) => {
36818
+ const skipForces = options?.skipForces ?? false;
36781
36819
  const nodes = nodesRef.current;
36782
36820
  const centerX = w / 2;
36783
36821
  const centerY = h / 2;
36822
+ const temp = skipForces ? 0 : Math.max(COOL, 1 - iter / maxIterations);
36784
36823
  for (const node of nodes) {
36785
36824
  node.fx = 0;
36786
36825
  node.fy = 0;
36787
36826
  }
36788
- for (let i = 0; i < nodes.length; i++) {
36789
- for (let j = i + 1; j < nodes.length; j++) {
36790
- const dx = nodes[j].x - nodes[i].x;
36791
- const dy = nodes[j].y - nodes[i].y;
36792
- const dist = Math.sqrt(dx * dx + dy * dy) || 1;
36793
- const force = repulsion / (dist * dist);
36794
- const fx = dx / dist * force;
36795
- const fy = dy / dist * force;
36796
- nodes[i].fx -= fx;
36797
- nodes[i].fy -= fy;
36798
- nodes[j].fx += fx;
36799
- nodes[j].fy += fy;
36827
+ if (!skipForces) {
36828
+ for (let i = 0; i < nodes.length; i++) {
36829
+ for (let j = i + 1; j < nodes.length; j++) {
36830
+ const dx = nodes[j].x - nodes[i].x;
36831
+ const dy = nodes[j].y - nodes[i].y;
36832
+ const dist = Math.sqrt(dx * dx + dy * dy) || 1;
36833
+ const force = repulsion / (dist * dist) * temp;
36834
+ const fx = dx / dist * force;
36835
+ const fy = dy / dist * force;
36836
+ nodes[i].fx -= fx;
36837
+ nodes[i].fy -= fy;
36838
+ nodes[j].fx += fx;
36839
+ nodes[j].fy += fy;
36840
+ }
36800
36841
  }
36801
- }
36802
- const nodeById = new Map(nodes.map((n) => [n.id, n]));
36803
- if (propSimilarity.length > 0) {
36804
- for (const pair of propSimilarity) {
36805
- const source = nodeById.get(pair.source);
36806
- const target = nodeById.get(pair.target);
36807
- if (!source || !target) continue;
36808
- const dx = target.x - source.x;
36809
- const dy = target.y - source.y;
36842
+ const nodeById = new Map(nodes.map((n) => [n.id, n]));
36843
+ const spring = (a, b, rest, k) => {
36844
+ const dx = b.x - a.x;
36845
+ const dy = b.y - a.y;
36810
36846
  const dist = Math.sqrt(dx * dx + dy * dy) || 1;
36811
- const w2 = Math.min(1, Math.max(0, pair.weight));
36812
- const linkTarget = linkDistance * (1 - w2);
36813
- const force = (dist - linkTarget) * (0.04 + 0.1 * w2);
36847
+ const force = (dist - rest) * k * temp;
36814
36848
  const fx = dx / dist * force;
36815
36849
  const fy = dy / dist * force;
36816
- source.fx += fx;
36817
- source.fy += fy;
36818
- target.fx -= fx;
36819
- target.fy -= fy;
36820
- }
36821
- } else {
36850
+ a.fx += fx;
36851
+ a.fy += fy;
36852
+ b.fx -= fx;
36853
+ b.fy -= fy;
36854
+ };
36822
36855
  for (const edge of propEdges) {
36823
36856
  const source = nodeById.get(edge.source);
36824
36857
  const target = nodeById.get(edge.target);
36825
36858
  if (!source || !target) continue;
36826
- const dx = target.x - source.x;
36827
- const dy = target.y - source.y;
36828
- const dist = Math.sqrt(dx * dx + dy * dy) || 1;
36859
+ drawnPairs.add(edgeKeyOf(edge.source, edge.target));
36829
36860
  const w2 = Math.min(1, Math.max(0, edge.weight ?? 1));
36830
- const linkTarget = linkDistance * (0.5 + (1 - w2) * 1.5);
36831
- const force = (dist - linkTarget) * (0.04 + 0.1 * w2);
36832
- const fx = dx / dist * force;
36833
- const fy = dy / dist * force;
36834
- source.fx += fx;
36835
- source.fy += fy;
36836
- target.fx -= fx;
36837
- target.fy -= fy;
36861
+ spring(source, target, linkDistance * (0.35 + (1 - w2) * 0.3), 0.14);
36838
36862
  }
36839
- }
36840
- const centroids = /* @__PURE__ */ new Map();
36841
- for (const node of nodes) {
36842
- const g = node.group ?? "__none__";
36843
- let c = centroids.get(g);
36844
- if (!c) {
36845
- c = { x: 0, y: 0, n: 0 };
36846
- centroids.set(g, c);
36863
+ for (const pair of activeSimilarity) {
36864
+ const source = nodeById.get(pair.source);
36865
+ const target = nodeById.get(pair.target);
36866
+ if (!source || !target) continue;
36867
+ const w2 = Math.min(1, Math.max(0, pair.weight));
36868
+ spring(source, target, linkDistance * (1.35 - 0.55 * w2), 0.015);
36847
36869
  }
36848
- c.x += node.x;
36849
- c.y += node.y;
36850
- c.n += 1;
36851
- }
36852
- for (const node of nodes) {
36853
- const c = centroids.get(node.group ?? "__none__");
36854
- if (c && c.n > 1) {
36855
- node.fx += (c.x / c.n - node.x) * 0.04;
36856
- node.fy += (c.y / c.n - node.y) * 0.04;
36857
- } else {
36858
- node.fx += (centerX - node.x) * 0.01;
36859
- node.fy += (centerY - node.y) * 0.01;
36870
+ const centroids = /* @__PURE__ */ new Map();
36871
+ for (const node of nodes) {
36872
+ const g = node.group ?? "__none__";
36873
+ let c = centroids.get(g);
36874
+ if (!c) {
36875
+ c = { x: 0, y: 0, n: 0 };
36876
+ centroids.set(g, c);
36877
+ }
36878
+ c.x += node.x;
36879
+ c.y += node.y;
36880
+ c.n += 1;
36881
+ }
36882
+ const multiCluster = centroids.size > 1;
36883
+ for (const node of nodes) {
36884
+ if (multiCluster) {
36885
+ const c = centroids.get(node.group ?? "__none__");
36886
+ if (c && c.n > 1) {
36887
+ node.fx += (c.x / c.n - node.x) * 0.06 * temp;
36888
+ node.fy += (c.y / c.n - node.y) * 0.06 * temp;
36889
+ } else {
36890
+ node.fx += (centerX - node.x) * 0.01 * temp;
36891
+ node.fy += (centerY - node.y) * 0.01 * temp;
36892
+ }
36893
+ } else {
36894
+ node.fx += (centerX - node.x) * 8e-3 * temp;
36895
+ node.fy += (centerY - node.y) * 8e-3 * temp;
36896
+ }
36897
+ }
36898
+ const damping = 0.9;
36899
+ for (const node of nodes) {
36900
+ node.vx = (node.vx + node.fx) * damping;
36901
+ node.vy = (node.vy + node.fy) * damping;
36902
+ node.x += node.vx;
36903
+ node.y += node.vy;
36860
36904
  }
36861
36905
  }
36862
- const damping = 0.9;
36863
36906
  for (const node of nodes) {
36864
- node.vx = (node.vx + node.fx) * damping;
36865
- node.vy = (node.vy + node.fy) * damping;
36866
- node.x += node.vx;
36867
- node.y += node.vy;
36868
36907
  node.x = Math.max(30, Math.min(w - 30, node.x));
36869
36908
  node.y = Math.max(30, Math.min(h - 30, node.y));
36870
36909
  }
36871
- const LABEL_GAP = 12;
36872
- const LABEL_H = 16;
36873
36910
  const pad = nodeSpacing / 2;
36874
- for (let i = 0; i < nodes.length; i++) {
36875
- for (let j = i + 1; j < nodes.length; j++) {
36876
- const a = nodes[i];
36877
- const b = nodes[j];
36878
- const ar = a.size || 8;
36879
- const br = b.size || 8;
36880
- if (showLabels) {
36881
- if (a.labelW === void 0) a.labelW = a.label ? measureLabelWidth(a.label) : 0;
36882
- if (b.labelW === void 0) b.labelW = b.label ? measureLabelWidth(b.label) : 0;
36883
- }
36884
- const labelBelow = showLabels ? LABEL_GAP + LABEL_H : 0;
36885
- const aHalfW = Math.max(ar, (a.labelW ?? 0) / 2) + pad;
36886
- const bHalfW = Math.max(br, (b.labelW ?? 0) / 2) + pad;
36887
- const aTop = a.y - ar - pad, aBot = a.y + ar + labelBelow + pad;
36888
- const bTop = b.y - br - pad, bBot = b.y + br + labelBelow + pad;
36889
- const overlapX = Math.min(a.x + aHalfW, b.x + bHalfW) - Math.max(a.x - aHalfW, b.x - bHalfW);
36890
- const overlapY = Math.min(aBot, bBot) - Math.max(aTop, bTop);
36891
- if (overlapX > 0 && overlapY > 0) {
36892
- if (overlapX <= overlapY) {
36893
- const push = overlapX / 2 * (b.x >= a.x ? 1 : -1);
36894
- a.x = Math.max(30, Math.min(w - 30, a.x - push));
36895
- b.x = Math.max(30, Math.min(w - 30, b.x + push));
36896
- } else {
36897
- const push = overlapY / 2 * (bTop + bBot >= aTop + aBot ? 1 : -1);
36898
- a.y = Math.max(30, Math.min(h - 30, a.y - push));
36899
- b.y = Math.max(30, Math.min(h - 30, b.y + push));
36911
+ const rectsOf = (n) => {
36912
+ const r = n.size || 8;
36913
+ const lw = showLabels ? n.labelW ?? (n.labelW = n.label ? measureLabelWidth(n.label, labelFont) : 0) : 0;
36914
+ return {
36915
+ circle: [n.x - r - pad, n.y - r - pad, n.x + r + pad, n.y + r + pad],
36916
+ label: [n.x - lw / 2 - pad, n.y + r + LABEL_GAP - 8, n.x + lw / 2 + pad, n.y + r + LABEL_GAP + LABEL_H]
36917
+ };
36918
+ };
36919
+ const sep = (r1, r2) => {
36920
+ const ox = Math.min(r1[2], r2[2]) - Math.max(r1[0], r2[0]);
36921
+ const oy = Math.min(r1[3], r2[3]) - Math.max(r1[1], r2[1]);
36922
+ if (ox <= 0 || oy <= 0) return null;
36923
+ 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 };
36924
+ };
36925
+ for (let pass = 0; pass < COLLIDE_PASSES; pass++) {
36926
+ for (let i = 0; i < nodes.length; i++) {
36927
+ for (let j = i + 1; j < nodes.length; j++) {
36928
+ const a = nodes[i];
36929
+ const b = nodes[j];
36930
+ const ra = rectsOf(a);
36931
+ const rb = rectsOf(b);
36932
+ let best = null;
36933
+ for (const [r1, r2] of [[ra.circle, rb.circle], [ra.circle, rb.label], [ra.label, rb.circle], [ra.label, rb.label]]) {
36934
+ const s = sep(r1, r2);
36935
+ if (s && (!best || s.depth < best.depth)) best = s;
36936
+ }
36937
+ if (best) {
36938
+ const push = best.depth / 2;
36939
+ if (best.axis === "x") {
36940
+ a.x = Math.max(30, Math.min(w - 30, a.x - push * best.sign));
36941
+ b.x = Math.max(30, Math.min(w - 30, b.x + push * best.sign));
36942
+ a.vx = 0;
36943
+ b.vx = 0;
36944
+ } else {
36945
+ a.y = Math.max(30, Math.min(h - 30, a.y - push * best.sign));
36946
+ b.y = Math.max(30, Math.min(h - 30, b.y + push * best.sign));
36947
+ a.vy = 0;
36948
+ b.vy = 0;
36949
+ }
36900
36950
  }
36901
36951
  }
36902
36952
  }
36903
36953
  }
36904
36954
  };
36905
36955
  if (fullRelayout) {
36906
- for (let i = 0; i < maxIterations; i++) tick();
36956
+ for (let i = 0; i < maxIterations; i++) tick(i);
36957
+ for (let i = 0; i < 4; i++) tick(maxIterations, { skipForces: true });
36958
+ const fitPad = 40;
36959
+ let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
36960
+ for (const node of nodesRef.current) {
36961
+ const r = node.size || 8;
36962
+ const lw = showLabels ? node.labelW ?? (node.labelW = node.label ? measureLabelWidth(node.label, labelFont) : 0) : 0;
36963
+ minX = Math.min(minX, node.x - r, node.x - lw / 2);
36964
+ minY = Math.min(minY, node.y - r);
36965
+ maxX = Math.max(maxX, node.x + r, node.x + lw / 2);
36966
+ maxY = Math.max(maxY, node.y + r + LABEL_GAP + LABEL_H);
36967
+ }
36968
+ const bboxW = Math.max(1, maxX - minX + fitPad * 2);
36969
+ const bboxH = Math.max(1, maxY - minY + fitPad * 2);
36970
+ const nextZoom = Math.min(1, viewW / bboxW, viewH / bboxH);
36971
+ setZoom(nextZoom);
36972
+ setOffset({ x: fitPad - minX * nextZoom, y: fitPad - minY * nextZoom });
36907
36973
  laidOutRef.current = true;
36908
36974
  } else {
36909
36975
  const centroids = /* @__PURE__ */ new Map();