@neo4j-nvl/base 1.1.0 → 1.2.0

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 (43) 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/d3ForceLayout.d.ts +20 -20
  6. package/dist/types/layouts/d3forcelayout/d3ForceLayout.test.d.ts +1 -0
  7. package/dist/types/layouts/d3forcelayout/types.d.ts +5 -6
  8. package/dist/types/layouts/forcedirectedlayout/physlayout/PhysLayout.d.ts +0 -1
  9. package/dist/types/layouts/forcedirectedlayout/physlayout/shaders/multilevel-fragment.d.ts +1 -1
  10. package/dist/types/layouts/forcedirectedlayout/physlayout/shaders/multilevel-repulsive-fragment.d.ts +1 -1
  11. package/dist/types/layouts/forcedirectedlayout/physlayout/solarmerger/SolarMerger.bench.d.ts +1 -0
  12. package/dist/types/layouts/gridLayout/GridLayout.d.ts +25 -9
  13. package/dist/types/modules/NvlController.d.ts +17 -3
  14. package/dist/types/modules/NvlController.test.d.ts +1 -0
  15. package/dist/types/modules/state/types.d.ts +2 -6
  16. package/dist/types/renderers/domrenderer/BaseRenderer.d.ts +1 -1
  17. package/dist/types/renderers/domrenderer/BaseRenderer.test.d.ts +1 -0
  18. package/dist/types/renderers/domrenderer/canvasrenderer/CanvasRenderer.d.ts +2 -36
  19. package/dist/types/renderers/domrenderer/canvasrenderer/arrowDrawing.d.ts +168 -0
  20. package/dist/types/renderers/domrenderer/canvasrenderer/arrowDrawing.test.d.ts +1 -0
  21. package/dist/types/renderers/domrenderer/canvasrenderer/nodeDrawing.d.ts +109 -0
  22. package/dist/types/renderers/domrenderer/canvasrenderer/nodeDrawing.test.d.ts +1 -0
  23. package/dist/types/renderers/domrenderer/shared/ImageCache.d.ts +3 -2
  24. package/dist/types/renderers/domrenderer/shared/arrows/ArrowBundle.d.ts +18 -2
  25. package/dist/types/renderers/domrenderer/shared/arrows/ArrowBundler.d.ts +10 -0
  26. package/dist/types/renderers/domrenderer/shared/nodes/nodes.d.ts +53 -1
  27. package/dist/types/renderers/domrenderer/shared/util.d.ts +6 -1
  28. package/dist/types/renderers/domrenderer/shared/wordwrap.d.ts +12 -1
  29. package/dist/types/renderers/domrenderer/svgrenderer/SvgRenderer.d.ts +1 -3
  30. package/dist/types/renderers/domrenderer/svgrenderer/arrowDrawing.d.ts +59 -0
  31. package/dist/types/renderers/domrenderer/svgrenderer/arrowDrawing.test.d.ts +1 -0
  32. package/dist/types/renderers/domrenderer/svgrenderer/nodeDrawing.d.ts +16 -0
  33. package/dist/types/renderers/domrenderer/svgrenderer/nodeDrawing.test.d.ts +1 -0
  34. package/dist/types/renderers/domrenderer/svgrenderer/svgUtils.d.ts +33 -141
  35. package/dist/types/renderers/domrenderer/svgrenderer/svgUtils.test.d.ts +1 -0
  36. package/dist/types/utils/constants.d.ts +3 -2
  37. package/dist/types/utils/geometry.d.ts +4 -0
  38. package/dist/types/utils/segmentAnalytics.d.ts +2 -2
  39. package/package.json +6 -5
  40. package/dist/types/layouts/forcedirectedlayout/physlayout/shaders/multilevel-fragment-verlet.d.ts +0 -2
  41. package/dist/types/layouts/forcedirectedlayout/physlayout/shaders/multilevel-repulsive-fragment-verlet.d.ts +0 -2
  42. package/dist/types/renderers/domrenderer/canvasrenderer/canvasUtils.d.ts +0 -56
  43. 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
+ }
@@ -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
  };
@@ -57,7 +57,6 @@ export declare class PhysLayout {
57
57
  private physSmallVao;
58
58
  private updateVao;
59
59
  private workaroundVao;
60
- enableVerlet: boolean;
61
60
  constructor(config: ForceDirectedOptions & {
62
61
  webGLContext: WebGLRenderingContext;
63
62
  state: NvlState;
@@ -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: "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\n\nfloat DAMP = 0.6;\nfloat COOL = 0.99998;\nfloat TEMP = max(0.02, pow(COOL, u_curIteration));\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 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) / (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 * (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 gl_FragColor = vec4(myPosition.xy, myPosition.zw);\n } else {\n vec2 prevVelocity = myPosition.zw;\n vec2 currentPos = myPosition.xy;\n vec2 prevPos = currentPos - prevVelocity;\n\n vec2 newPos = currentPos + TEMP * ( DAMP * (prevVelocity) + acceleration * TIMESTEP * TIMESTEP);\n vec2 newVelocity = newPos - currentPos;\n gl_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: "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 PI = 3.1415926535897932384626433832795;\nfloat TIMESTEP = 1.0 / 30.0;\nfloat VELOCITYDECAY = 1.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\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).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 = getSquaredLogClusterWeight(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 = (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 gl_FragColor = vec4(acceleration, vec2(finestIndex, 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;
@@ -50,7 +50,9 @@ export default class NvlController {
50
50
  private pixelRatio;
51
51
  private readonly removeResizeListener;
52
52
  private readonly removeMinimapResizeListener;
53
- private pendingZoomOperation;
53
+ private pendingZoom;
54
+ private pendingPan;
55
+ private pendingViewportOverride;
54
56
  private layoutRunner;
55
57
  private animationRequestId;
56
58
  private layoutDoneCallback;
@@ -116,10 +118,22 @@ export default class NvlController {
116
118
  private initiateFileDownload;
117
119
  private updateLayoutAndPositions;
118
120
  saveToFile(options: SaveToFileOptions): void;
121
+ private createSvgString;
119
122
  saveToSvg(options?: SaveToFileOptions): Promise<void>;
120
123
  getImageDataURL(options: SaveToFileOptions): string;
124
+ getSvgDataUrl(options?: SaveToFileOptions): Promise<string>;
121
125
  private prepareLargeFileForDownload;
122
126
  private createCanvasAndRenderImage;
123
127
  saveFullGraphToLargeFile(options: SaveToFileOptions): Promise<void>;
128
+ addAndUpdateElementsInGraph(nodes?: Node[] | PartialNode[], relationships?: Relationship[] | PartialRelationship[]): void;
129
+ updateElementsInGraph(nodes: Node[] | PartialNode[], relationships: Relationship[] | PartialRelationship[]): void;
130
+ /**
131
+ * Adds nodes and relationships in the current scene.
132
+ * @param {Node[]} nodes The nodes to be added.
133
+ * @param {Relationship[]} relationships The relationships to be added.
134
+ */
135
+ addElementsToGraph(nodes: Node[], relationships: Relationship[]): void;
136
+ private validateNodes;
137
+ private validateRelationships;
124
138
  }
125
139
  export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -46,17 +46,13 @@ export interface ForceDirectedOptions {
46
46
  */
47
47
  intelWorkaround?: boolean;
48
48
  /**
49
+ * @deprecated This option will be removed in a future release. For small datasets, use the d3Force layout instead.
49
50
  * Whether to enable automatic switching to CoseBilkent layout for small graphs.
50
51
  * When enabled, small graphs will automatically use the CoseBilkent layout algorithm
51
52
  * which can provide better initial positioning for smaller datasets.
52
- */
53
- enableCytoscape?: boolean;
54
- /**
55
- * Wether to use the new physics engine instead of the legacy one.
56
53
  * @defaultValue true
57
- * @internal
58
54
  */
59
- enableVerlet?: boolean;
55
+ enableCytoscape?: boolean;
60
56
  }
61
57
  /**
62
58
  * The options for the hierarchical layout
@@ -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
@@ -0,0 +1,168 @@
1
+ import type { BorderStyle, DisabledItemStyles } from '../../../modules/state/types';
2
+ import type { Relationship } from '../../../types/graph-element';
3
+ import type { Point } from '../../../utils/geometry';
4
+ import type ArrowBundle from '../shared/arrows/ArrowBundle';
5
+ /**
6
+ * Calculate label dimensions including font size, font weight, and truncated text.
7
+ *
8
+ * @param fullCaption - The complete caption text
9
+ * @param relWidth - The relationship width (may be undefined)
10
+ * @param selected - Whether the relationship is selected (affects font weight and width calculation)
11
+ * @param captionSize - The caption size multiplier (default 1)
12
+ * @param defaultRelWidth - The default relationship width to use if relWidth is undefined
13
+ * @param measureWidth - Function to measure text width given a text string
14
+ * @param maxWidth - The maximum width available for the label in pixels
15
+ * @returns LabelDimensions with calculated font metrics and label text
16
+ */
17
+ export declare const calculateLabelDimensions: (fullCaption: string, measureWidth: (text: string) => number, maxWidth: number) => {
18
+ label: string;
19
+ width: number;
20
+ };
21
+ /**
22
+ * Calculate label transform including rotation and flip adjustments.
23
+ *
24
+ * @param angle - The base rotation angle in radians
25
+ * @param fontSize - The font size in pixels (including pixel ratio scaling)
26
+ * @param flip - Whether to flip the label (applies additional rotation)
27
+ * @param point - The base point (used to calculate offset in flip case)
28
+ * @returns object with final position and angle
29
+ */
30
+ export declare const calculateLabelTransform: (angle: number, fontSize: number, flip: boolean, point: Point) => {
31
+ finalX: number;
32
+ finalY: number;
33
+ finalAngle: number;
34
+ };
35
+ /**
36
+ * Calculate vertical alignment offset for label based on caption alignment.
37
+ *
38
+ * @param captionAlign - The caption alignment: 'top' or 'bottom'
39
+ * @param fontSize - The font size in pixels (including pixel ratio scaling)
40
+ * @param lineWidth - The relationship line width in pixels
41
+ * @param padding - The padding around the label in pixels
42
+ * @returns The vertical offset in pixels to apply when drawing the label
43
+ */
44
+ export declare const calculateLabelHeightAlign: (captionAlign: string, fontSize: number, lineWidth: number, padding: number) => number;
45
+ /**
46
+ * Calculate final label geometry for hit detection.
47
+ *
48
+ * @param point - The original point (before transform)
49
+ * @param angle - The rotation angle in radians
50
+ * @param maxWidth - The maximum width of the label in pixels
51
+ * @param fontSize - The font size in pixels (including pixel ratio scaling)
52
+ * @param heightAlign - The vertical alignment offset in pixels
53
+ * @param padding - The padding around the label in pixels
54
+ * @param pixelRatio - The device pixel ratio
55
+ * @param flip - Whether the label is flipped
56
+ * @returns object with position, rotation, width, and height for hit detection
57
+ */
58
+ export declare const calculateLabelGeometry: (point: Point, angle: number, maxWidth: number, fontSize: number, heightAlign: number, padding: number, pixelRatio: number, flip: boolean) => {
59
+ position: {
60
+ x: number;
61
+ y: number;
62
+ };
63
+ rotation: number;
64
+ width: number;
65
+ height: number;
66
+ };
67
+ /**
68
+ * Determines the rendering state flags for a relationship.
69
+ *
70
+ * @param rel - The relationship to analyze
71
+ * @param disableArrowShadow - Global flag to disable arrow shadows
72
+ * @param showLabel - Whether the relationship label is visible
73
+ * @returns Object containing rendering state flags
74
+ */
75
+ export declare const determineArrowRenderingState: (rel: Relationship, disableArrowShadow: boolean, showLabel: boolean) => {
76
+ isSelected: boolean;
77
+ isDisabled: boolean;
78
+ showOverlayIcon: boolean;
79
+ shouldRenderShadow: boolean;
80
+ shouldShowHoverEffect: boolean;
81
+ };
82
+ /**
83
+ * Selects relationship colors based on rendering state.
84
+ *
85
+ * @param rel - The relationship to get colors for
86
+ * @param selectedBorderStyle - The selected border style containing ring colors
87
+ * @param disabledItemStyles - Styling for disabled items
88
+ * @param defaultColor - Default color to use if none is specified
89
+ * @returns Object containing colors for different rendering layers
90
+ */
91
+ export declare const selectArrowColors: (rel: Relationship, selectedBorderStyle: BorderStyle, disabledItemStyles: DisabledItemStyles, defaultColor: string) => {
92
+ mainColor: string;
93
+ selectedColor1: string;
94
+ selectedColor2: string;
95
+ hoverColor: string;
96
+ };
97
+ /**
98
+ * Calculates arrowhead adjustment for last segment truncation.
99
+ *
100
+ * @param lastSegmentDistance - Distance between the last two points
101
+ * @param headHeight - Height of the arrowhead
102
+ * @param headPositionOffset - Base position offset for the arrowhead (HeadChinHeight - headSelectedAdjustment)
103
+ * @param headSelectedAdjustment - Additional adjustment for selected state
104
+ * @param headChinHeight - Height of the arrowhead's chin (base)
105
+ * @param isSelected - Whether the arrow is selected
106
+ * @returns Object containing adjusted parameters and truncation flag
107
+ */
108
+ export declare const calculateArrowheadAdjustment: (lastSegmentDistance: number, headHeight: number, headPositionOffset: number, headSelectedAdjustment: number, headChinHeight: number, isSelected: boolean) => {
109
+ adjustedHeadPositionOffset: number;
110
+ adjustedHeadSelectedAdjustment: number;
111
+ shouldTruncateLastSegment: boolean;
112
+ };
113
+ /**
114
+ * The parameters for drawing an arrowhead.
115
+ */
116
+ type ArrowHeadParameters = {
117
+ headPosition: Point;
118
+ headAngle: number;
119
+ headHeight: number;
120
+ headChinHeight: number;
121
+ headWidth: number;
122
+ };
123
+ /**
124
+ * Creates an arrowhead shape using a polygon.
125
+ * @param ctx - The canvas context to draw on.
126
+ * @param headLineWidth - The width of the arrowhead's outline.
127
+ * @param color - The color of the arrowhead.
128
+ * @param arrowHeadParameters - The parameters for the arrowhead.
129
+ * @param fill - Whether or not to fill the triangle.
130
+ * @param stroke - Whether or not to stroke the triangle.
131
+ */
132
+ export declare const drawArrowHead: (ctx: CanvasRenderingContext2D, headLineWidth: number, color: string, arrowHeadParameters: ArrowHeadParameters, fill?: boolean, stroke?: boolean) => void;
133
+ /**
134
+ * Draws line segments on canvas (straight or curved).
135
+ *
136
+ * @param ctx - The canvas rendering context
137
+ * @param points - The array of points defining the segments
138
+ * @param width - The line width
139
+ * @param style - The stroke style (color)
140
+ * @param drawCurves - Whether to draw smooth curves through points
141
+ */
142
+ export declare const drawSegments: (ctx: CanvasRenderingContext2D, points: Point[], width: number, style: string, drawCurves: boolean) => void;
143
+ /**
144
+ * Draws a self-referencing loop on canvas using two quadratic Bézier curves.
145
+ *
146
+ * @param ctx - The canvas rendering context
147
+ * @param fromPoint - The start point of the loop
148
+ * @param toPoint - The end point of the loop
149
+ * @param apexPoint - The top point of the loop arc
150
+ * @param control1Point - Control point for the first curve segment
151
+ * @param control2Point - Control point for the second curve segment
152
+ */
153
+ export declare const drawLoop: (ctx: CanvasRenderingContext2D, fromPoint: Point, toPoint: Point, apexPoint: Point, control1Point: Point, control2Point: Point) => void;
154
+ /**
155
+ * Draws a label for a relationship on canvas.
156
+ *
157
+ * @param ctx - The canvas rendering context
158
+ * @param point - The position to draw the label at
159
+ * @param angle - The rotation angle for the label
160
+ * @param maxWidth - The maximum width available for the label
161
+ * @param rel - The relationship to draw the label for
162
+ * @param bundle - The arrow bundle containing label info
163
+ * @param disabledItemStyles - Styles for disabled items
164
+ * @param fontColor - The font color to use
165
+ * @param flip - Whether to flip the label orientation (default false)
166
+ */
167
+ export declare const drawLabel: (ctx: CanvasRenderingContext2D, point: Point, angle: number, maxWidth: number, rel: Relationship, bundle: ArrowBundle, disabledItemStyles: DisabledItemStyles, fontColor: string, flip?: boolean) => void;
168
+ export {};