@neo4j-nvl/base 1.1.0-e61ddd81 → 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 (24) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/dist/base.mjs +1 -1
  3. package/dist/types/layouts/d3forcelayout/__mocks__/d3ForceLayout.d.ts +10 -0
  4. package/dist/types/layouts/d3forcelayout/d3ForceLayout.d.ts +2 -6
  5. package/dist/types/layouts/d3forcelayout/d3ForceLayout.test.d.ts +1 -0
  6. package/dist/types/layouts/forcedirectedlayout/physlayout/PhysLayout.d.ts +0 -1
  7. package/dist/types/layouts/forcedirectedlayout/physlayout/shaders/multilevel-fragment.d.ts +1 -1
  8. package/dist/types/layouts/forcedirectedlayout/physlayout/shaders/multilevel-repulsive-fragment.d.ts +1 -1
  9. package/dist/types/layouts/forcedirectedlayout/physlayout/solarmerger/SolarMerger.bench.d.ts +1 -0
  10. package/dist/types/modules/NvlController.d.ts +3 -1
  11. package/dist/types/modules/NvlController.test.d.ts +1 -0
  12. package/dist/types/modules/state/types.d.ts +2 -6
  13. package/dist/types/renderers/domrenderer/shared/ImageCache.d.ts +3 -2
  14. package/dist/types/renderers/domrenderer/shared/arrows/ArrowBundler.d.ts +9 -0
  15. package/dist/types/renderers/domrenderer/shared/nodes/nodes.d.ts +7 -0
  16. package/dist/types/renderers/domrenderer/shared/util.d.ts +6 -1
  17. package/dist/types/renderers/domrenderer/shared/wordwrap.d.ts +12 -1
  18. package/dist/types/renderers/domrenderer/svgrenderer/svgUtils.d.ts +0 -11
  19. package/dist/types/utils/constants.d.ts +3 -2
  20. package/dist/types/utils/geometry.d.ts +4 -0
  21. package/dist/types/utils/segmentAnalytics.d.ts +2 -2
  22. package/package.json +5 -5
  23. package/dist/types/layouts/forcedirectedlayout/physlayout/shaders/multilevel-fragment-verlet.d.ts +0 -2
  24. package/dist/types/layouts/forcedirectedlayout/physlayout/shaders/multilevel-repulsive-fragment-verlet.d.ts +0 -2
@@ -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,4 +1,4 @@
1
- import type { Simulation, SimulationNodeDatum } from 'd3-force';
1
+ import type { Simulation, SimulationLinkDatum, SimulationNodeDatum } from 'd3-force';
2
2
  import type { NvlState } from '../../modules/state/types';
3
3
  import type { Node, Relationship } from '../../types/graph-element';
4
4
  import type { Point } from '../../utils/geometry';
@@ -6,11 +6,7 @@ import type { D3Node } from './types';
6
6
  export declare class D3ForceLayout {
7
7
  state: NvlState;
8
8
  d3Nodes: Record<string, D3Node>;
9
- d3RelList: {
10
- index: number;
11
- source: string;
12
- target: string;
13
- }[];
9
+ d3RelList: SimulationLinkDatum<D3Node>[];
14
10
  computing: boolean;
15
11
  center: Point;
16
12
  nodeRelCount: number[];
@@ -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;
@@ -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;
@@ -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
@@ -1,7 +1,8 @@
1
1
  import type { Cache } from '../shared/types';
2
2
  declare class ImageCache {
3
- cache: Cache;
4
- constructor();
3
+ readonly cache: Cache;
4
+ private readonly onImageLoadedCb?;
5
+ constructor(onImageLoaded?: () => void);
5
6
  getImage(src: string, inverted?: boolean): HTMLCanvasElement;
6
7
  private getOrCreateEntry;
7
8
  private invertCanvas;
@@ -24,6 +24,15 @@ export default class ArrowBundler {
24
24
  * @param positionMap A map of node IDs to positions.
25
25
  */
26
26
  updatePositions(positionMap: Record<string, Node>): void;
27
+ /**
28
+ * Remove a bundle reference from the node→bundles index for one node id.
29
+ */
30
+ private removeNodeFromMap;
31
+ /**
32
+ * Remove a relationship from a bundle.
33
+ * If the bundle is now empty, remove it from `bundles` and the node index.
34
+ */
35
+ private removeRelFromBundle;
27
36
  /**
28
37
  * Get a unique ID for a pair of IDs
29
38
  * @param id1 - The first ID.
@@ -2,6 +2,13 @@ import type { BorderStyles } from '../../../../modules/state/types';
2
2
  import type { Node } from '../../../../types/graph-element';
3
3
  import type AnimationHandler from '../../canvasrenderer/AnimationHandler';
4
4
  import type { TextSegment } from '../types';
5
+ /**
6
+ * Returns the effective node size, which is either the provided size
7
+ * if it's a positive and finite number, or the default node size otherwise.
8
+ * @param size
9
+ * @returns The effective node size.
10
+ */
11
+ export declare const getEffectiveNodeSize: (size?: number) => number;
5
12
  /**
6
13
  * Returns the bounding box of a node with the given x, y, and size properties.
7
14
  * If no size is given, the default size is used.
@@ -4,8 +4,13 @@ export declare const fontThresholds: [number, number][];
4
4
  * @param color - * @param opacity - * @returns rgba() string with the given opacity.
5
5
  */
6
6
  export declare const overrideOpacity: (color: string, opacity: number) => string;
7
- export declare function getIndividualInfoLevels(r: number, zoomLevel: number): {
7
+ export declare const getIndividualInfoLevels: (r: number, zoomLevel: number) => {
8
8
  nodeInfoLevel: number;
9
9
  fontInfoLevel: number;
10
10
  iconInfoLevel: number;
11
11
  };
12
+ /**
13
+ * On-screen size for a node icon drawn at `iconDrawSize` in graph space
14
+ * under a uniform `scale(zoom)` viewport transform.
15
+ */
16
+ export declare const getNodeIconScreenPx: (iconDrawSize: number, zoom: number) => number;
@@ -36,11 +36,22 @@ export declare const tryWithNumberOfLines: (text: string, measureWidth: (text: s
36
36
  export declare const tryBreakingOnSpaces: (text: string, measureWidth: (text: string) => number, getAvailableWidth: (a: number, b: number) => number, numberOfLines: number) => TextSegment[];
37
37
  /**
38
38
  * @param ctx - = Canvas.2dContext
39
- * @param rtlAdjustedText - The text to display
39
+ * @param text - The text to display
40
40
  *
41
41
  * @return array of text lines
42
42
  */
43
43
  export declare const getLines: (ctx: CanvasRenderingContext2D, text: string, fontFace: string, fontSize: string | number, maxWidth: number, hasIcon: boolean, maxNoLines: number, align: string) => TextSegment[];
44
+ /**
45
+ * Groups consecutive characters with the same style into text spans.
46
+ * @param lineText - The text of the line to process.
47
+ * @param stylesPerChar - A 2D array where each entry corresponds to a character.
48
+ * @param startCharIndex - The index of the first character of lineText in the full caption.
49
+ * @returns An array of objects, each containing a text span and its associated style string.
50
+ */
51
+ export declare const getGroupedTextSpans: (lineText: string, stylesPerChar: string[][], startCharIndex: number) => {
52
+ text: string;
53
+ style: string;
54
+ }[];
44
55
  /**
45
56
  * Returns the styles by char for a group of property values.
46
57
  * @param captions - = array of captions
@@ -65,14 +65,3 @@ export declare const createStyledTextElement: (options: {
65
65
  transform?: string;
66
66
  fontWeight?: string;
67
67
  }) => SVGTextElement;
68
- /**
69
- * Groups consecutive characters with the same style into text spans
70
- * @param lineText - The text of the line
71
- * @param stylesPerChar - Array of style arrays for each character
72
- * @param startCharIndex - The starting character index in the overall caption
73
- * @returns Array of text spans with their styles
74
- */
75
- export declare const getGroupedTextSpans: (lineText: string, stylesPerChar: string[][], startCharIndex: number) => {
76
- text: string;
77
- style: string;
78
- }[];
@@ -3,8 +3,7 @@ export declare const SpringTextureSize = 4096;
3
3
  export declare const MaxIndexBuffer = 65536;
4
4
  export declare const MinRelLength = 100;
5
5
  export declare const Gravity = 25;
6
- export declare const SimulationStopVelocity = 100;
7
- export declare const VerletSimulationStopVelocity = 0.25;
6
+ export declare const SimulationStopVelocity = 0.25;
8
7
  export declare const MappingEnd = 999999;
9
8
  export declare const ActiveAnimDuration = 1000;
10
9
  export declare const DefaultNodeSize = 25;
@@ -40,4 +39,6 @@ export declare const GlAdjust: number;
40
39
  */
41
40
  export declare const getDevicePixelRatio: () => number;
42
41
  export declare const HtmlCaptionMinWidthThreshold = 5;
42
+ /** Min on-screen icon size (px) before we skip drawing; avoids unreadable bitmaps when zoomed out. */
43
+ export declare const MinNodeIconScreenPx = 10;
43
44
  export declare const allowedWrappers: string[];
@@ -9,6 +9,10 @@ export interface Point {
9
9
  y: number;
10
10
  }
11
11
  export declare const isPointDefined: (point: Point | Node) => boolean;
12
+ /**
13
+ * Returns true when value is a finite number (not NaN or Infinity).
14
+ */
15
+ export declare const isValidFiniteNumber: (value: unknown) => value is number;
12
16
  /**
13
17
  * This function checks if the coordinates are defined.
14
18
  * @param from - The {@link Node} the edge starts from.
@@ -24,7 +24,7 @@ export declare abstract class AnalyticsFunction {
24
24
  /**
25
25
  * Get/fetch unique segment Id from the localstorage.
26
26
  */
27
- abstract getSegmentTrackingId(): Record<string, unknown>;
27
+ abstract getSegmentTrackingId(): string;
28
28
  /**
29
29
  * Track arbitrary event.
30
30
  */
@@ -44,7 +44,7 @@ export declare class SegmentAnalytics implements AnalyticsFunction {
44
44
  }): Promise<void>;
45
45
  identify(): void;
46
46
  loadSavedSegmentId(): string;
47
- getSegmentTrackingId(): any;
47
+ getSegmentTrackingId(): string;
48
48
  saveTrackingId(segmentId: string): void;
49
49
  private ready;
50
50
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neo4j-nvl/base",
3
- "version": "1.1.0-e61ddd81",
3
+ "version": "1.2.0",
4
4
  "license": "SEE LICENSE IN 'LICENSE.txt'",
5
5
  "homepage": "https://neo4j.com/docs/nvl/current/",
6
6
  "description": "Base library for the Neo4j Visualization Library",
@@ -23,13 +23,14 @@
23
23
  "build:dev": "yarn global:tsc && webpack --mode development",
24
24
  "watch": "concurrently 'tsc -w' 'webpack -w --mode development' -k -n 'tsc,webpack' -c 'cyan,magenta'",
25
25
  "test": "yarn global:jest",
26
+ "test:perf": "yarn global:jest --testPathPattern='.bench.' --testPathIgnorePatterns=",
26
27
  "eslint": "yarn global:eslint ./src/",
27
28
  "coverage": "yarn test --coverage --silent",
28
29
  "prepack": "cp ../../LICENSE.txt ./ && cp ../../CHANGELOG.md ./",
29
30
  "postpack": "rm LICENSE.txt && rm CHANGELOG.md"
30
31
  },
31
32
  "dependencies": {
32
- "@neo4j-nvl/layout-workers": "1.1.0-e61ddd81",
33
+ "@neo4j-nvl/layout-workers": "1.2.0",
33
34
  "@segment/analytics-next": "1.81.1",
34
35
  "color-string": "1.9.1",
35
36
  "d3-force": "3.0.0",
@@ -41,7 +42,7 @@
41
42
  "node-pid-controller": "1.0.1",
42
43
  "resizelistener": "1.1.0",
43
44
  "tinycolor2": "1.6.0",
44
- "uuid": "8.3.2"
45
+ "uuid": "14.0.0"
45
46
  },
46
47
  "devDependencies": {
47
48
  "@babel/core": "7.28.5",
@@ -58,6 +59,5 @@
58
59
  },
59
60
  "peerDependencies": {
60
61
  "neo4j-driver": "*"
61
- },
62
- "stableVersion": "1.1.0"
62
+ }
63
63
  }
@@ -1,2 +0,0 @@
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
- export default _default;
@@ -1,2 +0,0 @@
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
- export default _default;