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