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.
- package/dist/behaviour/move-behaviour.d.ts +50 -0
- package/dist/behaviour/move-behaviour.d.ts.map +1 -0
- package/dist/behaviour/move-behaviour.js +71 -0
- package/dist/behaviour/move-behaviour.js.map +1 -0
- package/dist/calculations/index.d.ts.map +1 -1
- package/dist/calculations/index.js +344 -14
- package/dist/calculations/index.js.map +1 -1
- package/dist/context/connection-context.d.ts.map +1 -1
- package/dist/context/connection-context.js.map +1 -1
- package/dist/context/error-context.d.ts.map +1 -1
- package/dist/context/error-context.js.map +1 -1
- package/dist/context/graph-context.d.ts.map +1 -1
- package/dist/context/graph-context.js.map +1 -1
- package/dist/context/graph-event-bus-context.d.ts +4 -0
- package/dist/context/graph-event-bus-context.d.ts.map +1 -0
- package/dist/context/graph-event-bus-context.js +8 -0
- package/dist/context/graph-event-bus-context.js.map +1 -0
- package/dist/context/graph-root-context.d.ts.map +1 -1
- package/dist/context/graph-root-context.js.map +1 -1
- package/dist/context/link-info-context.d.ts +4 -0
- package/dist/context/link-info-context.d.ts.map +1 -0
- package/dist/context/link-info-context.js +15 -0
- package/dist/context/link-info-context.js.map +1 -0
- package/dist/context/node-event-context.d.ts +4 -0
- package/dist/context/node-event-context.d.ts.map +1 -0
- package/dist/context/node-event-context.js +10 -0
- package/dist/context/node-event-context.js.map +1 -0
- package/dist/context/node-registry-context.d.ts +4 -0
- package/dist/context/node-registry-context.d.ts.map +1 -0
- package/dist/context/node-registry-context.js +13 -0
- package/dist/context/node-registry-context.js.map +1 -0
- package/dist/graph/index.d.ts +2 -2
- package/dist/graph/index.d.ts.map +1 -1
- package/dist/graph/index.js +256 -48
- package/dist/graph/index.js.map +1 -1
- package/dist/hooks/connection.d.ts +1 -1
- package/dist/hooks/graph-event-bus.d.ts +3 -0
- package/dist/hooks/graph-event-bus.d.ts.map +1 -0
- package/dist/hooks/graph-event-bus.js +7 -0
- package/dist/hooks/graph-event-bus.js.map +1 -0
- package/dist/hooks/link-info.d.ts +4 -0
- package/dist/hooks/link-info.d.ts.map +1 -0
- package/dist/hooks/link-info.js +7 -0
- package/dist/hooks/link-info.js.map +1 -0
- package/dist/hooks/node-registry.d.ts +3 -0
- package/dist/hooks/node-registry.d.ts.map +1 -0
- package/dist/hooks/node-registry.js +7 -0
- package/dist/hooks/node-registry.js.map +1 -0
- package/dist/hooks/nodeEvent.d.ts +7 -0
- package/dist/hooks/nodeEvent.d.ts.map +1 -0
- package/dist/hooks/nodeEvent.js +17 -0
- package/dist/hooks/nodeEvent.js.map +1 -0
- package/dist/hooks/use-graph-api.d.ts +50 -0
- package/dist/hooks/use-graph-api.d.ts.map +1 -0
- package/dist/hooks/use-graph-api.js +97 -0
- package/dist/hooks/use-graph-api.js.map +1 -0
- package/dist/link/base.d.ts +9 -20
- package/dist/link/base.d.ts.map +1 -1
- package/dist/link/base.js +17 -290
- package/dist/link/base.js.map +1 -1
- package/dist/link/temp-link.d.ts.map +1 -1
- package/dist/link/temp-link.js +18 -3
- package/dist/link/temp-link.js.map +1 -1
- package/dist/module.d.ts +9 -0
- package/dist/module.d.ts.map +1 -1
- package/dist/module.js +9 -0
- package/dist/module.js.map +1 -1
- package/dist/nodes/base.d.ts +2 -1
- package/dist/nodes/base.d.ts.map +1 -1
- package/dist/nodes/base.js +61 -89
- package/dist/nodes/base.js.map +1 -1
- package/dist/paths/bidirectional-path.d.ts +16 -0
- package/dist/paths/bidirectional-path.d.ts.map +1 -0
- package/dist/paths/bidirectional-path.js +260 -0
- package/dist/paths/bidirectional-path.js.map +1 -0
- package/dist/ports/base.d.ts.map +1 -1
- package/dist/ports/base.js +26 -13
- package/dist/ports/base.js.map +1 -1
- package/dist/providers/connection-provider.d.ts +1 -1
- package/dist/providers/connection-provider.d.ts.map +1 -1
- package/dist/providers/connection-provider.js +30 -16
- package/dist/providers/connection-provider.js.map +1 -1
- package/dist/providers/graph-event-bus-provider.d.ts +11 -0
- package/dist/providers/graph-event-bus-provider.d.ts.map +1 -0
- package/dist/providers/graph-event-bus-provider.js +46 -0
- package/dist/providers/graph-event-bus-provider.js.map +1 -0
- package/dist/providers/link-info-provider.d.ts +23 -0
- package/dist/providers/link-info-provider.d.ts.map +1 -0
- package/dist/providers/link-info-provider.js +31 -0
- package/dist/providers/link-info-provider.js.map +1 -0
- package/dist/providers/node-event-context.d.ts +3 -0
- package/dist/providers/node-event-context.d.ts.map +1 -0
- package/dist/providers/node-event-context.js +81 -0
- package/dist/providers/node-event-context.js.map +1 -0
- package/dist/providers/node-registry-provider.d.ts +11 -0
- package/dist/providers/node-registry-provider.d.ts.map +1 -0
- package/dist/providers/node-registry-provider.js +54 -0
- package/dist/providers/node-registry-provider.js.map +1 -0
- package/dist/style.css +1 -0
- package/dist/types.d.ts +206 -38
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/proxy.d.ts +2 -0
- package/dist/utils/proxy.d.ts.map +1 -0
- package/dist/utils/proxy.js +18 -0
- package/dist/utils/proxy.js.map +1 -0
- package/dist/utils/symbols.d.ts +2 -0
- package/dist/utils/symbols.d.ts.map +1 -0
- package/dist/utils/symbols.js +2 -0
- package/dist/utils/symbols.js.map +1 -0
- 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":"AAAA,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC;AAsCvD;;;;GAIG;AACH,MAAM,UAAU,gBAAgB,CAAC,EAC7B,UAAU,EACV,OAAO,EACP,IAAI,EACJ,QAAQ,EACR,SAAS,EACT,QAAQ,EACR,YAAY,GACU;IACtB,MAAM,OAAO,GAAG,MAAM,CAAY;QAC9B,MAAM,EAAE,KAAK;QACb,cAAc,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE;QAC9B,QAAQ,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE;QACxB,UAAU,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE;KAC7B,CAAC,CAAC;IAEH,MAAM,eAAe,GAAG,WAAW,CAAC,CAAC,CAAgC,EAAE,EAAE;QACrE,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,IAAI,IAAI,KAAK,UAAU;YAAE,OAAO;QAClD,CAAC,CAAC,cAAc,EAAE,CAAC;QACnB,OAAO,CAAC,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;QAC9B,OAAO,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC;QAC7C,OAAO,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC;QAC7C,OAAO,CAAC,OAAO,CAAC,QAAQ,GAAG,EAAE,CAAC,EAAE,QAAQ,CAAC,CAAC,EAAE,CAAC,EAAE,QAAQ,CAAC,CAAC,EAAE,CAAC;QAC5D,OAAO,CAAC,OAAO,CAAC,UAAU,GAAG,EAAE,CAAC,EAAE,QAAQ,CAAC,CAAC,EAAE,CAAC,EAAE,QAAQ,CAAC,CAAC,EAAE,CAAC;IAClE,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC;IAEnC,MAAM,aAAa,GAAG,WAAW,CAAC,CAAC,CAAgC,EAAE,EAAE;QACnE,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,IAAI,OAAO,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC;YAC3C,OAAO,CAAC,OAAO,CAAC,MAAM,GAAG,KAAK,CAAC;YAC/B,MAAM,YAAY,GAAY;gBAC1B,CAAC,EAAE,OAAO,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;gBAC/B,CAAC,EAAE,OAAO,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;gBAC/B,CAAC,EAAE,QAAQ,CAAC,CAAC;aAChB,CAAC;YACF,SAAS,EAAE,CAAC,YAAY,CAAC,CAAC;YAC1B,YAAY,EAAE,CAAC,MAAM,EAAE,EAAE,QAAQ,EAAE,YAAY,EAAE,CAAC,CAAC;QACvD,CAAC;IACL,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC,EAAE,SAAS,EAAE,YAAY,CAAC,CAAC,CAAC;IAE1C,MAAM,eAAe,GAAG,WAAW,CAAC,CAAC,CAAa,EAAE,EAAE;QAClD,IAAI,CAAC,UAAU,CAAC,OAAO,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM;YAAE,OAAO;QAC3D,MAAM,IAAI,GAAG,OAAO,EAAE,CAAC;QACvB,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;QACjE,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;QACjE,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,GAAG,EAAE,CAAC;QAC7C,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,GAAG,EAAE,CAAC;QAC7C,MAAM,YAAY,GAAY;YAC1B,CAAC,EAAE,IAAI;YACP,CAAC,EAAE,IAAI;YACP,CAAC,EAAE,QAAQ,CAAC,CAAC;SAChB,CAAC;QACF,OAAO,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,GAAG,IAAI,CAAC;QACpC,OAAO,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,GAAG,IAAI,CAAC;QACpC,UAAU,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC;QACvD,UAAU,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC;QACtD,QAAQ,EAAE,CAAC,YAAY,CAAC,CAAC;QACzB,YAAY,EAAE,CAAC,MAAM,EAAE,EAAE,QAAQ,EAAE,YAAY,EAAE,CAAC,CAAC;IACvD,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC,EAAE,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,YAAY,CAAC,CAAC,CAAC;IAE9D,oDAAoD;IACpD,SAAS,CAAC,GAAG,EAAE;QACX,IAAI,IAAI,KAAK,UAAU;YAAE,OAAO;QAEhC,MAAM,IAAI,GAAG,CAAC,CAAa,EAAE,EAAE,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC;QACjD,MAAM,MAAM,GAAG,CAAC,CAAa,EAAE,EAAE,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;QAErD,QAAQ,CAAC,gBAAgB,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;QAC3C,QAAQ,CAAC,gBAAgB,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;QAE/C,OAAO,GAAG,EAAE;YACR,QAAQ,CAAC,mBAAmB,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;YAC9C,QAAQ,CAAC,mBAAmB,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;QACtD,CAAC,CAAC;IACN,CAAC,EAAE,CAAC,aAAa,EAAE,eAAe,EAAE,IAAI,CAAC,CAAC,CAAC;IAE3C,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,OAAO,EAAE,CAAC;AACvD,CAAC"}
|
|
@@ -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;
|
|
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
|
|
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 =
|
|
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 =
|
|
620
|
-
const desiredSpanY =
|
|
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 =
|
|
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: (
|
|
637
|
-
y: (
|
|
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 +
|
|
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(
|
|
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] +
|
|
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] +
|
|
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,
|
|
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
|
|
1056
|
+
return layoutTree(nodes, links, options);
|
|
727
1057
|
case "structural":
|
|
728
1058
|
return layoutHierarchical(nodes, links, options, false);
|
|
729
1059
|
case "organic": {
|