@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.
@@ -34732,11 +34732,11 @@ var init_DocumentViewer = __esm({
34732
34732
  DocumentViewer.displayName = "DocumentViewer";
34733
34733
  }
34734
34734
  });
34735
- function measureLabelWidth(text) {
34735
+ function measureLabelWidth(text, fontFamily = "system-ui") {
34736
34736
  if (typeof document === "undefined") return text.length * 6;
34737
34737
  if (!labelMeasureCtx) labelMeasureCtx = document.createElement("canvas").getContext("2d");
34738
34738
  if (!labelMeasureCtx) return text.length * 6;
34739
- labelMeasureCtx.font = "12px system-ui";
34739
+ labelMeasureCtx.font = `12px ${fontFamily}`;
34740
34740
  return labelMeasureCtx.measureText(text).width + 4;
34741
34741
  }
34742
34742
  function getGroupColor(group, groups) {
@@ -34761,6 +34761,9 @@ function mulberry32(a) {
34761
34761
  return ((t ^ t >>> 14) >>> 0) / 4294967296;
34762
34762
  };
34763
34763
  }
34764
+ function edgeKeyOf(a, b) {
34765
+ return a < b ? `${a}\0${b}` : `${b}\0${a}`;
34766
+ }
34764
34767
  function resolveColor3(color, el) {
34765
34768
  const m = /^var\((--[^,)]+)(?:,\s*([^)]+))?\)$/.exec(color.trim());
34766
34769
  if (!m) return color;
@@ -34892,8 +34895,12 @@ var init_GraphCanvas = __esm({
34892
34895
  React81.useEffect(() => {
34893
34896
  const canvas = canvasRef.current;
34894
34897
  if (!canvas || propNodes.length === 0) return;
34895
- const w = logicalW;
34896
- const h = height;
34898
+ const viewW = logicalW;
34899
+ const viewH = height;
34900
+ const layoutScale = Math.max(1, Math.min(3, Math.sqrt(propNodes.length / 12)));
34901
+ const w = viewW * layoutScale;
34902
+ const h = viewH * layoutScale;
34903
+ const labelFont = resolveColor3("var(--font-family)", canvas) || "system-ui";
34897
34904
  const prevPos = /* @__PURE__ */ new Map();
34898
34905
  for (const pn of nodesRef.current) if (pn.x != null && pn.y != null) prevPos.set(pn.id, { x: pn.x, y: pn.y });
34899
34906
  const simNodes = propNodes.map((n, idx) => {
@@ -34928,133 +34935,192 @@ var init_GraphCanvas = __esm({
34928
34935
  const fullRelayout = !laidOutRef.current || overlap < 0.5;
34929
34936
  if (layout === "force") {
34930
34937
  const maxIterations = 300;
34931
- const tick = () => {
34938
+ const COOL = 0.12;
34939
+ const COLLIDE_PASSES = 6;
34940
+ const LABEL_GAP = 12;
34941
+ const LABEL_H = 16;
34942
+ const SIMILARITY_NEIGHBORS = 5;
34943
+ const SIMILARITY_MIN_WEIGHT = 0.25;
34944
+ const drawnPairs = /* @__PURE__ */ new Set();
34945
+ for (const edge of propEdges) drawnPairs.add(edgeKeyOf(edge.source, edge.target));
34946
+ const similarityNeighbors = /* @__PURE__ */ new Map();
34947
+ {
34948
+ const bySource = /* @__PURE__ */ new Map();
34949
+ for (const pair of propSimilarity) {
34950
+ if (pair.weight < SIMILARITY_MIN_WEIGHT) continue;
34951
+ if (drawnPairs.has(edgeKeyOf(pair.source, pair.target))) continue;
34952
+ const arr = bySource.get(pair.source) ?? [];
34953
+ arr.push(pair);
34954
+ bySource.set(pair.source, arr);
34955
+ }
34956
+ for (const [id, arr] of bySource) {
34957
+ arr.sort((a, b) => b.weight - a.weight);
34958
+ const keep = new Set(arr.slice(0, SIMILARITY_NEIGHBORS).map((p) => p.target));
34959
+ similarityNeighbors.set(id, keep);
34960
+ }
34961
+ }
34962
+ const activeSimilarity = propSimilarity.filter((pair) => {
34963
+ if (drawnPairs.has(edgeKeyOf(pair.source, pair.target))) return false;
34964
+ const a = similarityNeighbors.get(pair.source);
34965
+ const b = similarityNeighbors.get(pair.target);
34966
+ return (a?.has(pair.target) ?? false) || (b?.has(pair.source) ?? false);
34967
+ });
34968
+ const tick = (iter, options) => {
34969
+ const skipForces = options?.skipForces ?? false;
34932
34970
  const nodes = nodesRef.current;
34933
34971
  const centerX = w / 2;
34934
34972
  const centerY = h / 2;
34973
+ const temp = skipForces ? 0 : Math.max(COOL, 1 - iter / maxIterations);
34935
34974
  for (const node of nodes) {
34936
34975
  node.fx = 0;
34937
34976
  node.fy = 0;
34938
34977
  }
34939
- for (let i = 0; i < nodes.length; i++) {
34940
- for (let j = i + 1; j < nodes.length; j++) {
34941
- const dx = nodes[j].x - nodes[i].x;
34942
- const dy = nodes[j].y - nodes[i].y;
34943
- const dist = Math.sqrt(dx * dx + dy * dy) || 1;
34944
- const force = repulsion / (dist * dist);
34945
- const fx = dx / dist * force;
34946
- const fy = dy / dist * force;
34947
- nodes[i].fx -= fx;
34948
- nodes[i].fy -= fy;
34949
- nodes[j].fx += fx;
34950
- nodes[j].fy += fy;
34978
+ if (!skipForces) {
34979
+ for (let i = 0; i < nodes.length; i++) {
34980
+ for (let j = i + 1; j < nodes.length; j++) {
34981
+ const dx = nodes[j].x - nodes[i].x;
34982
+ const dy = nodes[j].y - nodes[i].y;
34983
+ const dist = Math.sqrt(dx * dx + dy * dy) || 1;
34984
+ const force = repulsion / (dist * dist) * temp;
34985
+ const fx = dx / dist * force;
34986
+ const fy = dy / dist * force;
34987
+ nodes[i].fx -= fx;
34988
+ nodes[i].fy -= fy;
34989
+ nodes[j].fx += fx;
34990
+ nodes[j].fy += fy;
34991
+ }
34951
34992
  }
34952
- }
34953
- const nodeById = new Map(nodes.map((n) => [n.id, n]));
34954
- if (propSimilarity.length > 0) {
34955
- for (const pair of propSimilarity) {
34956
- const source = nodeById.get(pair.source);
34957
- const target = nodeById.get(pair.target);
34958
- if (!source || !target) continue;
34959
- const dx = target.x - source.x;
34960
- const dy = target.y - source.y;
34993
+ const nodeById = new Map(nodes.map((n) => [n.id, n]));
34994
+ const spring = (a, b, rest, k) => {
34995
+ const dx = b.x - a.x;
34996
+ const dy = b.y - a.y;
34961
34997
  const dist = Math.sqrt(dx * dx + dy * dy) || 1;
34962
- const w2 = Math.min(1, Math.max(0, pair.weight));
34963
- const linkTarget = linkDistance * (1 - w2);
34964
- const force = (dist - linkTarget) * (0.04 + 0.1 * w2);
34998
+ const force = (dist - rest) * k * temp;
34965
34999
  const fx = dx / dist * force;
34966
35000
  const fy = dy / dist * force;
34967
- source.fx += fx;
34968
- source.fy += fy;
34969
- target.fx -= fx;
34970
- target.fy -= fy;
34971
- }
34972
- } else {
35001
+ a.fx += fx;
35002
+ a.fy += fy;
35003
+ b.fx -= fx;
35004
+ b.fy -= fy;
35005
+ };
34973
35006
  for (const edge of propEdges) {
34974
35007
  const source = nodeById.get(edge.source);
34975
35008
  const target = nodeById.get(edge.target);
34976
35009
  if (!source || !target) continue;
34977
- const dx = target.x - source.x;
34978
- const dy = target.y - source.y;
34979
- const dist = Math.sqrt(dx * dx + dy * dy) || 1;
35010
+ drawnPairs.add(edgeKeyOf(edge.source, edge.target));
34980
35011
  const w2 = Math.min(1, Math.max(0, edge.weight ?? 1));
34981
- const linkTarget = linkDistance * (0.5 + (1 - w2) * 1.5);
34982
- const force = (dist - linkTarget) * (0.04 + 0.1 * w2);
34983
- const fx = dx / dist * force;
34984
- const fy = dy / dist * force;
34985
- source.fx += fx;
34986
- source.fy += fy;
34987
- target.fx -= fx;
34988
- target.fy -= fy;
35012
+ spring(source, target, linkDistance * (0.35 + (1 - w2) * 0.3), 0.14);
34989
35013
  }
34990
- }
34991
- const centroids = /* @__PURE__ */ new Map();
34992
- for (const node of nodes) {
34993
- const g = node.group ?? "__none__";
34994
- let c = centroids.get(g);
34995
- if (!c) {
34996
- c = { x: 0, y: 0, n: 0 };
34997
- centroids.set(g, c);
35014
+ for (const pair of activeSimilarity) {
35015
+ const source = nodeById.get(pair.source);
35016
+ const target = nodeById.get(pair.target);
35017
+ if (!source || !target) continue;
35018
+ const w2 = Math.min(1, Math.max(0, pair.weight));
35019
+ spring(source, target, linkDistance * (1.35 - 0.55 * w2), 0.015);
34998
35020
  }
34999
- c.x += node.x;
35000
- c.y += node.y;
35001
- c.n += 1;
35002
- }
35003
- for (const node of nodes) {
35004
- const c = centroids.get(node.group ?? "__none__");
35005
- if (c && c.n > 1) {
35006
- node.fx += (c.x / c.n - node.x) * 0.04;
35007
- node.fy += (c.y / c.n - node.y) * 0.04;
35008
- } else {
35009
- node.fx += (centerX - node.x) * 0.01;
35010
- node.fy += (centerY - node.y) * 0.01;
35021
+ const centroids = /* @__PURE__ */ new Map();
35022
+ for (const node of nodes) {
35023
+ const g = node.group ?? "__none__";
35024
+ let c = centroids.get(g);
35025
+ if (!c) {
35026
+ c = { x: 0, y: 0, n: 0 };
35027
+ centroids.set(g, c);
35028
+ }
35029
+ c.x += node.x;
35030
+ c.y += node.y;
35031
+ c.n += 1;
35032
+ }
35033
+ const multiCluster = centroids.size > 1;
35034
+ for (const node of nodes) {
35035
+ if (multiCluster) {
35036
+ const c = centroids.get(node.group ?? "__none__");
35037
+ if (c && c.n > 1) {
35038
+ node.fx += (c.x / c.n - node.x) * 0.06 * temp;
35039
+ node.fy += (c.y / c.n - node.y) * 0.06 * temp;
35040
+ } else {
35041
+ node.fx += (centerX - node.x) * 0.01 * temp;
35042
+ node.fy += (centerY - node.y) * 0.01 * temp;
35043
+ }
35044
+ } else {
35045
+ node.fx += (centerX - node.x) * 8e-3 * temp;
35046
+ node.fy += (centerY - node.y) * 8e-3 * temp;
35047
+ }
35048
+ }
35049
+ const damping = 0.9;
35050
+ for (const node of nodes) {
35051
+ node.vx = (node.vx + node.fx) * damping;
35052
+ node.vy = (node.vy + node.fy) * damping;
35053
+ node.x += node.vx;
35054
+ node.y += node.vy;
35011
35055
  }
35012
35056
  }
35013
- const damping = 0.9;
35014
35057
  for (const node of nodes) {
35015
- node.vx = (node.vx + node.fx) * damping;
35016
- node.vy = (node.vy + node.fy) * damping;
35017
- node.x += node.vx;
35018
- node.y += node.vy;
35019
35058
  node.x = Math.max(30, Math.min(w - 30, node.x));
35020
35059
  node.y = Math.max(30, Math.min(h - 30, node.y));
35021
35060
  }
35022
- const LABEL_GAP = 12;
35023
- const LABEL_H = 16;
35024
35061
  const pad = nodeSpacing / 2;
35025
- for (let i = 0; i < nodes.length; i++) {
35026
- for (let j = i + 1; j < nodes.length; j++) {
35027
- const a = nodes[i];
35028
- const b = nodes[j];
35029
- const ar = a.size || 8;
35030
- const br = b.size || 8;
35031
- if (showLabels) {
35032
- if (a.labelW === void 0) a.labelW = a.label ? measureLabelWidth(a.label) : 0;
35033
- if (b.labelW === void 0) b.labelW = b.label ? measureLabelWidth(b.label) : 0;
35034
- }
35035
- const labelBelow = showLabels ? LABEL_GAP + LABEL_H : 0;
35036
- const aHalfW = Math.max(ar, (a.labelW ?? 0) / 2) + pad;
35037
- const bHalfW = Math.max(br, (b.labelW ?? 0) / 2) + pad;
35038
- const aTop = a.y - ar - pad, aBot = a.y + ar + labelBelow + pad;
35039
- const bTop = b.y - br - pad, bBot = b.y + br + labelBelow + pad;
35040
- const overlapX = Math.min(a.x + aHalfW, b.x + bHalfW) - Math.max(a.x - aHalfW, b.x - bHalfW);
35041
- const overlapY = Math.min(aBot, bBot) - Math.max(aTop, bTop);
35042
- if (overlapX > 0 && overlapY > 0) {
35043
- if (overlapX <= overlapY) {
35044
- const push = overlapX / 2 * (b.x >= a.x ? 1 : -1);
35045
- a.x = Math.max(30, Math.min(w - 30, a.x - push));
35046
- b.x = Math.max(30, Math.min(w - 30, b.x + push));
35047
- } else {
35048
- const push = overlapY / 2 * (bTop + bBot >= aTop + aBot ? 1 : -1);
35049
- a.y = Math.max(30, Math.min(h - 30, a.y - push));
35050
- b.y = Math.max(30, Math.min(h - 30, b.y + push));
35062
+ const rectsOf = (n) => {
35063
+ const r = n.size || 8;
35064
+ const lw = showLabels ? n.labelW ?? (n.labelW = n.label ? measureLabelWidth(n.label, labelFont) : 0) : 0;
35065
+ return {
35066
+ circle: [n.x - r - pad, n.y - r - pad, n.x + r + pad, n.y + r + pad],
35067
+ label: [n.x - lw / 2 - pad, n.y + r + LABEL_GAP - 8, n.x + lw / 2 + pad, n.y + r + LABEL_GAP + LABEL_H]
35068
+ };
35069
+ };
35070
+ const sep = (r1, r2) => {
35071
+ const ox = Math.min(r1[2], r2[2]) - Math.max(r1[0], r2[0]);
35072
+ const oy = Math.min(r1[3], r2[3]) - Math.max(r1[1], r2[1]);
35073
+ if (ox <= 0 || oy <= 0) return null;
35074
+ 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 };
35075
+ };
35076
+ for (let pass = 0; pass < COLLIDE_PASSES; pass++) {
35077
+ for (let i = 0; i < nodes.length; i++) {
35078
+ for (let j = i + 1; j < nodes.length; j++) {
35079
+ const a = nodes[i];
35080
+ const b = nodes[j];
35081
+ const ra = rectsOf(a);
35082
+ const rb = rectsOf(b);
35083
+ let best = null;
35084
+ for (const [r1, r2] of [[ra.circle, rb.circle], [ra.circle, rb.label], [ra.label, rb.circle], [ra.label, rb.label]]) {
35085
+ const s = sep(r1, r2);
35086
+ if (s && (!best || s.depth < best.depth)) best = s;
35087
+ }
35088
+ if (best) {
35089
+ const push = best.depth / 2;
35090
+ if (best.axis === "x") {
35091
+ a.x = Math.max(30, Math.min(w - 30, a.x - push * best.sign));
35092
+ b.x = Math.max(30, Math.min(w - 30, b.x + push * best.sign));
35093
+ a.vx = 0;
35094
+ b.vx = 0;
35095
+ } else {
35096
+ a.y = Math.max(30, Math.min(h - 30, a.y - push * best.sign));
35097
+ b.y = Math.max(30, Math.min(h - 30, b.y + push * best.sign));
35098
+ a.vy = 0;
35099
+ b.vy = 0;
35100
+ }
35051
35101
  }
35052
35102
  }
35053
35103
  }
35054
35104
  }
35055
35105
  };
35056
35106
  if (fullRelayout) {
35057
- for (let i = 0; i < maxIterations; i++) tick();
35107
+ for (let i = 0; i < maxIterations; i++) tick(i);
35108
+ for (let i = 0; i < 4; i++) tick(maxIterations, { skipForces: true });
35109
+ const fitPad = 40;
35110
+ let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
35111
+ for (const node of nodesRef.current) {
35112
+ const r = node.size || 8;
35113
+ const lw = showLabels ? node.labelW ?? (node.labelW = node.label ? measureLabelWidth(node.label, labelFont) : 0) : 0;
35114
+ minX = Math.min(minX, node.x - r, node.x - lw / 2);
35115
+ minY = Math.min(minY, node.y - r);
35116
+ maxX = Math.max(maxX, node.x + r, node.x + lw / 2);
35117
+ maxY = Math.max(maxY, node.y + r + LABEL_GAP + LABEL_H);
35118
+ }
35119
+ const bboxW = Math.max(1, maxX - minX + fitPad * 2);
35120
+ const bboxH = Math.max(1, maxY - minY + fitPad * 2);
35121
+ const nextZoom = Math.min(1, viewW / bboxW, viewH / bboxH);
35122
+ setZoom(nextZoom);
35123
+ setOffset({ x: fitPad - minX * nextZoom, y: fitPad - minY * nextZoom });
35058
35124
  laidOutRef.current = true;
35059
35125
  } else {
35060
35126
  const centroids = /* @__PURE__ */ new Map();