@neo4j-nvl/base 1.1.0 → 1.2.0-090ec876

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 (60) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/dist/base.mjs +1 -1
  3. package/dist/types/index.d.ts +20 -1
  4. package/dist/types/layouts/d3forcelayout/__mocks__/d3ForceLayout.d.ts +10 -0
  5. package/dist/types/layouts/d3forcelayout/constants.d.ts +0 -1
  6. package/dist/types/layouts/d3forcelayout/d3ForceLayout.d.ts +20 -20
  7. package/dist/types/layouts/d3forcelayout/d3ForceLayout.test.d.ts +1 -0
  8. package/dist/types/layouts/d3forcelayout/types.d.ts +5 -6
  9. package/dist/types/layouts/forcedirectedlayout/physlayout/PhysLayout.d.ts +17 -13
  10. package/dist/types/layouts/forcedirectedlayout/physlayout/shaders/multilevel-fragment.d.ts +1 -1
  11. package/dist/types/layouts/forcedirectedlayout/physlayout/shaders/multilevel-repulsive-fragment.d.ts +1 -1
  12. package/dist/types/layouts/forcedirectedlayout/physlayout/shaders/multilevel-update-fragment.d.ts +1 -1
  13. package/dist/types/layouts/forcedirectedlayout/physlayout/shaders/sim-vertex.d.ts +1 -1
  14. package/dist/types/layouts/forcedirectedlayout/physlayout/solarmerger/SolarMerger.bench.d.ts +1 -0
  15. package/dist/types/layouts/gridLayout/GridLayout.d.ts +25 -9
  16. package/dist/types/modules/NvlController.d.ts +25 -4
  17. package/dist/types/modules/NvlController.test.d.ts +1 -0
  18. package/dist/types/modules/Shader.d.ts +1 -1
  19. package/dist/types/modules/state/types.d.ts +32 -13
  20. package/dist/types/renderers/domrenderer/BaseRenderer.d.ts +1 -1
  21. package/dist/types/renderers/domrenderer/BaseRenderer.test.d.ts +1 -0
  22. package/dist/types/renderers/domrenderer/canvasrenderer/CanvasRenderer.d.ts +2 -36
  23. package/dist/types/renderers/domrenderer/canvasrenderer/arrowDrawing.d.ts +168 -0
  24. package/dist/types/renderers/domrenderer/canvasrenderer/arrowDrawing.test.d.ts +1 -0
  25. package/dist/types/renderers/domrenderer/canvasrenderer/nodeDrawing.d.ts +109 -0
  26. package/dist/types/renderers/domrenderer/canvasrenderer/nodeDrawing.test.d.ts +1 -0
  27. package/dist/types/renderers/domrenderer/shared/ImageCache.d.ts +3 -2
  28. package/dist/types/renderers/domrenderer/shared/arrows/ArrowBundle.d.ts +18 -2
  29. package/dist/types/renderers/domrenderer/shared/arrows/ArrowBundler.d.ts +10 -0
  30. package/dist/types/renderers/domrenderer/shared/nodes/nodes.d.ts +53 -1
  31. package/dist/types/renderers/domrenderer/shared/util.d.ts +6 -1
  32. package/dist/types/renderers/domrenderer/shared/wordwrap.d.ts +12 -1
  33. package/dist/types/renderers/domrenderer/svgrenderer/SvgRenderer.d.ts +1 -3
  34. package/dist/types/renderers/domrenderer/svgrenderer/arrowDrawing.d.ts +59 -0
  35. package/dist/types/renderers/domrenderer/svgrenderer/arrowDrawing.test.d.ts +1 -0
  36. package/dist/types/renderers/domrenderer/svgrenderer/nodeDrawing.d.ts +16 -0
  37. package/dist/types/renderers/domrenderer/svgrenderer/nodeDrawing.test.d.ts +1 -0
  38. package/dist/types/renderers/domrenderer/svgrenderer/svgUtils.d.ts +33 -141
  39. package/dist/types/renderers/domrenderer/svgrenderer/svgUtils.test.d.ts +1 -0
  40. package/dist/types/renderers/webglrenderer/Renderer.d.ts +1 -2
  41. package/dist/types/renderers/webglrenderer/node-animation-fragment.d.ts +1 -1
  42. package/dist/types/renderers/webglrenderer/node-animation-vertex.d.ts +1 -1
  43. package/dist/types/renderers/webglrenderer/node-fragment-point.d.ts +1 -1
  44. package/dist/types/renderers/webglrenderer/node-vertex-point.d.ts +1 -1
  45. package/dist/types/renderers/webglrenderer/rel-fragment-line.d.ts +1 -1
  46. package/dist/types/renderers/webglrenderer/rel-vertex-line.d.ts +1 -1
  47. package/dist/types/renderers/webglrenderer/viewport-box-fragment.d.ts +1 -1
  48. package/dist/types/renderers/webglrenderer/viewport-box-vertex.d.ts +1 -1
  49. package/dist/types/utils/canvasManagement.d.ts +1 -1
  50. package/dist/types/utils/constants.d.ts +8 -2
  51. package/dist/types/utils/geometry.d.ts +4 -0
  52. package/dist/types/utils/layoutSeeding.d.ts +7 -0
  53. package/dist/types/utils/layoutSeeding.test.d.ts +1 -0
  54. package/dist/types/utils/segmentAnalytics.d.ts +2 -2
  55. package/package.json +8 -6
  56. package/dist/types/layouts/forcedirectedlayout/physlayout/shaders/multilevel-fragment-verlet.d.ts +0 -2
  57. package/dist/types/layouts/forcedirectedlayout/physlayout/shaders/multilevel-repulsive-fragment-verlet.d.ts +0 -2
  58. package/dist/types/layouts/forcedirectedlayout/physlayout/shaders/workaround-fragment.d.ts +0 -2
  59. package/dist/types/renderers/domrenderer/canvasrenderer/canvasUtils.d.ts +0 -56
  60. package/dist/types/renderers/domrenderer/shared/nodes/nodeUtils.d.ts +0 -29
@@ -1,7 +1,7 @@
1
1
  import type { ExternalCallbacks } from './modules/ExternalCallbackHandler';
2
2
  import type { CircularOptions, ForceDirectedOptions, HierarchicalOptions, Renderer as InternalRenderer, Layout, LayoutOptions, NvlOptions, NvlState, ZoomOptions } from './modules/state/types';
3
3
  import { CanvasRendererType, CircularLayoutType, ForceDirectedLayoutType, FreeLayoutType, GridLayoutType, HierarchicalLayoutType, SvgRendererType, WebGLRendererType, d3ForceLayoutType } from './modules/state/types';
4
- import { drawCircleBand } from './renderers/domrenderer/canvasrenderer/canvasUtils';
4
+ import { drawCircleBand } from './renderers/domrenderer/canvasrenderer/nodeDrawing';
5
5
  import type { StyledCaption } from './renderers/domrenderer/shared/types';
6
6
  import type { Node, PartialNode, PartialRelationship, Relationship } from './types/graph-element';
7
7
  import { CompatibilityError } from './utils/errors';
@@ -289,6 +289,25 @@ declare class NVL {
289
289
  getImageDataUrl(options?: {
290
290
  backgroundColor?: string;
291
291
  }): string;
292
+ /**
293
+ * Returns the entire graph visualization as an SVG data URL.
294
+ * @param options - Options for the SVG export
295
+ * @param options.backgroundColor - The background color of the SVG (defaults to transparent).
296
+ * @returns A Promise resolving to a data URL string representing the graph as SVG (data:image/svg+xml;...)
297
+ * @experimental
298
+ *
299
+ * @example
300
+ * ```typescript
301
+ * const svgDataUrl = await nvl.getSvgDataUrl({ backgroundColor: '#ffffff' })
302
+ * const img = document.createElement('img')
303
+ * img.src = svgDataUrl
304
+ * img.alt = 'Graph Visualization'
305
+ * document.body.appendChild(img)
306
+ * ```
307
+ */
308
+ getSvgDataUrl(options?: {
309
+ backgroundColor?: string;
310
+ }): Promise<string>;
292
311
  /**
293
312
  * Saves the entire graph visualization canvas as a png to the client.
294
313
  * @param {{ filename: string, backgroundColor: string }} options The filename and background color of the png.
@@ -0,0 +1,10 @@
1
+ export declare class D3ForceLayout {
2
+ update(): void;
3
+ getNodePositions(items: unknown[]): unknown[];
4
+ getShouldUpdate(): boolean;
5
+ getComputing(): boolean;
6
+ destroy(): void;
7
+ setOptions(): void;
8
+ updateNodes(): void;
9
+ terminateUpdate(): void;
10
+ }
@@ -12,7 +12,6 @@ export declare const BARNES_HUT_THETA = 1.2;
12
12
  export declare const FORCE_LINK_DISTANCE: (relationship: SimulationLinkDatum<D3Node>) => number;
13
13
  export declare const FORCE_LINK_STRENGTH: (link: SimulationLinkDatum<SimulationNodeDatum>, count: number[]) => number;
14
14
  export declare const FORCE_COLLIDE_RADIUS: (node: D3Node) => number;
15
- export declare const LAYOUT_RADIUS: (numberOfNodes: number) => number;
16
15
  export declare const FORCE_CHARGE: () => number;
17
16
  export declare const FORCE_CHARGE_START: () => number;
18
17
  export declare const FORCE_CENTER_X = 0.03;
@@ -1,33 +1,33 @@
1
- export class d3ForceLayout {
2
- constructor(config: any);
3
- state: any;
4
- d3Nodes: {};
5
- d3RelList: {};
1
+ import type { Simulation, SimulationLinkDatum, SimulationNodeDatum } from 'd3-force';
2
+ import type { NvlState } from '../../modules/state/types';
3
+ import type { Node, Relationship } from '../../types/graph-element';
4
+ import type { Point } from '../../utils/geometry';
5
+ import type { D3Node } from './types';
6
+ export declare class D3ForceLayout {
7
+ state: NvlState;
8
+ d3Nodes: Record<string, D3Node>;
9
+ d3RelList: SimulationLinkDatum<D3Node>[];
6
10
  computing: boolean;
7
- center: {
8
- x: number;
9
- y: number;
10
- };
11
- nodeRelCount: any[];
12
- simulation: import("d3-force").Simulation<import("d3-force").SimulationNodeDatum, undefined>;
11
+ center: Point;
12
+ nodeRelCount: number[];
13
+ simulation: Simulation<SimulationNodeDatum, undefined>;
13
14
  simulationStopped: boolean;
14
15
  shouldUpdate: boolean;
15
16
  shouldReheatNodes: boolean;
16
17
  shouldCountNodeRels: boolean;
17
- stateDisposers: any[];
18
+ stateDisposers: (() => void)[];
19
+ constructor(config: {
20
+ state: NvlState;
21
+ });
18
22
  setOptions(options: any): void;
19
- updateNodes(positionList: any): void;
23
+ updateNodes(positionList: D3Node[]): void;
20
24
  update(refreshPositions?: boolean): void;
21
- layout(_nodes: any, _rels: any, firstTimeAddingNodes: any): void;
22
- getNodePositions(nodeList: any): {
23
- id: any;
24
- x: any;
25
- y: any;
26
- }[];
25
+ layout(_nodes: Node[], _rels: Relationship[], firstTimeAddingNodes: boolean): void;
26
+ getNodePositions(nodeList: D3Node[]): any[];
27
27
  getShouldUpdate(): boolean;
28
28
  getComputing(): boolean;
29
29
  terminateUpdate(): void;
30
30
  destroy(): void;
31
- setAlpha(alpha: any): void;
31
+ setAlpha(alpha: number): void;
32
32
  countNodeRels(): any[];
33
33
  }
@@ -1,9 +1,8 @@
1
- export type D3Node = {
2
- x: number | undefined;
3
- y: number | undefined;
1
+ import type { Node } from '../../types/graph-element';
2
+ export type D3Node = Node & {
4
3
  vx?: number;
5
4
  vy?: number;
6
- fx?: number | null;
7
- fy?: number | null;
8
- size?: number;
5
+ fx?: number;
6
+ fy?: number;
7
+ index?: number;
9
8
  };
@@ -7,7 +7,6 @@ export declare class PhysLayout {
7
7
  private physProjection;
8
8
  private physSmallProjection;
9
9
  private gl;
10
- private useReadpixelWorkaround;
11
10
  private averageNodeSize;
12
11
  private shouldUpdate;
13
12
  private iterationCount;
@@ -19,7 +18,6 @@ export declare class PhysLayout {
19
18
  private stateDisposers;
20
19
  private state;
21
20
  private dpr;
22
- private intelWorkaround;
23
21
  private simulationStopVelocitySquared;
24
22
  private gravity;
25
23
  private force;
@@ -31,6 +29,9 @@ export declare class PhysLayout {
31
29
  private subGraphs;
32
30
  private nodeSortMap;
33
31
  private firstUpdate;
32
+ private precomputeMs;
33
+ private precomputeStarted;
34
+ private precomputing;
34
35
  private curPhysData;
35
36
  private apprxRepForceShader;
36
37
  private levelsClusterTexture;
@@ -40,26 +41,22 @@ export declare class PhysLayout {
40
41
  private collisionDetectionMultiplier;
41
42
  private physShader;
42
43
  private physData;
43
- private workaroundData;
44
44
  private pinData;
45
45
  private updateData;
46
46
  private updateShader;
47
- private workaroundShader;
48
47
  private physPositions;
49
48
  private springTexture;
50
49
  private sizeTexture;
51
50
  private offsetTexture;
52
51
  private pinTexture;
53
52
  private addedNodes;
53
+ private seedingMethod;
54
54
  private updateTexture;
55
- private vaoExt;
56
55
  private physVao;
57
56
  private physSmallVao;
58
57
  private updateVao;
59
- private workaroundVao;
60
- enableVerlet: boolean;
61
58
  constructor(config: ForceDirectedOptions & {
62
- webGLContext: WebGLRenderingContext;
59
+ webGLContext: WebGL2RenderingContext;
63
60
  state: NvlState;
64
61
  });
65
62
  /**
@@ -77,6 +74,17 @@ export declare class PhysLayout {
77
74
  nodes: Node[];
78
75
  rels: Relationship[];
79
76
  }): import("./solarmerger/types").CoarsenedGraph;
77
+ /**
78
+ * Synchronously runs physics simulation for the configured time budget before
79
+ * the layout is first rendered.
80
+ * Sets `getComputing()` to `true` while running and clears
81
+ * it when precompute finishes (including early exit when the layout halts).
82
+ */
83
+ private runPrecompute;
84
+ private isPrecomputeEnabled;
85
+ private hasPendingPrecompute;
86
+ private hasInteractingNodes;
87
+ private shouldRunPrecompute;
80
88
  /**
81
89
  * Updates the node's positions for the next step in the physics layout iterations if the layout needs to update
82
90
  * @param refreshPositions whether the entire layout should be reheated
@@ -93,9 +101,7 @@ export declare class PhysLayout {
93
101
  */
94
102
  getShouldUpdate(): boolean;
95
103
  /**
96
- * Returns whether the current layout is computing.
97
- * Always returns false because the force-directed layout has no computing step
98
- * @returns Whether the layout is computing.
104
+ * @returns Whether the layout is running the precompute step.
99
105
  */
100
106
  getComputing(): boolean;
101
107
  /**
@@ -169,7 +175,5 @@ export declare class PhysLayout {
169
175
  private setupPhysicsForCoarse;
170
176
  private setupPinData;
171
177
  private setupUpdates;
172
- private setupReadpixelWorkaround;
173
- private doReadpixelWorkaround;
174
178
  private definePhysicsArrays;
175
179
  }
@@ -1,2 +1,2 @@
1
- declare const _default: "precision mediump float;\n\nuniform sampler2D u_physData;\nuniform sampler2D u_connections;\nuniform sampler2D u_connectionOffsets;\nuniform sampler2D u_pinnedNodes;\nuniform sampler2D u_sizeTexture;\nuniform float u_baseLength;\nuniform float u_curIteration;\nuniform float u_iterationMultiplier;\nuniform vec2 u_gravityCenter;\nuniform float u_numNodes;\nuniform float u_gravity;\n\nuniform sampler2D u_clusterData;\nuniform sampler2D u_prevForce;\nuniform float u_collisionMultiplier;\n\nfloat TIMESTEP = 1.0 / 30.0;\nfloat VELOCITYDECAY = 0.6;\nfloat accLimitLow = 500000.0;\nfloat accLimitHigh = 750000.0;\nfloat accLimitPosHigh = 10000000.0;\n\nconst float denseNodeThreshold = 1000.0;\nconst float MIN_DISTANCE = 0.00000001;\nconst float MAX_DISTANCE = 1000000000.0;\nconst float MAX_ACCELERATION = 500000.0;\n\nvec4 getTextureData(sampler2D texture, float index, float base) {\n float x = mod(index, base);\n float y = (index - x) / base;\n return texture2D(texture, vec2(x + 0.5, y + 0.5) / base);\n}\n\nbool isNan(float val) {\n return (val < 0.0 || 0.0 < val || val == 0.0) ? false : true;\n}\n\nconst float BIG_NUMBER = 999999999999999999.0;\nbool isInf(float val) {\n return val > BIG_NUMBER || val < -BIG_NUMBER;\n}\n\nvec2 getDelta(vec2 v1, vec2 v2) {\n vec2 delta = v1 - v2;\n float dist = length(delta);\n if (dist < MIN_DISTANCE || isNan(dist)) {\n return vec2(MIN_DISTANCE, MIN_DISTANCE);\n }\n if (dist > MAX_DISTANCE) {\n vec2 normDelta = delta / dist;\n return normDelta * MAX_DISTANCE;\n }\n return delta;\n}\n\nvec4 getOtherNodePosition(float i) {\n return getTextureData(u_physData, i, 256.0);\n}\n\nfloat getCombinedNodeSize(float i, float nodeSize) {\n float otherNodeSize = getTextureData(u_sizeTexture, i, 256.0).a;\n return nodeSize + otherNodeSize;\n}\n\nvec2 getSpringForce(float curConnection, float numConnections, float springFScale, vec4 myPosition, float i) {\n float curSpring = getTextureData(u_connections, curConnection + i, 4096.0).a;\n\n vec4 otherPosition = getTextureData(u_physData, curSpring, 256.0);\n float otherDataPosition = getTextureData(u_connectionOffsets, curSpring, 256.0).a;\n float avgDegree;\n#if INTEL_WORKAROUND\n avgDegree = max(numConnections, 4.0);\n#else\n float otherNumConnections = getTextureData(u_connections, otherDataPosition, 4096.0).a;\n avgDegree = max((otherNumConnections + numConnections * 3.0) / 4.0, 4.0);\n#endif\n\n vec2 delta = getDelta(myPosition.xy, otherPosition.xy);\n float dist = length(delta);\n\n float F = (dist * dist * springFScale) / (u_baseLength * avgDegree);\n if (u_collisionMultiplier > 0.0) {\n F *= u_collisionMultiplier;\n }\n return (-delta / dist) * F;\n}\n\nvec2 getCollisionForce(float combinedNodeSize, float dist, float fScale, vec2 delta) {\n float collisionForce = ((combinedNodeSize - dist) / dist);\n if (u_collisionMultiplier > 0.0) {\n collisionForce *= u_collisionMultiplier;\n }\n return (delta * collisionForce * fScale * combinedNodeSize) / (combinedNodeSize + 1.0);\n}\n\nvec2 getRepulsionForce(float dist, float fScale, vec2 delta) {\n float F = (u_baseLength * u_baseLength * (fScale / 1.5)) / dist;\n return (delta / dist) * F * 0.05;\n}\n\nvoid main(void) {\n float textureSide = 256.0; //#TEXTURE_SIDE#;\n float index = (gl_FragCoord.x - 0.5) + (gl_FragCoord.y - 0.5) * textureSide;\n\n if (index >= u_numNodes) {\n discard;\n }\n\n vec4 clusterData = getTextureData(u_clusterData, index, 256.0);\n\n float clusterIndex = clusterData.x;\n float clusterStartIndex = clusterData.y;\n float clusterSize = clusterData.z;\n float clusterWeight = clusterData.w;\n\n vec4 myPosition = getTextureData(u_physData, index, 256.0);\n vec4 previousForce = getTextureData(u_prevForce, clusterIndex, 256.0);\n\n vec2 acceleration = previousForce.xy;\n\n float isPinned = getTextureData(u_pinnedNodes, index, 256.0).a;\n float nodeSize = getTextureData(u_sizeTexture, index, 256.0).a;\n\n if (isPinned > 0.5) {\n gl_FragColor = vec4(myPosition.xy, 0.0, 0.0);\n return;\n }\n\n float curConnection = getTextureData(u_connectionOffsets, index, 256.0).a;\n float numConnections = getTextureData(u_connections, curConnection, 4096.0).a;\n\n float fScale = 1.0 + sqrt(u_iterationMultiplier);\n float springFScale = fScale;\n\n if (numConnections > denseNodeThreshold) {\n springFScale = sqrt(fScale);\n }\n\n float numOfRels = 0.0;\n\n // Springs\n for (float i = 1.0; i <= 256.0 * 256.0; i++) {\n if (numOfRels >= numConnections) {\n break;\n }\n acceleration += getSpringForce(curConnection, numConnections, springFScale, myPosition, i);\n numOfRels += 1.0;\n }\n\n // Repulsion && collision detection\n if (u_collisionMultiplier > 0.0) {\n float number_of_collisions = 0.0;\n for (float i = 0.0; i < 256.0 * 256.0; i++) {\n if (i >= u_numNodes) {\n break;\n }\n\n if (i == index) {\n continue;\n }\n\n vec4 otherPosition = getOtherNodePosition(i);\n vec2 delta = getDelta(myPosition.xy, otherPosition.xy);\n float dist = length(delta);\n float combinedNodeSize = getCombinedNodeSize(i, nodeSize) * 2.0;\n\n if (dist < combinedNodeSize && number_of_collisions < 40.0) {\n number_of_collisions++;\n acceleration += getCollisionForce(combinedNodeSize, dist, fScale, delta);\n }\n\n if (i >= clusterStartIndex && i < clusterStartIndex + clusterSize) {\n acceleration += getRepulsionForce(dist, fScale, delta);\n }\n }\n } else {\n for (float i = 0.0; i < 256.0 * 256.0; i++) {\n if (i >= clusterStartIndex + clusterSize || i >= u_numNodes) {\n break;\n }\n\n if (i < clusterStartIndex || i == index) {\n continue;\n }\n\n vec4 otherPosition = getOtherNodePosition(i);\n vec2 delta = getDelta(myPosition.xy, otherPosition.xy);\n float dist = length(delta);\n float combinedNodeSize = getCombinedNodeSize(i, nodeSize);\n\n if (dist < combinedNodeSize) {\n acceleration += getCollisionForce(combinedNodeSize, dist, fScale, delta);\n }\n\n acceleration += getRepulsionForce(dist, fScale, delta);\n }\n }\n\n // Gravity\n vec2 delta = getDelta(u_gravityCenter, myPosition.xy);\n float dist = length(delta);\n\n vec2 grav = (delta / dist) * u_gravity * fScale;\n acceleration += grav * smoothstep(0.0, 500.0, dist);\n\n float accMagnitude = length(acceleration);\n acceleration *= min(MAX_ACCELERATION, accMagnitude) / accMagnitude;\n\n float iterationFrictionThreshold = 1000.0;\n\n if (u_curIteration > iterationFrictionThreshold) {\n float friction = 1.0 + pow((u_curIteration - iterationFrictionThreshold), 2.0) / 100.0;\n acceleration *= 1.0 / friction;\n }\n\n if (u_curIteration == 0.0) {\n gl_FragColor = vec4(myPosition.xy, acceleration * TIMESTEP * 0.5);\n } else {\n myPosition.zw = myPosition.zw * VELOCITYDECAY;\n gl_FragColor = vec4(myPosition.xy + myPosition.zw * TIMESTEP, myPosition.zw + acceleration * TIMESTEP);\n }\n}";
1
+ declare const _default: "#version 300 es\nprecision mediump float;\n\nuniform sampler2D u_physData;\nuniform sampler2D u_connections;\nuniform sampler2D u_connectionOffsets;\nuniform sampler2D u_pinnedNodes;\nuniform sampler2D u_sizeTexture;\nuniform float u_baseLength;\nuniform float u_curIteration;\nuniform float u_iterationMultiplier;\nuniform vec2 u_gravityCenter;\nuniform float u_numNodes;\nuniform float u_gravity;\n\nuniform sampler2D u_clusterData;\nuniform sampler2D u_prevForce;\nuniform float u_collisionMultiplier;\n\nout vec4 fragColor;\n\nfloat DAMP = 0.6;\nfloat COOL = 0.99998;\nfloat TIMESTEP = 1.0 / 30.0;\nfloat VELOCITYDECAY = 0.6;\nfloat accLimitLow = 500000.0;\nfloat accLimitHigh = 750000.0;\nfloat accLimitPosHigh = 10000000.0;\n\nconst float denseNodeThreshold = 1000.0;\nconst float MIN_DISTANCE = 0.00000001;\nconst float MAX_DISTANCE = 1000000000.0;\nconst float MAX_ACCELERATION = 50000.0;\n\nvec4 getTextureData(sampler2D tex, float index, float base) {\n float x = mod(index, base);\n float y = (index - x) / base;\n return texture(tex, vec2(x + 0.5, y + 0.5) / base);\n}\n\nbool isNan(float val) {\n return (val < 0.0 || 0.0 < val || val == 0.0) ? false : true;\n}\n\nconst float BIG_NUMBER = 999999999999999999.0;\nbool isInf(float val) {\n return val > BIG_NUMBER || val < -BIG_NUMBER;\n}\n\nvec2 getDelta(vec2 v1, vec2 v2) {\n vec2 delta = v1 - v2;\n float dist = length(delta);\n if (dist < MIN_DISTANCE || isNan(dist)) {\n return vec2(MIN_DISTANCE, MIN_DISTANCE);\n }\n if (dist > MAX_DISTANCE) {\n vec2 normDelta = delta / dist;\n return normDelta * MAX_DISTANCE;\n }\n return delta;\n}\n\nvec4 getOtherNodePosition(float i) {\n return getTextureData(u_physData, i, 256.0);\n}\n\nfloat getCombinedNodeSize(float i, float nodeSize) {\n float otherNodeSize = getTextureData(u_sizeTexture, i, 256.0).r;\n return nodeSize + otherNodeSize;\n}\n\nvec2 getSpringForce(float curConnection, float numConnections, float springFScale, vec4 myPosition, float i) {\n float curSpring = getTextureData(u_connections, curConnection + i, 4096.0).r;\n\n vec4 otherPosition = getTextureData(u_physData, curSpring, 256.0);\n float otherDataPosition = getTextureData(u_connectionOffsets, curSpring, 256.0).r;\n float otherNumConnections = getTextureData(u_connections, otherDataPosition, 4096.0).r;\n float avgDegree = max((otherNumConnections + numConnections * 3.0) / 4.0, 4.0);\n\n vec2 delta = getDelta(myPosition.xy, otherPosition.xy);\n float dist = length(delta);\n\n float F = (dist * dist * springFScale) / (u_baseLength * avgDegree);\n if (u_collisionMultiplier > 0.0) {\n F *= u_collisionMultiplier;\n }\n return (-delta / dist) * F;\n}\n\nvec2 getCollisionForce(float combinedNodeSize, float dist, float fScale, vec2 delta) {\n float collisionForce = (combinedNodeSize - dist) / (dist);\n if (u_collisionMultiplier > 0.0) {\n collisionForce *= u_collisionMultiplier;\n }\n return (delta * collisionForce * fScale * combinedNodeSize) / (combinedNodeSize + 1.0);\n}\n\nvec2 getRepulsionForce(float dist, float fScale, vec2 delta) {\n float F = (u_baseLength * u_baseLength * fScale) / (dist);\n return (delta / dist) * F * 0.05;\n}\n\nvoid main(void) {\n float textureSide = 256.0; //#TEXTURE_SIDE#;\n float index = (gl_FragCoord.x - 0.5) + (gl_FragCoord.y - 0.5) * textureSide;\n\n if (index >= u_numNodes) {\n discard;\n }\n\n vec4 clusterData = getTextureData(u_clusterData, index, 256.0);\n\n float clusterIndex = clusterData.x;\n float clusterStartIndex = clusterData.y;\n float clusterSize = clusterData.z;\n float clusterWeight = clusterData.w;\n\n vec4 myPosition = getTextureData(u_physData, index, 256.0);\n vec4 previousForce = getTextureData(u_prevForce, clusterIndex, 256.0);\n\n vec2 acceleration = previousForce.xy;\n\n float isPinned = getTextureData(u_pinnedNodes, index, 256.0).r;\n float nodeSize = getTextureData(u_sizeTexture, index, 256.0).r;\n\n if (isPinned > 0.5) {\n fragColor = vec4(myPosition.xy, 0.0, 0.0);\n return;\n }\n\n float curConnection = getTextureData(u_connectionOffsets, index, 256.0).r;\n float numConnections = getTextureData(u_connections, curConnection, 4096.0).r;\n\n float fScale = 1.0 + sqrt(u_iterationMultiplier);\n float springFScale = fScale;\n\n if (numConnections > denseNodeThreshold) {\n springFScale = sqrt(fScale);\n }\n\n float numOfRels = 0.0;\n\n // Springs\n for (float i = 1.0; i <= 256.0 * 256.0; i++) {\n if (numOfRels >= numConnections) {\n break;\n }\n acceleration += getSpringForce(curConnection, numConnections, springFScale, myPosition, i);\n numOfRels += 1.0;\n }\n\n // Repulsion && collision detection\n if (u_collisionMultiplier > 0.0) {\n float number_of_collisions = 0.0;\n for (float i = 0.0; i < 256.0 * 256.0; i++) {\n if (i >= u_numNodes) {\n break;\n }\n\n if (i == index) {\n continue;\n }\n\n vec4 otherPosition = getOtherNodePosition(i);\n vec2 delta = getDelta(myPosition.xy, otherPosition.xy);\n float dist = length(delta);\n float combinedNodeSize = getCombinedNodeSize(i, nodeSize) * 2.0;\n\n if (dist < combinedNodeSize && number_of_collisions < 40.0) {\n number_of_collisions++;\n acceleration += getCollisionForce(combinedNodeSize, dist, fScale, delta);\n }\n\n if (i >= clusterStartIndex && i < clusterStartIndex + clusterSize) {\n acceleration += getRepulsionForce(dist, fScale, delta);\n }\n }\n } else {\n for (float i = 0.0; i < 256.0 * 256.0; i++) {\n if (i >= clusterStartIndex + clusterSize || i >= u_numNodes) {\n break;\n }\n\n if (i < clusterStartIndex || i == index) {\n continue;\n }\n\n vec4 otherPosition = getOtherNodePosition(i);\n vec2 delta = getDelta(myPosition.xy, otherPosition.xy);\n float dist = length(delta);\n float combinedNodeSize = getCombinedNodeSize(i, nodeSize);\n\n if (dist < combinedNodeSize) {\n acceleration += getCollisionForce(combinedNodeSize, dist, fScale, delta);\n }\n\n acceleration += getRepulsionForce(dist, fScale, delta);\n }\n }\n\n // Gravity\n vec2 delta = getDelta(u_gravityCenter, myPosition.xy);\n float dist = length(delta);\n\n vec2 grav = (delta / dist) * u_gravity * fScale * (dist / 1000.0);\n acceleration += grav;\n\n float accMagnitude = length(acceleration);\n acceleration *= min(MAX_ACCELERATION, accMagnitude) / accMagnitude;\n\n float iterationFrictionThreshold = 1000.0;\n\n if (u_curIteration > iterationFrictionThreshold) {\n float friction = 1.0 + pow((u_curIteration - iterationFrictionThreshold), 2.0) / 100.0;\n acceleration *= 1.0 / friction;\n }\n\n if (u_curIteration == 0.0) {\n fragColor = vec4(myPosition.xy, myPosition.zw);\n } else {\n float TEMP = max(0.02, pow(COOL, u_curIteration));\n vec2 prevVelocity = myPosition.zw;\n vec2 currentPos = myPosition.xy;\n\n vec2 newPos = currentPos + TEMP * ( DAMP * (prevVelocity) + acceleration * TIMESTEP * TIMESTEP);\n vec2 newVelocity = newPos - currentPos;\n fragColor = vec4(newPos, newVelocity);\n }\n}";
2
2
  export default _default;
@@ -1,2 +1,2 @@
1
- declare const _default: "precision mediump float;\nuniform sampler2D u_physData;\nuniform sampler2D u_clusterData;\nuniform sampler2D u_finestIndexes;\nuniform sampler2D u_prevForce;\n\nuniform float u_baseLength;\nuniform float u_numNodes;\nuniform float u_iterationMultiplier;\nuniform float u_isTopLevel;\n\nfloat TIMESTEP = 1.0 / 30.0;\nfloat VELOCITYDECAY = 0.6;\n\nvec4 getTextureData(sampler2D texture, float index, float base) {\n float x = mod(index, base);\n float y = (index - x) / base;\n return texture2D(texture, vec2(x + 0.5, y + 0.5) / base);\n}\n\nfloat getLogClusterWeight(float value) {\n return value / max(log(value), 1.0);\n}\n\nvoid main(void) {\n float index = (gl_FragCoord.x - 0.5) + (gl_FragCoord.y - 0.5) * 256.0;\n\n if (index >= u_numNodes) {\n discard;\n }\n\n vec4 clusterData = getTextureData(u_clusterData, index, 256.0);\n\n float clusterIndex = clusterData.x;\n float clusterStartIndex = clusterData.y;\n float clusterSize = clusterData.z;\n\n float finestIndex = getTextureData(u_finestIndexes, index, 256.0).a;\n vec4 myPosition = getTextureData(u_physData, finestIndex, 256.0);\n vec4 previousForce = getTextureData(u_prevForce, clusterIndex, 256.0);\n float fScale = 1.0 + sqrt(u_iterationMultiplier);\n\n vec2 acceleration = previousForce.xy;\n\n // Repulsion & Collision Detection\n for (float i = 0.0; i < 256.0 * 256.0; i++) {\n if (i >= clusterStartIndex + clusterSize || i >= u_numNodes) {\n break;\n }\n if (i < clusterStartIndex || i == index) {\n continue;\n }\n\n vec4 otherClusterData = getTextureData(u_clusterData, i, 256.0);\n float otherClusterWeight = getLogClusterWeight(otherClusterData.w);\n\n float otherFinestIndex = getTextureData(u_finestIndexes, i, 256.0).a;\n vec4 otherPosition = getTextureData(u_physData, otherFinestIndex, 256.0);\n\n vec2 delta = myPosition.xy - otherPosition.xy;\n float dist = max(length(delta), 0.0000001);\n float maxDist = 25.0;\n float repulsionForceScale = 0.1;\n\n float F = ((u_baseLength * u_baseLength) * (fScale / 1.5)) / dist;\n\n if (u_isTopLevel == 1.0) {\n repulsionForceScale = 0.3;\n }\n\n acceleration += ((delta / dist) * F) * repulsionForceScale * otherClusterWeight;\n\n if (dist < maxDist) {\n float collide = (maxDist - dist) / dist;\n acceleration += delta * collide * fScale * maxDist / (maxDist + 1.0);\n }\n }\n\n gl_FragColor = vec4(acceleration, vec2(finestIndex, 0));\n}";
1
+ declare const _default: "#version 300 es\nprecision mediump float;\nuniform sampler2D u_physData;\nuniform sampler2D u_clusterData;\nuniform sampler2D u_finestIndexes;\nuniform sampler2D u_prevForce;\n\nuniform float u_baseLength;\nuniform float u_numNodes;\nuniform float u_iterationMultiplier;\nuniform float u_isTopLevel;\n\nout vec4 fragColor;\n\nfloat PI = 3.1415926535897932384626433832795;\nfloat TIMESTEP = 1.0 / 30.0;\nfloat VELOCITYDECAY = 1.0;\n\nvec4 getTextureData(sampler2D tex, float index, float base) {\n float x = mod(index, base);\n float y = (index - x) / base;\n return texture(tex, vec2(x + 0.5, y + 0.5) / base);\n}\n\nfloat getSquaredLogClusterWeight(float value) {\n return pow(log(value), 2.0);\n}\n\nvoid main(void) {\n float index = (gl_FragCoord.x - 0.5) + (gl_FragCoord.y - 0.5) * 256.0;\n\n if (index >= u_numNodes) {\n discard;\n }\n\n vec4 clusterData = getTextureData(u_clusterData, index, 256.0);\n\n float clusterIndex = clusterData.x;\n float clusterStartIndex = clusterData.y;\n float clusterSize = clusterData.z;\n float clusterArea = pow(clusterSize + u_baseLength * 2.0, 2.0);\n\n float finestIndex = getTextureData(u_finestIndexes, index, 256.0).r;\n vec4 myPosition = getTextureData(u_physData, finestIndex, 256.0);\n vec4 previousForce = getTextureData(u_prevForce, clusterIndex, 256.0);\n float fScale = 1.0 + sqrt(u_iterationMultiplier);\n\n vec2 acceleration = previousForce.xy;\n\n // Repulsion & Collision Detection\n for (float i = 0.0; i < 256.0 * 256.0; i++) {\n if (i >= clusterStartIndex + clusterSize || i >= u_numNodes) {\n break;\n }\n if (i < clusterStartIndex || i == index) {\n continue;\n }\n\n vec4 otherClusterData = getTextureData(u_clusterData, i, 256.0);\n float otherClusterWeight = getSquaredLogClusterWeight(otherClusterData.w);\n\n float otherFinestIndex = getTextureData(u_finestIndexes, i, 256.0).r;\n vec4 otherPosition = getTextureData(u_physData, otherFinestIndex, 256.0);\n\n vec2 delta = myPosition.xy - otherPosition.xy;\n float dist = max(length(delta), 0.0000001);\n float maxDist = 25.0;\n float repulsionForceScale = 0.1;\n\n float F = (clusterArea * fScale) / (dist * dist);\n\n if (u_isTopLevel == 1.0) {\n repulsionForceScale = 0.2;\n }\n\n acceleration += ((delta / sqrt(dist)) * F) * repulsionForceScale * otherClusterWeight;\n\n if (dist < maxDist) {\n float collide = (maxDist - dist) / (dist * dist);\n acceleration += delta * collide * fScale * maxDist / (maxDist + 1.0);\n }\n }\n\n fragColor = vec4(acceleration, vec2(finestIndex, 0));\n}";
2
2
  export default _default;
@@ -1,2 +1,2 @@
1
- declare const _default: "precision mediump float;\n\nuniform sampler2D u_physData;\nuniform sampler2D u_updateData;\nuniform float u_numNodesNew;\n\nconst float INDEX_FLAG = 999999.0;\n\nvec4 getTextureData(sampler2D texture, float index, float base) {\n float x = mod(index, base);\n float y = (index - x) / base;\n return texture2D(texture, vec2(x + 0.5, y + 0.5) / base);\n}\n\nvoid main(void) {\n float textureSide = 256.0; //#TEXTURE_SIDE#;\n float index = (gl_FragCoord.x - 0.5) + (gl_FragCoord.y - 0.5)*textureSide;\n\n if (index >= u_numNodesNew) {\n discard;\n }\n\n float firstValue = getTextureData(u_updateData, index * 2.0, 256.0).a;\n float secondValue = getTextureData(u_updateData, index * 2.0 + 1.0, 256.0).a;\n\n if (secondValue == INDEX_FLAG) {\n vec4 myPosition = getTextureData(u_physData, firstValue, 256.0);\n gl_FragColor = myPosition;\n } else {\n gl_FragColor = vec4(firstValue, secondValue, 0.0, 0.0);\n }\n}\n";
1
+ declare const _default: "#version 300 es\nprecision mediump float;\n\nuniform sampler2D u_physData;\nuniform sampler2D u_updateData;\nuniform float u_numNodesNew;\n\nout vec4 fragColor;\n\nconst float INDEX_FLAG = 999999.0;\n\nvec4 getTextureData(sampler2D tex, float index, float base) {\n float x = mod(index, base);\n float y = (index - x) / base;\n return texture(tex, vec2(x + 0.5, y + 0.5) / base);\n}\n\nvoid main(void) {\n float textureSide = 256.0; //#TEXTURE_SIDE#;\n float index = (gl_FragCoord.x - 0.5) + (gl_FragCoord.y - 0.5)*textureSide;\n\n if (index >= u_numNodesNew) {\n discard;\n }\n\n float firstValue = getTextureData(u_updateData, index * 2.0, 256.0).r;\n float secondValue = getTextureData(u_updateData, index * 2.0 + 1.0, 256.0).r;\n\n if (secondValue == INDEX_FLAG) {\n vec4 myPosition = getTextureData(u_physData, firstValue, 256.0);\n fragColor = myPosition;\n } else {\n fragColor = vec4(firstValue, secondValue, 0.0, 0.0);\n }\n}\n";
2
2
  export default _default;
@@ -1,2 +1,2 @@
1
- declare const _default: "uniform mat4 u_projection;\n\nattribute vec2 a_position;\n//attribute float a_index;\n\n//varying highp float index;\n\nvoid main() {\n// index = a_index;\n gl_Position = u_projection * vec4(a_position, 0.0, 1.0);\n}";
1
+ declare const _default: "#version 300 es\nuniform mat4 u_projection;\n\nin vec2 a_position;\n//in float a_index;\n\n//out highp float index;\n\nvoid main() {\n// index = a_index;\n gl_Position = u_projection * vec4(a_position, 0.0, 1.0);\n}";
2
2
  export default _default;
@@ -1,14 +1,30 @@
1
- export class GridLayout {
2
- constructor(config: any);
3
- state: any;
4
- positions: {};
5
- stateDisposers: any[];
1
+ import type { NvlState } from '../../modules/state/types';
2
+ import type { Node } from '../../types/graph-element';
3
+ import type { Point } from '../../utils/geometry';
4
+ export declare class GridLayout {
5
+ state: NvlState;
6
+ positions: Record<string, Node & Point>;
7
+ stateDisposers: (() => void)[];
6
8
  shouldUpdate: boolean;
7
- setOptions(options: any): void;
8
- updateNodes(positionList: any): void;
9
+ constructor(config: {
10
+ state: NvlState;
11
+ });
12
+ setOptions(): void;
13
+ updateNodes(positionList: {
14
+ id: string;
15
+ x: number;
16
+ y: number;
17
+ }[]): void;
9
18
  update(refreshPositions?: boolean): void;
10
- layout(mobxNodes: any, nodeIds: any, idToPosition: any, rels: any): void;
11
- getNodePositions(nodeList: any): any[];
19
+ layout(mobxNodes: any): void;
20
+ setNodePositions(idToPosition: Record<string, Node & Point>): void;
21
+ getNodePositions(nodeList: {
22
+ id: string;
23
+ }[]): {
24
+ id: string;
25
+ x: number;
26
+ y: number;
27
+ }[];
12
28
  getShouldUpdate(): boolean;
13
29
  getComputing(): boolean;
14
30
  terminateUpdate(): void;
@@ -1,4 +1,4 @@
1
- import type { Node, Relationship } from '../types/graph-element';
1
+ import { type Node, type PartialNode, type PartialRelationship, type Relationship } from '../types/graph-element';
2
2
  import '../types/nvl-window-functions';
3
3
  import type { Point } from '../utils/geometry';
4
4
  import type { ExternalCallbacks } from './ExternalCallbackHandler';
@@ -37,7 +37,7 @@ export default class NvlController {
37
37
  private readonly c2dCanvas;
38
38
  private readonly svg;
39
39
  private isInRenderSwitchAnimation;
40
- private justSwitchedRenderer;
40
+ private previousRenderer;
41
41
  private justSwitchedLayout;
42
42
  private layoutUpdating;
43
43
  private layoutComputing;
@@ -46,12 +46,16 @@ export default class NvlController {
46
46
  private readonly stateDisposers;
47
47
  private readonly zoomTransitionHandler;
48
48
  private currentLayout;
49
+ private lastTimestamp;
50
+ private accumulator;
49
51
  private readonly layoutTimeLimit;
52
+ private readonly smoothFrameBudgetMs;
50
53
  private pixelRatio;
51
54
  private readonly removeResizeListener;
52
55
  private readonly removeMinimapResizeListener;
53
- private pendingZoomOperation;
54
- private layoutRunner;
56
+ private pendingZoom;
57
+ private pendingPan;
58
+ private pendingViewportOverride;
55
59
  private animationRequestId;
56
60
  private layoutDoneCallback;
57
61
  private layoutComputingCallback;
@@ -59,8 +63,13 @@ export default class NvlController {
59
63
  private descriptionElement;
60
64
  constructor(state: NvlState, frame: HTMLElement, options: NvlOptions);
61
65
  private onWebGLContextLost;
66
+ private fpsToFrameBudgetMs;
67
+ private getLayoutNeedsToUpdate;
68
+ private isWithinBudget;
62
69
  private updateMinimapZoom;
63
70
  private startMainLoop;
71
+ private renderNodeHtmlOverlays;
72
+ private renderRelationshipHtmlOverlays;
64
73
  updateHtmlOverlay(): void;
65
74
  dumpNodes(): Node[];
66
75
  dumpRelationships(): Relationship[];
@@ -116,10 +125,22 @@ export default class NvlController {
116
125
  private initiateFileDownload;
117
126
  private updateLayoutAndPositions;
118
127
  saveToFile(options: SaveToFileOptions): void;
128
+ private createSvgString;
119
129
  saveToSvg(options?: SaveToFileOptions): Promise<void>;
120
130
  getImageDataURL(options: SaveToFileOptions): string;
131
+ getSvgDataUrl(options?: SaveToFileOptions): Promise<string>;
121
132
  private prepareLargeFileForDownload;
122
133
  private createCanvasAndRenderImage;
123
134
  saveFullGraphToLargeFile(options: SaveToFileOptions): Promise<void>;
135
+ addAndUpdateElementsInGraph(nodes?: Node[] | PartialNode[], relationships?: Relationship[] | PartialRelationship[]): void;
136
+ updateElementsInGraph(nodes: Node[] | PartialNode[], relationships: Relationship[] | PartialRelationship[]): void;
137
+ /**
138
+ * Adds nodes and relationships in the current scene.
139
+ * @param {Node[]} nodes The nodes to be added.
140
+ * @param {Relationship[]} relationships The relationships to be added.
141
+ */
142
+ addElementsToGraph(nodes: Node[], relationships: Relationship[]): void;
143
+ private validateNodes;
144
+ private validateRelationships;
124
145
  }
125
146
  export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -4,7 +4,7 @@ export default class Shader {
4
4
  private curTexture;
5
5
  private attributeInfo;
6
6
  private uniformInfo;
7
- constructor(gl: WebGLRenderingContext, vsSource: string, fsSource: string, defines?: {});
7
+ constructor(gl: WebGL2RenderingContext, vsSource: string, fsSource: string, defines?: {});
8
8
  setUniform(name: string, value: any): void;
9
9
  setAttributePointer(name: string, size: number, start: number, stride: number): void;
10
10
  setAttributePointerFloat(name: string, size: number, start: number, stride: number): void;
@@ -38,25 +38,31 @@ export type DisabledItemStyles = {
38
38
  */
39
39
  export interface ForceDirectedOptions {
40
40
  /**
41
- * Enables a workaround for Intel GPU compatibility issues in WebGL shader compilation.
42
- * When enabled, this flag modifies how physics simulation shaders are compiled to avoid
43
- * rendering problems on Intel graphics hardware.
44
- *
45
- * @remarks Requires a restart of NVL as shaders need to be recompiled.
41
+ * @deprecated No longer has any effect. The physics layout now uses WebGL2 which does not
42
+ * require the Intel GPU shader workaround. This option will be removed in a future release.
46
43
  */
47
44
  intelWorkaround?: boolean;
48
45
  /**
46
+ * @deprecated This option will be removed in a future release. For small datasets, use the d3Force layout instead.
49
47
  * Whether to enable automatic switching to CoseBilkent layout for small graphs.
50
48
  * When enabled, small graphs will automatically use the CoseBilkent layout algorithm
51
49
  * which can provide better initial positioning for smaller datasets.
50
+ * @defaultValue true
52
51
  */
53
52
  enableCytoscape?: boolean;
54
53
  /**
55
- * Wether to use the new physics engine instead of the legacy one.
56
- * @defaultValue true
57
- * @internal
54
+ * How initial node positions are seeded before the physics simulation runs.
55
+ * @defaultValue 'box'
58
56
  */
59
- enableVerlet?: boolean;
57
+ seedingMethod?: 'box' | 'circle';
58
+ /**
59
+ * @experimental
60
+ * Milliseconds of synchronous physics simulation to run before the layout is first rendered.
61
+ * Precompute stops early if the simulation stabilizes before the time budget is reached.
62
+ * @defaultValue 0
63
+ * @note Blocks the main thread, so use sparingly. Will be capped at 1000.
64
+ */
65
+ precomputeMs?: number;
60
66
  }
61
67
  /**
62
68
  * The options for the hierarchical layout
@@ -231,11 +237,24 @@ export interface NvlOptions {
231
237
  minimapViewportBoxColor?: string;
232
238
  };
233
239
  /**
234
- * @internal
235
- * @hidden
236
- * Defines a time limit for how long layout iterations may run
240
+ * Target minimum presentation rate (fps) while layout is active.
241
+ * Rendering will not drop below this rate at the cost of stabilization speed.
242
+ *
243
+ * @defaultValue 3
244
+ *
245
+ * @note Must be within range of 1 and 120.
246
+ */
247
+ minFps?: number;
248
+ /**
249
+ * Target maximum presentation rate (fps) while layout is active.
250
+ * Rendering will not go above with rate. Any additional
251
+ * computation available is used to speed up layout stabilization.
252
+ *
253
+ * @defaultValue 30
254
+ *
255
+ * @note Must be within range of 1 and 120.
237
256
  */
238
- layoutTimeLimit?: number;
257
+ maxFps?: number;
239
258
  /**
240
259
  * Disables tracking of library usage
241
260
  * @defaultValue false
@@ -31,7 +31,7 @@ export default abstract class BaseRenderer {
31
31
  * @param clientHeight - Height of the rendering area
32
32
  * @returns Array of relationships to render, sorted (disabled, normal, selected)
33
33
  */
34
- protected getRelationshipsToRender(showCaptions: boolean, zoom?: number, clientWidth?: number, clientHeight?: number): RelationshipToRender[];
34
+ protected getRelationshipsToRender(showCaptions?: boolean, zoom?: number, clientWidth?: number, clientHeight?: number): RelationshipToRender[];
35
35
  /**
36
36
  * Gets nodes to render, sorted by their rendering order
37
37
  * @param positionArray - Array of nodes with positions
@@ -2,7 +2,8 @@ import type { NvlState } from '../../../modules/state/types';
2
2
  import type { Node } from '../../../types/graph-element';
3
3
  import type { Point } from '../../../utils/geometry';
4
4
  import type { HitTargetNode, HitTargetRelationship } from '../../../utils/hittest';
5
- import BaseRenderer, { RendererOptions } from '../BaseRenderer';
5
+ import type { RendererOptions } from '../BaseRenderer';
6
+ import BaseRenderer from '../BaseRenderer';
6
7
  export default class CanvasRenderer extends BaseRenderer {
7
8
  private canvas;
8
9
  private context;
@@ -30,13 +31,6 @@ export default class CanvasRenderer extends BaseRenderer {
30
31
  * Draws a single node on the canvas with all its visual elements (circle, icons, captions, etc.)
31
32
  * @param ctx - The canvas rendering context
32
33
  * @param node - The node to draw
33
- * @param imageCache - The image cache for icons
34
- * @param animationHandler - The animation handler for transitions
35
- * @param nodeBorderStyles - The border styles for nodes
36
- * @param disabledItemStyles - The styles for disabled items
37
- * @param defaultNodeColor - The default node color
38
- * @param ellipsisWidth - The width of the ellipsis character
39
- * @param zoomLevel - The current zoom level
40
34
  */
41
35
  private drawNode;
42
36
  /**
@@ -47,18 +41,6 @@ export default class CanvasRenderer extends BaseRenderer {
47
41
  * Helper method to disable shadow on canvas context
48
42
  */
49
43
  private disableShadow;
50
- /**
51
- * Draws line segments on canvas (straight or curved)
52
- */
53
- private drawSegments;
54
- /**
55
- * Draws a self-referencing loop on canvas
56
- */
57
- private drawLoop;
58
- /**
59
- * Draws a label for a relationship
60
- */
61
- private drawLabel;
62
44
  /**
63
45
  * Renders a waypoint arrow (relationship between two different nodes)
64
46
  */
@@ -67,10 +49,6 @@ export default class CanvasRenderer extends BaseRenderer {
67
49
  * Renders a self-referencing arrow (relationship from node to itself)
68
50
  */
69
51
  private renderSelfArrow;
70
- /**
71
- * Main arrow rendering method that delegates to waypoint or self-arrow rendering
72
- */
73
- private renderArrow;
74
52
  /**
75
53
  * Draws the nodes and relationships to their positions on the canvas.
76
54
  * @param positionArray {Node[]} - The array of nodes to draw
@@ -99,18 +77,6 @@ export default class CanvasRenderer extends BaseRenderer {
99
77
  * @todo: sort relationships by distance descending
100
78
  */
101
79
  getRelsAt(pointer: Point): HitTargetRelationship[];
102
- /**
103
- * Returns an array of styles for the rings of a node.
104
- * It takes the node as an argument, and an optional array of size animations.
105
- * If the node is selected, it returns the selectedStyle.rings array.
106
- * If the node is not selected, it returns an array containing only one object with
107
- * a width of 0 and an empty color.
108
- * @param node - The node to get the ring styles for.
109
- * @param animationHandler - The animation handler.
110
- * @param nodeBorderStyles - The node border styles.
111
- * @returns An array of ring styles.
112
- */
113
- private getRingStyles;
114
80
  /**
115
81
  * Handles zoom and pan on the canvas.
116
82
  * @param context {CanvasRenderingContext2D} - The canvas context