@opentrace/components 0.1.1-rc.54 → 0.1.1-rc.62

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.
Files changed (29) hide show
  1. package/dist/opentrace-components.cjs +354 -246
  2. package/dist/opentrace-components.cjs.map +1 -1
  3. package/dist/opentrace-components.js +355 -247
  4. package/dist/opentrace-components.js.map +1 -1
  5. package/dist/src/GraphCanvas.d.ts +5 -0
  6. package/dist/src/GraphCanvas.d.ts.map +1 -1
  7. package/dist/src/colors/communityColors.d.ts.map +1 -1
  8. package/dist/src/config/graphLayout.d.ts +9 -9
  9. package/dist/src/config/graphLayout.d.ts.map +1 -1
  10. package/dist/src/graph/LayoutPipeline.d.ts +4 -6
  11. package/dist/src/graph/LayoutPipeline.d.ts.map +1 -1
  12. package/dist/src/graph/spacingWorker.d.ts +4 -0
  13. package/dist/src/graph/spacingWorker.d.ts.map +1 -1
  14. package/dist/src/graph/useGraphFilters.d.ts.map +1 -1
  15. package/dist/src/graph/useGraphInstance.d.ts.map +1 -1
  16. package/dist/src/graph/useGraphVisuals.d.ts.map +1 -1
  17. package/dist/src/index.d.ts +1 -1
  18. package/dist/src/index.d.ts.map +1 -1
  19. package/dist/src/sigma/useSelectionPulse.d.ts +3 -0
  20. package/dist/src/sigma/useSelectionPulse.d.ts.map +1 -0
  21. package/dist/{useHighlights-DbMfb0-p.js → useHighlights-CmOAWaLE.js} +60 -53
  22. package/dist/{useHighlights-DbMfb0-p.js.map → useHighlights-CmOAWaLE.js.map} +1 -1
  23. package/dist/{useHighlights-fRWg-A_c.cjs → useHighlights-zx7DM4V0.cjs} +60 -53
  24. package/dist/{useHighlights-fRWg-A_c.cjs.map → useHighlights-zx7DM4V0.cjs.map} +1 -1
  25. package/dist/utils.cjs +1 -1
  26. package/dist/utils.js +1 -1
  27. package/package.json +1 -1
  28. package/dist/assets/spacingWorker-hXLGHyRg.js +0 -123
  29. package/dist/assets/spacingWorker-hXLGHyRg.js.map +0 -1
@@ -2,7 +2,7 @@
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
3
  const jsxRuntime = require("react/jsx-runtime");
4
4
  const t = require("react");
5
- const useHighlights = require("./useHighlights-fRWg-A_c.cjs");
5
+ const useHighlights = require("./useHighlights-zx7DM4V0.cjs");
6
6
  const urlNormalize = require("./urlNormalize-DL0SAEQS.cjs");
7
7
  var _documentCurrentScript = typeof document !== "undefined" ? document.currentScript : null;
8
8
  function _toPrimitive$1(t2, r) {
@@ -5252,8 +5252,112 @@ createEdgeCurveProgram({
5252
5252
  extremity: "both"
5253
5253
  })
5254
5254
  });
5255
- const NODE_SIZE_MIN = 2;
5256
- const NODE_SIZE_MAX = 8;
5255
+ const PING_DURATION = 400;
5256
+ const PING_MAX_SCALE = 2;
5257
+ const PING_ALPHA = 0.5;
5258
+ const HALO_SCALE = 1.05;
5259
+ const HALO_ALPHA = 0.35;
5260
+ const HALO_LINE_WIDTH = 5;
5261
+ function useSelectionPulse(sigma, selectedNodeId, enabled) {
5262
+ const startTimeRef = t.useRef(0);
5263
+ const pingRafRef = t.useRef(null);
5264
+ t.useEffect(() => {
5265
+ if (!sigma || !selectedNodeId || !enabled) {
5266
+ if (pingRafRef.current !== null) {
5267
+ cancelAnimationFrame(pingRafRef.current);
5268
+ pingRafRef.current = null;
5269
+ }
5270
+ return;
5271
+ }
5272
+ const graph = sigma.getGraph();
5273
+ if (!graph.hasNode(selectedNodeId)) return;
5274
+ startTimeRef.current = performance.now();
5275
+ function safeRefresh() {
5276
+ try {
5277
+ sigma?.refresh();
5278
+ } catch {
5279
+ }
5280
+ }
5281
+ function pingLoop() {
5282
+ if (!sigma || !selectedNodeId) return;
5283
+ const elapsed = performance.now() - startTimeRef.current;
5284
+ if (elapsed < PING_DURATION) {
5285
+ safeRefresh();
5286
+ pingRafRef.current = requestAnimationFrame(pingLoop);
5287
+ } else {
5288
+ pingRafRef.current = null;
5289
+ safeRefresh();
5290
+ }
5291
+ }
5292
+ pingRafRef.current = requestAnimationFrame(pingLoop);
5293
+ const handler = () => {
5294
+ if (!sigma || !selectedNodeId) return;
5295
+ const graph2 = sigma.getGraph();
5296
+ if (!graph2.hasNode(selectedNodeId)) return;
5297
+ const attrs = graph2.getNodeAttributes(selectedNodeId);
5298
+ const nodePosition = sigma.graphToViewport({
5299
+ x: attrs.x,
5300
+ y: attrs.y
5301
+ });
5302
+ const camera = sigma.getCamera();
5303
+ const baseSize = attrs.size ?? 5;
5304
+ const screenSize = baseSize / camera.ratio;
5305
+ const container = sigma.getContainer();
5306
+ const canvas = container.querySelector(
5307
+ ".sigma-hovers"
5308
+ );
5309
+ if (!canvas) return;
5310
+ const ctx = canvas.getContext("2d");
5311
+ if (!ctx) return;
5312
+ const color = attrs.color ?? "#ffffff";
5313
+ const elapsed = performance.now() - startTimeRef.current;
5314
+ if (elapsed < PING_DURATION) {
5315
+ const t2 = elapsed / PING_DURATION;
5316
+ const scale2 = 1 + (PING_MAX_SCALE - 1) * t2;
5317
+ const alpha = PING_ALPHA * (1 - t2);
5318
+ ctx.save();
5319
+ ctx.globalAlpha = alpha;
5320
+ ctx.strokeStyle = color;
5321
+ ctx.lineWidth = 2;
5322
+ ctx.beginPath();
5323
+ ctx.arc(
5324
+ nodePosition.x,
5325
+ nodePosition.y,
5326
+ screenSize * scale2,
5327
+ 0,
5328
+ Math.PI * 2
5329
+ );
5330
+ ctx.stroke();
5331
+ ctx.restore();
5332
+ }
5333
+ ctx.save();
5334
+ ctx.globalAlpha = HALO_ALPHA;
5335
+ ctx.strokeStyle = color;
5336
+ ctx.lineWidth = HALO_LINE_WIDTH;
5337
+ ctx.beginPath();
5338
+ ctx.arc(
5339
+ nodePosition.x,
5340
+ nodePosition.y,
5341
+ screenSize * HALO_SCALE,
5342
+ 0,
5343
+ Math.PI * 2
5344
+ );
5345
+ ctx.stroke();
5346
+ ctx.restore();
5347
+ };
5348
+ sigma.on("afterRender", handler);
5349
+ safeRefresh();
5350
+ return () => {
5351
+ sigma.off("afterRender", handler);
5352
+ if (pingRafRef.current !== null) {
5353
+ cancelAnimationFrame(pingRafRef.current);
5354
+ pingRafRef.current = null;
5355
+ }
5356
+ };
5357
+ }, [sigma, selectedNodeId, enabled]);
5358
+ }
5359
+ const NODE_SIZE_MIN = 3;
5360
+ const NODE_SIZE_MAX = 12;
5257
5361
  const NODE_SIZE_DEGREE_SCALE = 1;
5258
5362
  const NODE_SIZE_MULTIPLIERS = {
5259
5363
  Repository: 1,
@@ -5265,12 +5369,12 @@ const EDGE_SIZE_DEFAULT = 1;
5265
5369
  const EDGE_SIZE_DEFAULT_LINE = 2;
5266
5370
  const EDGE_SIZE_HIGHLIGHTED = 1.25;
5267
5371
  const EDGE_SIZE_DIMMED = 0.5;
5268
- const EDGE_OPACITY_DEFAULT = 0.6;
5372
+ const EDGE_OPACITY_DEFAULT = 0.78;
5269
5373
  const EDGE_OPACITY_HIGHLIGHTED = 1;
5270
- const EDGE_OPACITY_DIMMED = 0.05;
5271
- const NODE_OPACITY_DIMMED = 0.15;
5374
+ const EDGE_OPACITY_DIMMED = 0.08;
5375
+ const NODE_OPACITY_DIMMED = 0.22;
5272
5376
  const NODE_SIZE_DIMMED_SCALE = 0.35;
5273
- const ZOOM_SIZE_EXPONENT = 0.9;
5377
+ const ZOOM_SIZE_EXPONENT = 0.7;
5274
5378
  const FORCE_LINK_DISTANCE = 200;
5275
5379
  const FORCE_CHARGE_STRENGTH = -200;
5276
5380
  const FORCE_SIMULATION_TICKS = 80;
@@ -5278,15 +5382,15 @@ const FORCE_CLUSTER_STRENGTH = 0.3;
5278
5382
  const FORCE_CLUSTER_TICKS = 40;
5279
5383
  const FA2_ENABLED = true;
5280
5384
  const FA2_GRAVITY = 0.1;
5281
- const FA2_SCALING_RATIO = 30;
5282
- const FA2_SLOW_DOWN = 2;
5385
+ const FA2_SCALING_RATIO = 120;
5386
+ const FA2_SLOW_DOWN = 0.5;
5283
5387
  const FA2_BARNES_HUT_THRESHOLD = 300;
5284
5388
  const FA2_BARNES_HUT_THETA = 0.5;
5285
5389
  const FA2_STRONG_GRAVITY = false;
5286
5390
  const FA2_LIN_LOG_MODE = true;
5287
5391
  const FA2_OUTBOUND_ATTRACTION = true;
5288
5392
  const FA2_ADJUST_SIZES = true;
5289
- const FA2_DURATION = 3e3;
5393
+ const FA2_DURATION = 2e4;
5290
5394
  const NOVERLAP_MAX_ITERATIONS = 50;
5291
5395
  const NOVERLAP_RATIO = 1.5;
5292
5396
  const NOVERLAP_MARGIN = 25;
@@ -5351,6 +5455,145 @@ function endpointId(endpoint) {
5351
5455
  return endpoint.id;
5352
5456
  return String(endpoint);
5353
5457
  }
5458
+ function applySpacing(pos, assignments, radiusScale, gap, maxIterations, pushFactor) {
5459
+ const groups = /* @__PURE__ */ new Map();
5460
+ for (const [id] of pos) {
5461
+ const cid = assignments[id];
5462
+ if (cid === void 0) continue;
5463
+ let list = groups.get(cid);
5464
+ if (!list) {
5465
+ list = [];
5466
+ groups.set(cid, list);
5467
+ }
5468
+ list.push(id);
5469
+ }
5470
+ if (groups.size < 2) return;
5471
+ const comms = [];
5472
+ for (const [, ids] of groups) {
5473
+ let cx = 0, cy = 0;
5474
+ for (const id of ids) {
5475
+ const p = pos.get(id);
5476
+ cx += p.x;
5477
+ cy += p.y;
5478
+ }
5479
+ cx /= ids.length;
5480
+ cy /= ids.length;
5481
+ comms.push({
5482
+ nodeIds: ids,
5483
+ cx,
5484
+ cy,
5485
+ radius: Math.sqrt(ids.length) * radiusScale
5486
+ });
5487
+ }
5488
+ for (let iter = 0; iter < maxIterations; iter++) {
5489
+ const pushX = new Float64Array(comms.length);
5490
+ const pushY = new Float64Array(comms.length);
5491
+ let maxOverlap = 0;
5492
+ for (let i = 0; i < comms.length; i++) {
5493
+ for (let j2 = i + 1; j2 < comms.length; j2++) {
5494
+ const a = comms[i];
5495
+ const b = comms[j2];
5496
+ const dx = b.cx - a.cx;
5497
+ const dy = b.cy - a.cy;
5498
+ const dist = Math.sqrt(dx * dx + dy * dy);
5499
+ const minDist = a.radius + b.radius + gap;
5500
+ if (dist < minDist) {
5501
+ const overlap = (minDist - dist) / minDist;
5502
+ if (overlap > maxOverlap) maxOverlap = overlap;
5503
+ const push = (minDist - dist) * pushFactor;
5504
+ if (dist > 1e-3) {
5505
+ const nx = dx / dist;
5506
+ const ny = dy / dist;
5507
+ const totalSize = a.nodeIds.length + b.nodeIds.length;
5508
+ const aRatio = b.nodeIds.length / totalSize;
5509
+ const bRatio = a.nodeIds.length / totalSize;
5510
+ pushX[i] -= nx * push * aRatio;
5511
+ pushY[i] -= ny * push * aRatio;
5512
+ pushX[j2] += nx * push * bRatio;
5513
+ pushY[j2] += ny * push * bRatio;
5514
+ } else {
5515
+ const angle = Math.random() * Math.PI * 2;
5516
+ pushX[i] -= Math.cos(angle) * minDist * 0.5;
5517
+ pushY[i] -= Math.sin(angle) * minDist * 0.5;
5518
+ pushX[j2] += Math.cos(angle) * minDist * 0.5;
5519
+ pushY[j2] += Math.sin(angle) * minDist * 0.5;
5520
+ }
5521
+ }
5522
+ }
5523
+ }
5524
+ if (maxOverlap <= 0.05) break;
5525
+ for (let i = 0; i < comms.length; i++) {
5526
+ const ox = pushX[i];
5527
+ const oy = pushY[i];
5528
+ if (Math.abs(ox) < 0.01 && Math.abs(oy) < 0.01) continue;
5529
+ comms[i].cx += ox;
5530
+ comms[i].cy += oy;
5531
+ for (const id of comms[i].nodeIds) {
5532
+ const p = pos.get(id);
5533
+ p.x += ox;
5534
+ p.y += oy;
5535
+ }
5536
+ }
5537
+ }
5538
+ }
5539
+ function applyNoverlap(pos, sizes, assignments, margin, iterations) {
5540
+ const groups = /* @__PURE__ */ new Map();
5541
+ for (const [id] of pos) {
5542
+ const cid = assignments[id];
5543
+ if (cid === void 0) continue;
5544
+ let list = groups.get(cid);
5545
+ if (!list) {
5546
+ list = [];
5547
+ groups.set(cid, list);
5548
+ }
5549
+ list.push(id);
5550
+ }
5551
+ let minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity;
5552
+ for (const [, p] of pos) {
5553
+ if (p.x < minX) minX = p.x;
5554
+ if (p.x > maxX) maxX = p.x;
5555
+ if (p.y < minY) minY = p.y;
5556
+ if (p.y > maxY) maxY = p.y;
5557
+ }
5558
+ const scale2 = Math.max(maxX - minX, maxY - minY, 1) / 4e3;
5559
+ const scaledMargin = margin * scale2;
5560
+ const MAX_INLINE = 500;
5561
+ for (const [, ids] of groups) {
5562
+ if (ids.length < 3 || ids.length > MAX_INLINE) continue;
5563
+ const nodes = ids.map((id) => ({
5564
+ id,
5565
+ x: pos.get(id).x,
5566
+ y: pos.get(id).y,
5567
+ size: (sizes.get(id) ?? 3) * scale2
5568
+ }));
5569
+ for (let iter = 0; iter < iterations; iter++) {
5570
+ for (let i = 0; i < nodes.length; i++) {
5571
+ const a = nodes[i];
5572
+ for (let j2 = i + 1; j2 < nodes.length; j2++) {
5573
+ const b = nodes[j2];
5574
+ const dx = b.x - a.x;
5575
+ const dy = b.y - a.y;
5576
+ const dist = Math.sqrt(dx * dx + dy * dy);
5577
+ const minDist = a.size + b.size + scaledMargin;
5578
+ if (dist < minDist && dist > 1e-3) {
5579
+ const push = (minDist - dist) * 0.5;
5580
+ const nx = dx / dist;
5581
+ const ny = dy / dist;
5582
+ a.x -= nx * push;
5583
+ a.y -= ny * push;
5584
+ b.x += nx * push;
5585
+ b.y += ny * push;
5586
+ }
5587
+ }
5588
+ }
5589
+ }
5590
+ for (const n2 of nodes) {
5591
+ const p = pos.get(n2.id);
5592
+ p.x = n2.x;
5593
+ p.y = n2.y;
5594
+ }
5595
+ }
5596
+ }
5354
5597
  function useGraphInstance({
5355
5598
  allNodes,
5356
5599
  allLinks,
@@ -5427,6 +5670,48 @@ function useGraphInstance({
5427
5670
  }
5428
5671
  });
5429
5672
  }
5673
+ const { assignments: communityAssignments } = communityData;
5674
+ if (communityAssignments) {
5675
+ const communityGroups = /* @__PURE__ */ new Map();
5676
+ for (const node of allNodes) {
5677
+ const cid = communityAssignments[node.id];
5678
+ if (cid === void 0) continue;
5679
+ let list = communityGroups.get(cid);
5680
+ if (!list) {
5681
+ list = [];
5682
+ communityGroups.set(cid, list);
5683
+ }
5684
+ list.push(node.id);
5685
+ }
5686
+ let vcIdx = 0;
5687
+ for (const [, members] of communityGroups) {
5688
+ if (members.length < 2) continue;
5689
+ const baseTies = members.length >= 10 ? 3 : 2;
5690
+ for (const nodeId of members) {
5691
+ const count = Math.min(baseTies, members.length - 1);
5692
+ for (let c2 = 0; c2 < count; c2++) {
5693
+ const targetIdx = (members.indexOf(nodeId) + c2 + 1) % members.length;
5694
+ const targetId = members[targetIdx];
5695
+ if (targetId === nodeId) continue;
5696
+ const vcKey = `_vc_${vcIdx++}`;
5697
+ if (seenEdges.has(`${nodeId}-_COMMUNITY_-${targetId}`)) continue;
5698
+ seenEdges.add(`${nodeId}-_COMMUNITY_-${targetId}`);
5699
+ serializedEdges.push({
5700
+ key: vcKey,
5701
+ source: nodeId,
5702
+ target: targetId,
5703
+ attributes: {
5704
+ label: "_COMMUNITY_",
5705
+ color: "transparent",
5706
+ size: 0,
5707
+ hidden: true,
5708
+ _virtual: true
5709
+ }
5710
+ });
5711
+ }
5712
+ }
5713
+ }
5714
+ }
5430
5715
  graph.import({
5431
5716
  nodes: serializedNodes,
5432
5717
  edges: serializedEdges
@@ -5474,6 +5759,15 @@ function useGraphInstance({
5474
5759
  console.timeEnd("[graph] d3-force worker layout");
5475
5760
  console.log(`[graph] layout computed for ${pos.size} nodes`);
5476
5761
  }
5762
+ const { assignments: assignments2 } = communityData;
5763
+ if (assignments2 && Object.keys(assignments2).length > 0) {
5764
+ applySpacing(pos, assignments2, 40, 100, 50, 0.5);
5765
+ const sizeMap = /* @__PURE__ */ new Map();
5766
+ for (const sn of serializedNodes) {
5767
+ sizeMap.set(sn.key, sn.attributes.size);
5768
+ }
5769
+ applyNoverlap(pos, sizeMap, assignments2, layoutConfig.noverlapMargin, layoutConfig.noverlapCommunityIterations ?? 20);
5770
+ }
5477
5771
  graph.updateEachNodeAttributes((_id, attrs) => {
5478
5772
  const p = pos.get(_id);
5479
5773
  if (p) {
@@ -5563,6 +5857,10 @@ function useGraphVisuals(graph, layoutReady, visualState, layoutConfig, _degreeM
5563
5857
  const defaultEdgeSize = isLargeGraph ? EDGE_SIZE_DEFAULT_LINE : EDGE_SIZE_DEFAULT;
5564
5858
  graph.updateEachEdgeAttributes(
5565
5859
  (_id, attrs, source, target) => {
5860
+ if (attrs._virtual) {
5861
+ attrs.hidden = true;
5862
+ return attrs;
5863
+ }
5566
5864
  const linkKey = `${source}-${target}`;
5567
5865
  const isHighlighted = highlightLinks.has(linkKey);
5568
5866
  const baseColor = getLinkColor(attrs.label);
@@ -6853,13 +7151,10 @@ function LayoutPipeline({
6853
7151
  layoutReady,
6854
7152
  layoutConfig,
6855
7153
  optimizeTick,
6856
- communityAssignments,
6857
7154
  onOptimizeStatus
6858
7155
  }) {
6859
7156
  const sigma = v();
6860
7157
  const timerRef = t.useRef(null);
6861
- const spacingWorkerRef = t.useRef(null);
6862
- const perCommunityNoverlapCancel = t.useRef(null);
6863
7158
  const { start, stop } = n({
6864
7159
  settings: {
6865
7160
  gravity: layoutConfig.fa2Gravity,
@@ -6878,245 +7173,24 @@ function LayoutPipeline({
6878
7173
  clearTimeout(timerRef.current);
6879
7174
  timerRef.current = null;
6880
7175
  }
6881
- if (spacingWorkerRef.current) {
6882
- spacingWorkerRef.current.terminate();
6883
- spacingWorkerRef.current = null;
6884
- }
6885
- perCommunityNoverlapCancel.current?.();
6886
- perCommunityNoverlapCancel.current = null;
6887
7176
  stop();
6888
7177
  }
6889
- function runSpacing(onDone) {
6890
- const graph = sigma.getGraph();
6891
- if (graph.order === 0 || !communityAssignments) {
6892
- onDone();
6893
- return;
6894
- }
6895
- onOptimizeStatus?.({ phase: "spacing" });
6896
- const nodes = [];
6897
- graph.forEachNode((id, attrs) => {
6898
- const cid = communityAssignments[id];
6899
- if (cid === void 0) return;
6900
- nodes.push({
6901
- id,
6902
- x: attrs.x,
6903
- y: attrs.y,
6904
- communityId: cid
6905
- });
6906
- });
6907
- const worker2 = new Worker(new URL(
6908
- /* @vite-ignore */
6909
- "/assets/spacingWorker-hXLGHyRg.js",
6910
- typeof document === "undefined" ? require("url").pathToFileURL(__filename).href : _documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === "SCRIPT" && _documentCurrentScript.src || new URL("opentrace-components.cjs", document.baseURI).href
6911
- ), {
6912
- type: "module"
6913
- });
6914
- spacingWorkerRef.current = worker2;
6915
- worker2.onmessage = (e2) => {
6916
- const msg = e2.data;
6917
- if (msg.type === "progress") {
6918
- if (msg.updates.length > 0) {
6919
- const posMap = /* @__PURE__ */ new Map();
6920
- for (const u of msg.updates) posMap.set(u.id, { x: u.x, y: u.y });
6921
- graph.updateEachNodeAttributes((id, attrs) => {
6922
- const pos = posMap.get(id);
6923
- if (pos) {
6924
- attrs.x = pos.x;
6925
- attrs.y = pos.y;
6926
- }
6927
- return attrs;
6928
- });
6929
- }
6930
- onOptimizeStatus?.({
6931
- phase: "spacing",
6932
- iteration: msg.iteration,
6933
- cleanRatio: 1 - msg.maxOverlap
6934
- });
6935
- if (process.env.NODE_ENV === "development") {
6936
- console.log(
6937
- `%c[spacing]%c iter ${msg.iteration}: overlap ${(msg.maxOverlap * 100).toFixed(0)}%, ${msg.updates.length} nodes moved`,
6938
- "color: #fbbf24; font-weight: bold",
6939
- "color: inherit"
6940
- );
6941
- }
6942
- } else if (msg.type === "done") {
6943
- if (process.env.NODE_ENV === "development") {
6944
- console.log(
6945
- `%c[spacing]%c done: ${msg.iterations} iters, ${(msg.maxOverlap * 100).toFixed(0)}% max overlap in ${msg.totalMs.toFixed(0)}ms`,
6946
- "color: #fbbf24; font-weight: bold",
6947
- "color: inherit"
6948
- );
6949
- }
6950
- worker2.terminate();
6951
- spacingWorkerRef.current = null;
6952
- onDone();
6953
- }
6954
- };
6955
- worker2.onerror = () => {
6956
- worker2.terminate();
6957
- spacingWorkerRef.current = null;
6958
- onDone();
6959
- };
6960
- worker2.postMessage({
6961
- nodes,
6962
- radiusScale: 40,
6963
- gap: 100,
6964
- maxIterations: 50,
6965
- overlapThreshold: 0.05
6966
- });
6967
- }
6968
- function getGraphScaleFactor() {
6969
- const graph = sigma.getGraph();
6970
- let minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity;
6971
- graph.forEachNode((_, attrs) => {
6972
- const x = attrs.x;
6973
- const y = attrs.y;
6974
- if (x < minX) minX = x;
6975
- if (x > maxX) maxX = x;
6976
- if (y < minY) minY = y;
6977
- if (y > maxY) maxY = y;
6978
- });
6979
- return Math.max(maxX - minX, maxY - minY, 1) / 4e3;
6980
- }
6981
- function runPerCommunityNoverlap(onDone) {
6982
- const graph = sigma.getGraph();
6983
- if (graph.order === 0 || !communityAssignments) {
6984
- onDone();
6985
- return;
6986
- }
6987
- const scale2 = getGraphScaleFactor();
6988
- const groups = /* @__PURE__ */ new Map();
6989
- graph.forEachNode((id) => {
6990
- const cid = communityAssignments[id];
6991
- if (cid === void 0) return;
6992
- let list = groups.get(cid);
6993
- if (!list) {
6994
- list = [];
6995
- groups.set(cid, list);
6996
- }
6997
- list.push(id);
6998
- });
6999
- const sorted = [...groups.entries()].sort(
7000
- (a, b) => b[1].length - a[1].length
7001
- );
7002
- const MAX_INLINE = 500;
7003
- let idx = 0;
7004
- let totalMoved = 0;
7005
- let cancelled = false;
7006
- let pendingCallbackId = null;
7007
- perCommunityNoverlapCancel.current = () => {
7008
- cancelled = true;
7009
- if (pendingCallbackId !== null) {
7010
- if (typeof cancelIdleCallback === "function") {
7011
- cancelIdleCallback(pendingCallbackId);
7012
- } else {
7013
- clearTimeout(pendingCallbackId);
7014
- }
7015
- pendingCallbackId = null;
7016
- }
7017
- };
7018
- function processNext() {
7019
- if (cancelled || idx >= sorted.length) {
7020
- if (process.env.NODE_ENV === "development") {
7021
- console.log(
7022
- `%c[noverlap]%c per-community done: ${sorted.length} communities, ${totalMoved} nodes moved${cancelled ? " (cancelled)" : ""}`,
7023
- "color: #4ade80; font-weight: bold",
7024
- "color: inherit"
7025
- );
7026
- }
7027
- perCommunityNoverlapCancel.current = null;
7028
- if (!cancelled) onDone();
7029
- return;
7030
- }
7031
- const [, nodeIds] = sorted[idx++];
7032
- if (nodeIds.length < 3 || nodeIds.length > MAX_INLINE) {
7033
- scheduleNext();
7034
- return;
7035
- }
7036
- const subGraph = /* @__PURE__ */ new Map();
7037
- for (const nid of nodeIds) {
7038
- const attrs = graph.getNodeAttributes(nid);
7039
- subGraph.set(nid, {
7040
- x: attrs.x,
7041
- y: attrs.y,
7042
- size: (attrs.size ?? 3) * scale2
7043
- });
7044
- }
7045
- const margin = layoutConfig.noverlapMargin * scale2;
7046
- const iters = layoutConfig.noverlapCommunityIterations ?? 20;
7047
- const nodeArr = [...subGraph.entries()];
7048
- for (let iter = 0; iter < iters; iter++) {
7049
- for (let i = 0; i < nodeArr.length; i++) {
7050
- const [, a] = nodeArr[i];
7051
- for (let j2 = i + 1; j2 < nodeArr.length; j2++) {
7052
- const [, b] = nodeArr[j2];
7053
- const dx = b.x - a.x;
7054
- const dy = b.y - a.y;
7055
- const dist = Math.sqrt(dx * dx + dy * dy);
7056
- const minDist = a.size + b.size + margin;
7057
- if (dist < minDist && dist > 1e-3) {
7058
- const push = (minDist - dist) * 0.5;
7059
- const nx = dx / dist;
7060
- const ny = dy / dist;
7061
- a.x -= nx * push;
7062
- a.y -= ny * push;
7063
- b.x += nx * push;
7064
- b.y += ny * push;
7065
- }
7066
- }
7067
- }
7068
- }
7069
- let moved = 0;
7070
- for (const [nid, pos] of subGraph) {
7071
- const attrs = graph.getNodeAttributes(nid);
7072
- if (Math.abs(pos.x - attrs.x) > 0.1 || Math.abs(pos.y - attrs.y) > 0.1) {
7073
- graph.mergeNodeAttributes(nid, { x: pos.x, y: pos.y });
7074
- moved++;
7075
- }
7076
- }
7077
- totalMoved += moved;
7078
- scheduleNext();
7079
- }
7080
- function scheduleNext() {
7081
- if (typeof requestIdleCallback === "function") {
7082
- pendingCallbackId = requestIdleCallback(processNext, { timeout: 50 });
7083
- } else {
7084
- pendingCallbackId = setTimeout(processNext, 0);
7085
- }
7086
- }
7087
- processNext();
7088
- }
7089
7178
  t.useEffect(() => {
7090
7179
  if (!layoutReady) return;
7091
7180
  cleanup();
7092
7181
  const sigmaInstance = sigma;
7093
7182
  const graph = sigma.getGraph();
7094
- runSpacing(() => {
7095
- zoomToFit(sigmaInstance, 0);
7096
- const runFA2 = (duration, onDone) => {
7097
- if (layoutConfig.fa2Enabled && graph.order > 0) {
7098
- onOptimizeStatus?.({ phase: "fa2" });
7099
- start();
7100
- timerRef.current = setTimeout(() => {
7101
- stop();
7102
- onDone();
7103
- }, duration);
7104
- } else {
7105
- onDone();
7106
- }
7107
- };
7108
- runFA2(layoutConfig.fa2Duration, () => {
7109
- onOptimizeStatus?.({ phase: "noverlap" });
7110
- runPerCommunityNoverlap(() => {
7111
- runSpacing(() => {
7112
- runFA2(Math.min(layoutConfig.fa2Duration, 1500), () => {
7113
- zoomToFit(sigmaInstance, 600);
7114
- onOptimizeStatus?.(null);
7115
- });
7116
- });
7117
- });
7118
- });
7119
- });
7183
+ zoomToFit(sigmaInstance, 0);
7184
+ if (layoutConfig.fa2Enabled && graph.order > 0) {
7185
+ onOptimizeStatus?.({ phase: "fa2" });
7186
+ start();
7187
+ timerRef.current = setTimeout(() => {
7188
+ stop();
7189
+ onOptimizeStatus?.(null);
7190
+ }, layoutConfig.fa2Duration);
7191
+ } else {
7192
+ onOptimizeStatus?.(null);
7193
+ }
7120
7194
  return cleanup;
7121
7195
  }, [layoutReady, optimizeTick, layoutConfig, start, stop, sigma]);
7122
7196
  return null;
@@ -7316,10 +7390,25 @@ function GraphEventHandler({
7316
7390
  }, [sigma, onNodeClick, onEdgeClick, onStageClick]);
7317
7391
  return null;
7318
7392
  }
7393
+ function AnimationEffects({
7394
+ selectedNodeId,
7395
+ animationSettings
7396
+ }) {
7397
+ const sigma = v();
7398
+ useSelectionPulse(
7399
+ sigma,
7400
+ selectedNodeId,
7401
+ animationSettings.selectionPulse
7402
+ );
7403
+ return null;
7404
+ }
7319
7405
  const defaultGetSubType = () => null;
7320
7406
  const EMPTY_STRING_SET = /* @__PURE__ */ new Set();
7321
7407
  const EMPTY_NUMBER_SET = /* @__PURE__ */ new Set();
7322
7408
  const EMPTY_SUB_TYPES = /* @__PURE__ */ new Map();
7409
+ const DEFAULT_ANIMATION = {
7410
+ selectionPulse: true
7411
+ };
7323
7412
  const GraphCanvas = t.memo(
7324
7413
  t.forwardRef(
7325
7414
  function GraphCanvas2(props, ref) {
@@ -7344,6 +7433,7 @@ const GraphCanvas = t.memo(
7344
7433
  availableSubTypes = EMPTY_SUB_TYPES,
7345
7434
  zIndex: zIndexEnabled = false,
7346
7435
  communityData: communityDataProp,
7436
+ animationSettings = DEFAULT_ANIMATION,
7347
7437
  onNodeClick,
7348
7438
  onEdgeClick,
7349
7439
  onStageClick,
@@ -7474,9 +7564,21 @@ const GraphCanvas = t.memo(
7474
7564
  }),
7475
7565
  [isLargeGraph, zIndexEnabled]
7476
7566
  );
7567
+ const edgeReducer = t.useCallback(
7568
+ (_edge, data) => {
7569
+ if (!sigmaRef.current) return data;
7570
+ const ratio = sigmaRef.current.getCamera().ratio;
7571
+ return { ...data, size: (data.size || 1) * ratio };
7572
+ },
7573
+ []
7574
+ );
7477
7575
  const handleSigmaReady = t.useCallback(
7478
7576
  (sigma) => {
7479
7577
  sigmaRef.current = sigma;
7578
+ sigma.setSetting(
7579
+ "edgeReducer",
7580
+ edgeReducer
7581
+ );
7480
7582
  },
7481
7583
  []
7482
7584
  );
@@ -7620,10 +7722,16 @@ const GraphCanvas = t.memo(
7620
7722
  layoutReady,
7621
7723
  layoutConfig,
7622
7724
  optimizeTick,
7623
- communityAssignments: communityData.assignments,
7624
7725
  onOptimizeStatus
7625
7726
  }
7626
7727
  ),
7728
+ /* @__PURE__ */ jsxRuntime.jsx(
7729
+ AnimationEffects,
7730
+ {
7731
+ selectedNodeId: selectedNodeId ?? null,
7732
+ animationSettings
7733
+ }
7734
+ ),
7627
7735
  /* @__PURE__ */ jsxRuntime.jsx(SigmaRefCapture, { onReady: handleSigmaReady })
7628
7736
  ]
7629
7737
  }