react-html-graph 1.2.2 → 1.2.3

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 (110) hide show
  1. package/dist/behaviour/move-behaviour.d.ts +50 -0
  2. package/dist/behaviour/move-behaviour.d.ts.map +1 -0
  3. package/dist/behaviour/move-behaviour.js +71 -0
  4. package/dist/behaviour/move-behaviour.js.map +1 -0
  5. package/dist/calculations/index.d.ts.map +1 -1
  6. package/dist/calculations/index.js +344 -14
  7. package/dist/calculations/index.js.map +1 -1
  8. package/dist/context/connection-context.d.ts.map +1 -1
  9. package/dist/context/connection-context.js.map +1 -1
  10. package/dist/context/error-context.d.ts.map +1 -1
  11. package/dist/context/error-context.js.map +1 -1
  12. package/dist/context/graph-context.d.ts.map +1 -1
  13. package/dist/context/graph-context.js.map +1 -1
  14. package/dist/context/graph-event-bus-context.d.ts +4 -0
  15. package/dist/context/graph-event-bus-context.d.ts.map +1 -0
  16. package/dist/context/graph-event-bus-context.js +8 -0
  17. package/dist/context/graph-event-bus-context.js.map +1 -0
  18. package/dist/context/graph-root-context.d.ts.map +1 -1
  19. package/dist/context/graph-root-context.js.map +1 -1
  20. package/dist/context/link-info-context.d.ts +4 -0
  21. package/dist/context/link-info-context.d.ts.map +1 -0
  22. package/dist/context/link-info-context.js +15 -0
  23. package/dist/context/link-info-context.js.map +1 -0
  24. package/dist/context/node-event-context.d.ts +4 -0
  25. package/dist/context/node-event-context.d.ts.map +1 -0
  26. package/dist/context/node-event-context.js +10 -0
  27. package/dist/context/node-event-context.js.map +1 -0
  28. package/dist/context/node-registry-context.d.ts +4 -0
  29. package/dist/context/node-registry-context.d.ts.map +1 -0
  30. package/dist/context/node-registry-context.js +13 -0
  31. package/dist/context/node-registry-context.js.map +1 -0
  32. package/dist/graph/index.d.ts +2 -2
  33. package/dist/graph/index.d.ts.map +1 -1
  34. package/dist/graph/index.js +256 -48
  35. package/dist/graph/index.js.map +1 -1
  36. package/dist/hooks/connection.d.ts +1 -1
  37. package/dist/hooks/graph-event-bus.d.ts +3 -0
  38. package/dist/hooks/graph-event-bus.d.ts.map +1 -0
  39. package/dist/hooks/graph-event-bus.js +7 -0
  40. package/dist/hooks/graph-event-bus.js.map +1 -0
  41. package/dist/hooks/link-info.d.ts +4 -0
  42. package/dist/hooks/link-info.d.ts.map +1 -0
  43. package/dist/hooks/link-info.js +7 -0
  44. package/dist/hooks/link-info.js.map +1 -0
  45. package/dist/hooks/node-registry.d.ts +3 -0
  46. package/dist/hooks/node-registry.d.ts.map +1 -0
  47. package/dist/hooks/node-registry.js +7 -0
  48. package/dist/hooks/node-registry.js.map +1 -0
  49. package/dist/hooks/nodeEvent.d.ts +7 -0
  50. package/dist/hooks/nodeEvent.d.ts.map +1 -0
  51. package/dist/hooks/nodeEvent.js +17 -0
  52. package/dist/hooks/nodeEvent.js.map +1 -0
  53. package/dist/hooks/use-graph-api.d.ts +50 -0
  54. package/dist/hooks/use-graph-api.d.ts.map +1 -0
  55. package/dist/hooks/use-graph-api.js +97 -0
  56. package/dist/hooks/use-graph-api.js.map +1 -0
  57. package/dist/link/base.d.ts +9 -20
  58. package/dist/link/base.d.ts.map +1 -1
  59. package/dist/link/base.js +17 -290
  60. package/dist/link/base.js.map +1 -1
  61. package/dist/link/temp-link.d.ts.map +1 -1
  62. package/dist/link/temp-link.js +18 -3
  63. package/dist/link/temp-link.js.map +1 -1
  64. package/dist/module.d.ts +9 -0
  65. package/dist/module.d.ts.map +1 -1
  66. package/dist/module.js +9 -0
  67. package/dist/module.js.map +1 -1
  68. package/dist/nodes/base.d.ts +2 -1
  69. package/dist/nodes/base.d.ts.map +1 -1
  70. package/dist/nodes/base.js +61 -89
  71. package/dist/nodes/base.js.map +1 -1
  72. package/dist/paths/bidirectional-path.d.ts +16 -0
  73. package/dist/paths/bidirectional-path.d.ts.map +1 -0
  74. package/dist/paths/bidirectional-path.js +260 -0
  75. package/dist/paths/bidirectional-path.js.map +1 -0
  76. package/dist/ports/base.d.ts.map +1 -1
  77. package/dist/ports/base.js +26 -13
  78. package/dist/ports/base.js.map +1 -1
  79. package/dist/providers/connection-provider.d.ts +1 -1
  80. package/dist/providers/connection-provider.d.ts.map +1 -1
  81. package/dist/providers/connection-provider.js +30 -16
  82. package/dist/providers/connection-provider.js.map +1 -1
  83. package/dist/providers/graph-event-bus-provider.d.ts +11 -0
  84. package/dist/providers/graph-event-bus-provider.d.ts.map +1 -0
  85. package/dist/providers/graph-event-bus-provider.js +46 -0
  86. package/dist/providers/graph-event-bus-provider.js.map +1 -0
  87. package/dist/providers/link-info-provider.d.ts +23 -0
  88. package/dist/providers/link-info-provider.d.ts.map +1 -0
  89. package/dist/providers/link-info-provider.js +31 -0
  90. package/dist/providers/link-info-provider.js.map +1 -0
  91. package/dist/providers/node-event-context.d.ts +3 -0
  92. package/dist/providers/node-event-context.d.ts.map +1 -0
  93. package/dist/providers/node-event-context.js +81 -0
  94. package/dist/providers/node-event-context.js.map +1 -0
  95. package/dist/providers/node-registry-provider.d.ts +11 -0
  96. package/dist/providers/node-registry-provider.d.ts.map +1 -0
  97. package/dist/providers/node-registry-provider.js +54 -0
  98. package/dist/providers/node-registry-provider.js.map +1 -0
  99. package/dist/style.css +1 -0
  100. package/dist/types.d.ts +206 -38
  101. package/dist/types.d.ts.map +1 -1
  102. package/dist/utils/proxy.d.ts +2 -0
  103. package/dist/utils/proxy.d.ts.map +1 -0
  104. package/dist/utils/proxy.js +18 -0
  105. package/dist/utils/proxy.js.map +1 -0
  106. package/dist/utils/symbols.d.ts +2 -0
  107. package/dist/utils/symbols.d.ts.map +1 -0
  108. package/dist/utils/symbols.js +2 -0
  109. package/dist/utils/symbols.js.map +1 -0
  110. package/package.json +1 -1
@@ -0,0 +1,50 @@
1
+ import { GraphMode, NodeEventEmitter, Point3D } from "../types";
2
+ type MoveState = {
3
+ moving: boolean;
4
+ movePointStart: {
5
+ x: number;
6
+ y: number;
7
+ };
8
+ startPos: {
9
+ x: number;
10
+ y: number;
11
+ };
12
+ currentPos: {
13
+ x: number;
14
+ y: number;
15
+ };
16
+ };
17
+ /** Opções para o hook useMoveBehaviour. */
18
+ export interface UseMoveBehaviourOptions {
19
+ /** Ref do elemento DOM do nó. */
20
+ elementRef: React.RefObject<HTMLElement | null>;
21
+ /** Getter estável para o zoom atual. */
22
+ getZoom: () => number;
23
+ /** Modo do grafo (readonly não permite drag). */
24
+ mode: GraphMode;
25
+ /** Posição efetiva atual do nó. */
26
+ position: Point3D;
27
+ /** Callback chamado ao finalizar o movimento. */
28
+ onMoveEnd?: (newPosition: Point3D) => void;
29
+ /** Callback chamado durante o arraste para reportar estado. */
30
+ onMoving?: (currentPosition: Point3D) => void;
31
+ /** Emitter de eventos do nó. */
32
+ eventEmitter?: NodeEventEmitter | null;
33
+ }
34
+ /** Retorno do hook useMoveBehaviour. */
35
+ export interface UseMoveBehaviourReturn {
36
+ /** Handler de mouseDown para iniciar o arraste. */
37
+ handleMouseDown: (e: React.MouseEvent<HTMLElement>) => void;
38
+ /** Handler de mouseUp para finalizar o arraste. */
39
+ handleMouseUp: (e: MouseEvent | React.MouseEvent) => void;
40
+ /** Posição corrente durante o arraste (via ref). */
41
+ moveRef: React.RefObject<MoveState>;
42
+ }
43
+ /**
44
+ * Hook que encapsula a lógica de drag/move de um nó do grafo.
45
+ * Atualiza o DOM diretamente durante o arraste (sem setState)
46
+ * e registra listeners globais de mousemove/mouseup no document.
47
+ */
48
+ export declare function useMoveBehaviour({ elementRef, getZoom, mode, position, onMoveEnd, onMoving, eventEmitter, }: UseMoveBehaviourOptions): UseMoveBehaviourReturn;
49
+ export {};
50
+ //# sourceMappingURL=move-behaviour.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"move-behaviour.d.ts","sourceRoot":"","sources":["../../src/behaviour/move-behaviour.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,gBAAgB,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AAEhE,KAAK,SAAS,GAAG;IACb,MAAM,EAAE,OAAO,CAAC;IAChB,cAAc,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACzC,QAAQ,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACnC,UAAU,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CACxC,CAAC;AAEF,2CAA2C;AAC3C,MAAM,WAAW,uBAAuB;IACpC,iCAAiC;IACjC,UAAU,EAAE,KAAK,CAAC,SAAS,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;IAChD,wCAAwC;IACxC,OAAO,EAAE,MAAM,MAAM,CAAC;IACtB,iDAAiD;IACjD,IAAI,EAAE,SAAS,CAAC;IAChB,mCAAmC;IACnC,QAAQ,EAAE,OAAO,CAAC;IAClB,iDAAiD;IACjD,SAAS,CAAC,EAAE,CAAC,WAAW,EAAE,OAAO,KAAK,IAAI,CAAC;IAC3C,+DAA+D;IAC/D,QAAQ,CAAC,EAAE,CAAC,eAAe,EAAE,OAAO,KAAK,IAAI,CAAC;IAC9C,gCAAgC;IAChC,YAAY,CAAC,EAAE,gBAAgB,GAAG,IAAI,CAAC;CAC1C;AAED,wCAAwC;AACxC,MAAM,WAAW,sBAAsB;IACnC,mDAAmD;IACnD,eAAe,EAAE,CAAC,CAAC,EAAE,KAAK,CAAC,UAAU,CAAC,WAAW,CAAC,KAAK,IAAI,CAAC;IAC5D,mDAAmD;IACnD,aAAa,EAAE,CAAC,CAAC,EAAE,UAAU,GAAG,KAAK,CAAC,UAAU,KAAK,IAAI,CAAC;IAC1D,oDAAoD;IACpD,OAAO,EAAE,KAAK,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC;CACvC;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,EAC7B,UAAU,EACV,OAAO,EACP,IAAI,EACJ,QAAQ,EACR,SAAS,EACT,QAAQ,EACR,YAAY,GACf,EAAE,uBAAuB,GAAG,sBAAsB,CAoElD"}
@@ -0,0 +1,71 @@
1
+ import { useCallback, useEffect, useRef } from "react";
2
+ /**
3
+ * Hook que encapsula a lógica de drag/move de um nó do grafo.
4
+ * Atualiza o DOM diretamente durante o arraste (sem setState)
5
+ * e registra listeners globais de mousemove/mouseup no document.
6
+ */
7
+ export function useMoveBehaviour({ elementRef, getZoom, mode, position, onMoveEnd, onMoving, eventEmitter, }) {
8
+ const moveRef = useRef({
9
+ moving: false,
10
+ movePointStart: { x: 0, y: 0 },
11
+ startPos: { x: 0, y: 0 },
12
+ currentPos: { x: 0, y: 0 },
13
+ });
14
+ const handleMouseDown = useCallback((e) => {
15
+ if (e.button !== 0 || mode === "readonly")
16
+ return;
17
+ e.preventDefault();
18
+ moveRef.current.moving = true;
19
+ moveRef.current.movePointStart.x = e.clientX;
20
+ moveRef.current.movePointStart.y = e.clientY;
21
+ moveRef.current.startPos = { x: position.x, y: position.y };
22
+ moveRef.current.currentPos = { x: position.x, y: position.y };
23
+ }, [position.x, position.y, mode]);
24
+ const handleMouseUp = useCallback((e) => {
25
+ if (e.button === 0 && moveRef.current.moving) {
26
+ moveRef.current.moving = false;
27
+ const nextPosition = {
28
+ x: moveRef.current.currentPos.x,
29
+ y: moveRef.current.currentPos.y,
30
+ z: position.z,
31
+ };
32
+ onMoveEnd?.(nextPosition);
33
+ eventEmitter?.("move", { position: nextPosition });
34
+ }
35
+ }, [position.z, onMoveEnd, eventEmitter]);
36
+ const handleMouseMove = useCallback((e) => {
37
+ if (!elementRef.current || !moveRef.current.moving)
38
+ return;
39
+ const zoom = getZoom();
40
+ const dx = (e.clientX - moveRef.current.movePointStart.x) / zoom;
41
+ const dy = (e.clientY - moveRef.current.movePointStart.y) / zoom;
42
+ const newX = moveRef.current.startPos.x + dx;
43
+ const newY = moveRef.current.startPos.y + dy;
44
+ const nextPosition = {
45
+ x: newX,
46
+ y: newY,
47
+ z: position.z,
48
+ };
49
+ moveRef.current.currentPos.x = newX;
50
+ moveRef.current.currentPos.y = newY;
51
+ elementRef.current.style.left = `${newX.toFixed(0)}px`;
52
+ elementRef.current.style.top = `${newY.toFixed(0)}px`;
53
+ onMoving?.(nextPosition);
54
+ eventEmitter?.("move", { position: nextPosition });
55
+ }, [position.z, getZoom, elementRef, onMoving, eventEmitter]);
56
+ // Registra listeners globais de mousemove e mouseup
57
+ useEffect(() => {
58
+ if (mode === "readonly")
59
+ return;
60
+ const onUp = (e) => handleMouseUp(e);
61
+ const onMove = (e) => handleMouseMove(e);
62
+ document.addEventListener("mouseup", onUp);
63
+ document.addEventListener("mousemove", onMove);
64
+ return () => {
65
+ document.removeEventListener("mouseup", onUp);
66
+ document.removeEventListener("mousemove", onMove);
67
+ };
68
+ }, [handleMouseUp, handleMouseMove, mode]);
69
+ return { handleMouseDown, handleMouseUp, moveRef };
70
+ }
71
+ //# sourceMappingURL=move-behaviour.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"move-behaviour.js","sourceRoot":"","sources":["../../src/behaviour/move-behaviour.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/calculations/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,SAAS,EACT,UAAU,EACV,WAAW,EACX,WAAW,EACX,WAAW,EACX,YAAY,EACZ,YAAY,EACZ,aAAa,EAChB,MAAM,SAAS,CAAC;AAkhCjB;;;;;;;;;GASG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,QAK/C;AAqCD;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,SAAS,GAAG,OAAO,CAAC,UAAU,CAAC,CAEnE;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC;IAAE,SAAS,EAAE,WAAW,EAAE,CAAA;CAAE,CAAC,CAEzF;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,YAAY,CAAC,CAEzE;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,aAAa,CAAC,CAE5E"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/calculations/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,SAAS,EACT,UAAU,EACV,WAAW,EACX,WAAW,EACX,WAAW,EACX,YAAY,EACZ,YAAY,EACZ,aAAa,EAChB,MAAM,SAAS,CAAC;AAw/CjB;;;;;;;;;GASG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,QAK/C;AAqCD;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,SAAS,GAAG,OAAO,CAAC,UAAU,CAAC,CAEnE;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC;IAAE,SAAS,EAAE,WAAW,EAAE,CAAA;CAAE,CAAC,CAEzF;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,YAAY,CAAC,CAEzE;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,aAAa,CAAC,CAE5E"}
@@ -344,15 +344,80 @@ function workerSource() {
344
344
  scaleY: Math.min(viewportHeight / viewportWidth, 1.8),
345
345
  };
346
346
  }
347
- function getPreferredDirection(options = {}, fallback) {
347
+ function getLayoutDirection(options = {}, fallback, respectViewport = true) {
348
348
  if (options.direction)
349
349
  return options.direction;
350
+ if (!respectViewport)
351
+ return fallback;
350
352
  const viewportWidth = options.viewport?.width ?? 0;
351
353
  const viewportHeight = options.viewport?.height ?? 0;
352
354
  if (viewportWidth <= 0 || viewportHeight <= 0)
353
355
  return fallback;
354
356
  return viewportWidth >= viewportHeight ? "LR" : "TB";
355
357
  }
358
+ function buildLayerOrderIndex(layers) {
359
+ const layerOrderIndex = new Map();
360
+ for (const ids of layers.values()) {
361
+ ids.forEach((id, index) => {
362
+ layerOrderIndex.set(id, index);
363
+ });
364
+ }
365
+ return layerOrderIndex;
366
+ }
367
+ function getNeighborBarycenter(nodeId, depths, neighbors, layerOrderIndex, currentDepth, relation) {
368
+ const positions = (neighbors.get(nodeId) ?? [])
369
+ .filter(neighborId => {
370
+ const neighborDepth = depths.get(neighborId);
371
+ if (neighborDepth === undefined)
372
+ return false;
373
+ return relation === "before"
374
+ ? neighborDepth < currentDepth
375
+ : neighborDepth > currentDepth;
376
+ })
377
+ .map(neighborId => layerOrderIndex.get(neighborId))
378
+ .filter((value) => value !== undefined);
379
+ if (positions.length === 0)
380
+ return null;
381
+ return positions.reduce((sum, value) => sum + value, 0) / positions.length;
382
+ }
383
+ function reorderLayerByBarycenter(ids, depths, neighbors, layerOrderIndex, fallbackOrderIndex, currentDepth, relation) {
384
+ return [...ids].sort((leftId, rightId) => {
385
+ const leftBarycenter = getNeighborBarycenter(leftId, depths, neighbors, layerOrderIndex, currentDepth, relation);
386
+ const rightBarycenter = getNeighborBarycenter(rightId, depths, neighbors, layerOrderIndex, currentDepth, relation);
387
+ if (leftBarycenter !== null
388
+ && rightBarycenter !== null
389
+ && leftBarycenter !== rightBarycenter) {
390
+ return leftBarycenter - rightBarycenter;
391
+ }
392
+ if (leftBarycenter !== null)
393
+ return -1;
394
+ if (rightBarycenter !== null)
395
+ return 1;
396
+ return (fallbackOrderIndex.get(leftId) ?? 0) - (fallbackOrderIndex.get(rightId) ?? 0);
397
+ });
398
+ }
399
+ function optimizeHierarchicalLayerOrder(layers, depths, incoming, outgoing, fallbackOrderIndex) {
400
+ const layerKeys = [...layers.keys()].sort((a, b) => a - b);
401
+ if (layerKeys.length <= 1)
402
+ return layers;
403
+ for (let sweep = 0; sweep < 4; sweep++) {
404
+ let layerOrderIndex = buildLayerOrderIndex(layers);
405
+ for (let index = 1; index < layerKeys.length; index++) {
406
+ const depth = layerKeys[index];
407
+ const ids = layers.get(depth) ?? [];
408
+ layers.set(depth, reorderLayerByBarycenter(ids, depths, incoming, layerOrderIndex, fallbackOrderIndex, depth, "before"));
409
+ layerOrderIndex = buildLayerOrderIndex(layers);
410
+ }
411
+ layerOrderIndex = buildLayerOrderIndex(layers);
412
+ for (let index = layerKeys.length - 2; index >= 0; index--) {
413
+ const depth = layerKeys[index];
414
+ const ids = layers.get(depth) ?? [];
415
+ layers.set(depth, reorderLayerByBarycenter(ids, depths, outgoing, layerOrderIndex, fallbackOrderIndex, depth, "after"));
416
+ layerOrderIndex = buildLayerOrderIndex(layers);
417
+ }
418
+ }
419
+ return layers;
420
+ }
356
421
  function layoutSequential(nodes, links, options = {}) {
357
422
  const gapX = options.gapX ?? 80;
358
423
  const gapY = options.gapY ?? 80;
@@ -387,12 +452,14 @@ function workerSource() {
387
452
  }
388
453
  return normalizeLayout(nodes, packDisconnectedComponents(nodes, links, positions, options), options);
389
454
  }
390
- function layoutHierarchical(nodes, links, options = {}, treeMode = false) {
455
+ function layoutHierarchical(nodes, links, options = {}, treeMode = false, respectViewport = false) {
456
+ const { outgoing, incoming } = buildGraphIndex(nodes, links);
391
457
  const gapX = options.gapX ?? 100;
392
458
  const gapY = options.gapY ?? 80;
393
- const direction = getPreferredDirection(options, treeMode ? "TB" : "LR");
459
+ const direction = getLayoutDirection(options, treeMode ? "TB" : "LR", respectViewport);
394
460
  const depths = getHierarchicalDepths(nodes, links, treeMode);
395
461
  const order = sortNodeIds(nodes);
462
+ const fallbackOrderIndex = new Map(order.map((id, index) => [id, index]));
396
463
  const nodeMap = new Map(nodes.map(node => [node.id, node]));
397
464
  const layers = new Map();
398
465
  for (const id of order) {
@@ -401,6 +468,7 @@ function workerSource() {
401
468
  group.push(id);
402
469
  layers.set(depth, group);
403
470
  }
471
+ optimizeHierarchicalLayerOrder(layers, depths, incoming, outgoing, fallbackOrderIndex);
404
472
  const layerKeys = [...layers.keys()].sort((a, b) => a - b);
405
473
  const positions = [];
406
474
  let primaryCursor = 0;
@@ -432,6 +500,130 @@ function workerSource() {
432
500
  adjusted = flipLayout(nodes, adjusted, "y");
433
501
  return normalizeLayout(nodes, packDisconnectedComponents(nodes, links, adjusted, options), options);
434
502
  }
503
+ function layoutTree(nodes, links, options = {}) {
504
+ const { outgoing, incoming, indegree } = buildGraphIndex(nodes, links);
505
+ const gapX = options.gapX ?? 100;
506
+ const gapY = options.gapY ?? 80;
507
+ const direction = getLayoutDirection(options, "TB", false);
508
+ const depths = getHierarchicalDepths(nodes, links, true);
509
+ const order = sortNodeIds(nodes);
510
+ const fallbackOrderIndex = new Map(order.map((id, index) => [id, index]));
511
+ const nodeMap = new Map(nodes.map(node => [node.id, node]));
512
+ const layers = new Map();
513
+ for (const id of order) {
514
+ const depth = depths.get(id) ?? 0;
515
+ const group = layers.get(depth) ?? [];
516
+ group.push(id);
517
+ layers.set(depth, group);
518
+ }
519
+ optimizeHierarchicalLayerOrder(layers, depths, incoming, outgoing, fallbackOrderIndex);
520
+ const layerOrderIndex = buildLayerOrderIndex(layers);
521
+ const roots = order.filter(id => (indegree.get(id) ?? 0) === 0);
522
+ const rootSet = new Set(roots);
523
+ const forestRoots = [];
524
+ const assigned = new Set();
525
+ const children = new Map();
526
+ const sortByTreeOrder = (leftId, rightId) => {
527
+ const leftDepth = depths.get(leftId) ?? 0;
528
+ const rightDepth = depths.get(rightId) ?? 0;
529
+ if (leftDepth !== rightDepth)
530
+ return leftDepth - rightDepth;
531
+ return (layerOrderIndex.get(leftId) ?? fallbackOrderIndex.get(leftId) ?? 0)
532
+ - (layerOrderIndex.get(rightId) ?? fallbackOrderIndex.get(rightId) ?? 0);
533
+ };
534
+ const buildTree = (nodeId) => {
535
+ const currentDepth = depths.get(nodeId) ?? 0;
536
+ const childIds = (outgoing.get(nodeId) ?? [])
537
+ .filter(childId => childId !== nodeId && (depths.get(childId) ?? currentDepth) > currentDepth && !assigned.has(childId))
538
+ .sort(sortByTreeOrder);
539
+ children.set(nodeId, childIds);
540
+ for (const childId of childIds) {
541
+ assigned.add(childId);
542
+ buildTree(childId);
543
+ }
544
+ };
545
+ for (const rootId of [...roots, ...order.filter(id => !rootSet.has(id))]) {
546
+ if (assigned.has(rootId))
547
+ continue;
548
+ assigned.add(rootId);
549
+ forestRoots.push(rootId);
550
+ buildTree(rootId);
551
+ }
552
+ const layerKeys = [...layers.keys()].sort((a, b) => a - b);
553
+ const primaryOffsetByDepth = new Map();
554
+ let primaryCursor = 0;
555
+ for (const layerKey of layerKeys) {
556
+ const ids = layers.get(layerKey) ?? [];
557
+ const primarySize = ids.reduce((max, id) => {
558
+ const node = nodeMap.get(id);
559
+ if (!node)
560
+ return max;
561
+ return Math.max(max, direction === "LR" || direction === "RL" ? node.width : node.height);
562
+ }, 0);
563
+ primaryOffsetByDepth.set(layerKey, primaryCursor);
564
+ primaryCursor += primarySize + (direction === "LR" || direction === "RL" ? gapX : gapY);
565
+ }
566
+ const secondaryGap = direction === "LR" || direction === "RL" ? gapY : gapX;
567
+ const secondarySize = (nodeId) => {
568
+ const node = nodeMap.get(nodeId);
569
+ if (!node)
570
+ return 0;
571
+ return direction === "LR" || direction === "RL" ? node.height : node.width;
572
+ };
573
+ const secondaryPositionById = new Map();
574
+ let secondaryCursor = 0;
575
+ const placeSubtree = (nodeId) => {
576
+ const node = nodeMap.get(nodeId);
577
+ if (!node) {
578
+ return { start: secondaryCursor, end: secondaryCursor };
579
+ }
580
+ const nodeSecondarySize = secondarySize(nodeId);
581
+ const childIds = children.get(nodeId) ?? [];
582
+ if (childIds.length === 0) {
583
+ const start = secondaryCursor;
584
+ secondaryPositionById.set(nodeId, start);
585
+ secondaryCursor += nodeSecondarySize + secondaryGap;
586
+ return { start, end: start + nodeSecondarySize };
587
+ }
588
+ let start = Infinity;
589
+ let end = -Infinity;
590
+ for (const childId of childIds) {
591
+ const bounds = placeSubtree(childId);
592
+ start = Math.min(start, bounds.start);
593
+ end = Math.max(end, bounds.end);
594
+ }
595
+ const centeredStart = start + (end - start - nodeSecondarySize) / 2;
596
+ secondaryPositionById.set(nodeId, centeredStart);
597
+ return {
598
+ start: Math.min(start, centeredStart),
599
+ end: Math.max(end, centeredStart + nodeSecondarySize),
600
+ };
601
+ };
602
+ for (const rootId of forestRoots) {
603
+ placeSubtree(rootId);
604
+ secondaryCursor += secondaryGap;
605
+ }
606
+ const positions = order
607
+ .map(id => {
608
+ const node = nodeMap.get(id);
609
+ if (!node)
610
+ return null;
611
+ const depth = depths.get(id) ?? 0;
612
+ const primary = primaryOffsetByDepth.get(depth) ?? 0;
613
+ const secondary = secondaryPositionById.get(id) ?? 0;
614
+ if (direction === "LR" || direction === "RL") {
615
+ return { id, x: primary, y: secondary, z: node.z };
616
+ }
617
+ return { id, x: secondary, y: primary, z: node.z };
618
+ })
619
+ .filter((position) => Boolean(position));
620
+ let adjusted = positions;
621
+ if (direction === "RL")
622
+ adjusted = flipLayout(nodes, adjusted, "x");
623
+ if (direction === "BT")
624
+ adjusted = flipLayout(nodes, adjusted, "y");
625
+ return normalizeLayout(nodes, packDisconnectedComponents(nodes, links, adjusted, options), options);
626
+ }
435
627
  function layoutRadial(nodes, links, options = {}) {
436
628
  const { outgoing, incoming, indegree, undirected } = buildGraphIndex(nodes, links);
437
629
  const order = sortNodeIds(nodes);
@@ -596,13 +788,135 @@ function workerSource() {
596
788
  }
597
789
  }
598
790
  }
791
+ function getMidpoint(from, to) {
792
+ return {
793
+ x: (from.x + to.x) / 2,
794
+ y: (from.y + to.y) / 2,
795
+ };
796
+ }
797
+ function getNormalizedVector(x, y) {
798
+ const length = Math.sqrt(x * x + y * y) || 1;
799
+ return { x: x / length, y: y / length };
800
+ }
801
+ function segmentsIntersect(leftFrom, leftTo, rightFrom, rightTo) {
802
+ const epsilon = 0.001;
803
+ const leftToRightFrom = (leftTo.x - leftFrom.x) * (rightFrom.y - leftFrom.y) - (leftTo.y - leftFrom.y) * (rightFrom.x - leftFrom.x);
804
+ const leftToRightTo = (leftTo.x - leftFrom.x) * (rightTo.y - leftFrom.y) - (leftTo.y - leftFrom.y) * (rightTo.x - leftFrom.x);
805
+ const rightToLeftFrom = (rightTo.x - rightFrom.x) * (leftFrom.y - rightFrom.y) - (rightTo.y - rightFrom.y) * (leftFrom.x - rightFrom.x);
806
+ const rightToLeftTo = (rightTo.x - rightFrom.x) * (leftTo.y - rightFrom.y) - (rightTo.y - rightFrom.y) * (leftTo.x - rightFrom.x);
807
+ return (((leftToRightFrom > epsilon && leftToRightTo < -epsilon) || (leftToRightFrom < -epsilon && leftToRightTo > epsilon))
808
+ && ((rightToLeftFrom > epsilon && rightToLeftTo < -epsilon) || (rightToLeftFrom < -epsilon && rightToLeftTo > epsilon)));
809
+ }
810
+ function applyLinkCrossingForces(edges, centers, displacement, strength, seedCenters) {
811
+ if (strength <= 0 || edges.length < 2)
812
+ return 0;
813
+ let crossingCount = 0;
814
+ for (let leftIndex = 0; leftIndex < edges.length; leftIndex++) {
815
+ const leftEdge = edges[leftIndex];
816
+ const leftFrom = centers[leftEdge.fromIndex];
817
+ const leftTo = centers[leftEdge.toIndex];
818
+ for (let rightIndex = leftIndex + 1; rightIndex < edges.length; rightIndex++) {
819
+ const rightEdge = edges[rightIndex];
820
+ if (leftEdge.fromIndex === rightEdge.fromIndex
821
+ || leftEdge.fromIndex === rightEdge.toIndex
822
+ || leftEdge.toIndex === rightEdge.fromIndex
823
+ || leftEdge.toIndex === rightEdge.toIndex) {
824
+ continue;
825
+ }
826
+ const rightFrom = centers[rightEdge.fromIndex];
827
+ const rightTo = centers[rightEdge.toIndex];
828
+ if (!segmentsIntersect(leftFrom, leftTo, rightFrom, rightTo))
829
+ continue;
830
+ crossingCount += 1;
831
+ const leftMid = getMidpoint(leftFrom, leftTo);
832
+ const rightMid = getMidpoint(rightFrom, rightTo);
833
+ let separationX = leftMid.x - rightMid.x;
834
+ let separationY = leftMid.y - rightMid.y;
835
+ const leftSeedFrom = seedCenters?.[leftEdge.fromIndex];
836
+ const leftSeedTo = seedCenters?.[leftEdge.toIndex];
837
+ const rightSeedFrom = seedCenters?.[rightEdge.fromIndex];
838
+ const rightSeedTo = seedCenters?.[rightEdge.toIndex];
839
+ if (leftSeedFrom && leftSeedTo && rightSeedFrom && rightSeedTo) {
840
+ const leftSeedMid = getMidpoint(leftSeedFrom, leftSeedTo);
841
+ const rightSeedMid = getMidpoint(rightSeedFrom, rightSeedTo);
842
+ separationX = leftSeedMid.x - rightSeedMid.x;
843
+ separationY = leftSeedMid.y - rightSeedMid.y;
844
+ }
845
+ if (Math.abs(separationX) < 0.001 && Math.abs(separationY) < 0.001) {
846
+ separationX = (leftFrom.x + leftTo.x) - (rightFrom.x + rightTo.x);
847
+ separationY = (leftFrom.y + leftTo.y) - (rightFrom.y + rightTo.y);
848
+ }
849
+ const direction = getNormalizedVector(separationX, separationY);
850
+ const pushX = direction.x * strength;
851
+ const pushY = direction.y * strength;
852
+ displacement[leftEdge.fromIndex].x += pushX * 0.5;
853
+ displacement[leftEdge.fromIndex].y += pushY * 0.5;
854
+ displacement[leftEdge.toIndex].x += pushX * 0.5;
855
+ displacement[leftEdge.toIndex].y += pushY * 0.5;
856
+ displacement[rightEdge.fromIndex].x -= pushX * 0.5;
857
+ displacement[rightEdge.fromIndex].y -= pushY * 0.5;
858
+ displacement[rightEdge.toIndex].x -= pushX * 0.5;
859
+ displacement[rightEdge.toIndex].y -= pushY * 0.5;
860
+ }
861
+ }
862
+ return crossingCount;
863
+ }
864
+ function resolveLinkCrossings(nodes, edges, centers, spacing, gapX, gapY, seedCenters) {
865
+ if (edges.length < 2)
866
+ return;
867
+ for (let iteration = 0; iteration < 20; iteration++) {
868
+ const displacement = nodes.map(() => ({ x: 0, y: 0 }));
869
+ const crossingCount = applyLinkCrossingForces(edges, centers, displacement, Math.max(spacing * 0.12, 6), seedCenters);
870
+ if (crossingCount === 0) {
871
+ return;
872
+ }
873
+ for (let index = 0; index < centers.length; index++) {
874
+ centers[index].x += displacement[index].x;
875
+ centers[index].y += displacement[index].y;
876
+ }
877
+ separateOverlappingNodes(nodes, centers, gapX, gapY);
878
+ }
879
+ }
599
880
  function runForceLayout(nodes, links, options = {}, directedBias = 0, gravity = 0.015, seedPositions) {
600
881
  const gapX = options.gapX ?? 140;
601
882
  const gapY = options.gapY ?? 100;
883
+ const averageNodeSize = nodes.reduce((sum, node) => sum + Math.max(node.width, node.height), 0) / Math.max(nodes.length, 1);
884
+ const baseSpacing = Math.max(gapX, gapY, averageNodeSize * 0.85, 48);
885
+ const overlapGapX = gapX > 0 ? gapX : Math.min(baseSpacing * 0.25, 24);
886
+ const overlapGapY = gapY > 0 ? gapY : Math.min(baseSpacing * 0.25, 24);
602
887
  const iterations = Math.max(1, Math.floor(options.iterations ?? 220));
603
888
  const nodeMap = new Map(nodes.map((node, index) => [node.id, { node, index }]));
889
+ const { outgoing, incoming } = buildGraphIndex(nodes, links);
890
+ const nodeDegrees = nodes.map(node => {
891
+ const degree = new Set([
892
+ ...(outgoing.get(node.id) ?? []),
893
+ ...(incoming.get(node.id) ?? []),
894
+ ]);
895
+ return degree.size;
896
+ });
897
+ const edges = links
898
+ .map(link => {
899
+ const fromEntry = nodeMap.get(link.from);
900
+ const toEntry = nodeMap.get(link.to);
901
+ if (!fromEntry || !toEntry)
902
+ return null;
903
+ return {
904
+ fromIndex: fromEntry.index,
905
+ toIndex: toEntry.index,
906
+ };
907
+ })
908
+ .filter((edge) => Boolean(edge));
604
909
  const nodeRadii = nodes.map(node => Math.sqrt(node.width * node.width + node.height * node.height) / 2);
605
910
  const seedPositionMap = new Map((seedPositions ?? []).map(position => [position.id, position]));
911
+ const seedCenters = nodes.map(node => {
912
+ const seed = seedPositionMap.get(node.id);
913
+ if (!seed)
914
+ return null;
915
+ return {
916
+ x: seed.x + node.width / 2,
917
+ y: seed.y + node.height / 2,
918
+ };
919
+ });
606
920
  const rawPositions = nodes.map(node => {
607
921
  const seed = seedPositionMap.get(node.id);
608
922
  return {
@@ -616,16 +930,17 @@ function workerSource() {
616
930
  const rawCenterX = rawBounds.left + rawBounds.width / 2;
617
931
  const rawCenterY = rawBounds.top + rawBounds.height / 2;
618
932
  const { scaleX, scaleY } = getViewportScales(options);
619
- const desiredSpanX = Math.max(gapX, gapY) * Math.max(2, Math.sqrt(Math.max(1, nodes.length))) * scaleX;
620
- const desiredSpanY = Math.max(gapX, gapY) * Math.max(2, Math.sqrt(Math.max(1, nodes.length))) * scaleY;
933
+ const desiredSpanX = baseSpacing * Math.max(2, Math.sqrt(Math.max(1, nodes.length))) * scaleX;
934
+ const desiredSpanY = baseSpacing * Math.max(2, Math.sqrt(Math.max(1, nodes.length))) * scaleY;
621
935
  const currentSpanX = Math.max(rawBounds.width, 1);
622
936
  const currentSpanY = Math.max(rawBounds.height, 1);
623
937
  const compactScaleX = Math.min(1, desiredSpanX / currentSpanX);
624
938
  const compactScaleY = Math.min(1, desiredSpanY / currentSpanY);
625
939
  const useCircularSeed = rawBounds.width < 4 && rawBounds.height < 4;
626
940
  const centers = nodes.map((node, index) => {
941
+ const rawPosition = rawPositions[index] ?? { x: node.x, y: node.y };
627
942
  if (useCircularSeed) {
628
- const seedRadius = Math.max(gapX, gapY) * 0.8;
943
+ const seedRadius = baseSpacing * 0.8;
629
944
  const angle = (Math.PI * 2 * index) / Math.max(1, nodes.length);
630
945
  return {
631
946
  x: Math.cos(angle) * seedRadius * scaleX,
@@ -633,14 +948,14 @@ function workerSource() {
633
948
  };
634
949
  }
635
950
  return {
636
- x: (node.x + node.width / 2 - rawCenterX) * compactScaleX,
637
- y: (node.y + node.height / 2 - rawCenterY) * compactScaleY,
951
+ x: (rawPosition.x + node.width / 2 - rawCenterX) * compactScaleX,
952
+ y: (rawPosition.y + node.height / 2 - rawCenterY) * compactScaleY,
638
953
  };
639
954
  });
640
955
  const totalNodeArea = nodes.reduce((sum, node) => sum + node.width * node.height, 0);
641
- const area = Math.max(totalNodeArea + gapX * gapY * Math.max(1, nodes.length) * 0.35, 1);
956
+ const area = Math.max(totalNodeArea + baseSpacing * baseSpacing * Math.max(1, nodes.length) * 0.35, 1);
642
957
  const k = Math.sqrt(area / Math.max(1, nodes.length));
643
- let temperature = Math.max(gapX, gapY, 60);
958
+ let temperature = Math.max(baseSpacing, 60);
644
959
  for (let iteration = 0; iteration < iterations; iteration++) {
645
960
  const displacement = nodes.map(() => ({ x: 0, y: 0 }));
646
961
  for (let i = 0; i < nodes.length; i++) {
@@ -653,7 +968,7 @@ function workerSource() {
653
968
  dy = (Math.random() - 0.5) * 0.01;
654
969
  distance = Math.sqrt(dx * dx + dy * dy);
655
970
  }
656
- const minimumDistance = nodeRadii[i] + nodeRadii[j] + Math.max(gapX, gapY) * 0.18;
971
+ const minimumDistance = nodeRadii[i] + nodeRadii[j] + baseSpacing * 0.18;
657
972
  const safeDistance = Math.max(distance - nodeRadii[i] - nodeRadii[j], 1);
658
973
  const force = (k * k) / (safeDistance * 1.5);
659
974
  const fx = (dx / distance) * force;
@@ -681,7 +996,7 @@ function workerSource() {
681
996
  let dx = centers[i].x - centers[j].x;
682
997
  let dy = centers[i].y - centers[j].y;
683
998
  let distance = Math.sqrt(dx * dx + dy * dy) || 1;
684
- const springLength = nodeRadii[i] + nodeRadii[j] + Math.max(gapX, gapY) * 0.28;
999
+ const springLength = nodeRadii[i] + nodeRadii[j] + baseSpacing * 0.28;
685
1000
  const force = (distance - springLength) * 0.2;
686
1001
  const fx = (dx / distance) * force;
687
1002
  const fy = (dy / distance) * force;
@@ -694,6 +1009,19 @@ function workerSource() {
694
1009
  displacement[j].x += directedBias;
695
1010
  }
696
1011
  }
1012
+ if (seedCenters.some(center => center !== null)) {
1013
+ const settlingFactor = 0.35 + (iteration / Math.max(iterations - 1, 1)) * 0.85;
1014
+ for (let i = 0; i < nodes.length; i++) {
1015
+ const seedCenter = seedCenters[i];
1016
+ if (!seedCenter)
1017
+ continue;
1018
+ const degree = nodeDegrees[i];
1019
+ const leafBias = degree <= 1 ? 1.6 : degree === 2 ? 1.15 : 0.9;
1020
+ const seedStrength = 0.04 * leafBias * settlingFactor;
1021
+ displacement[i].x += (seedCenter.x - centers[i].x) * seedStrength;
1022
+ displacement[i].y += (seedCenter.y - centers[i].y) * seedStrength;
1023
+ }
1024
+ }
697
1025
  for (let i = 0; i < nodes.length; i++) {
698
1026
  displacement[i].x -= centers[i].x * gravity;
699
1027
  displacement[i].y -= centers[i].y * gravity;
@@ -704,7 +1032,9 @@ function workerSource() {
704
1032
  }
705
1033
  temperature *= 0.96;
706
1034
  }
707
- separateOverlappingNodes(nodes, centers, gapX, gapY);
1035
+ separateOverlappingNodes(nodes, centers, overlapGapX, overlapGapY);
1036
+ resolveLinkCrossings(nodes, edges, centers, baseSpacing, overlapGapX, overlapGapY, seedCenters);
1037
+ separateOverlappingNodes(nodes, centers, overlapGapX, overlapGapY);
708
1038
  const positions = nodes.map((node, index) => ({
709
1039
  id: node.id,
710
1040
  x: centers[index].x - node.width / 2,
@@ -723,7 +1053,7 @@ function workerSource() {
723
1053
  case "radial":
724
1054
  return layoutRadial(nodes, links, options);
725
1055
  case "tree":
726
- return layoutHierarchical(nodes, links, options, true);
1056
+ return layoutTree(nodes, links, options);
727
1057
  case "structural":
728
1058
  return layoutHierarchical(nodes, links, options, false);
729
1059
  case "organic": {