@neo4j-nvl/base 1.2.2 → 2.0.0-2b04269b

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 (81) hide show
  1. package/CHANGELOG.md +31 -1
  2. package/README.md +8 -0
  3. package/dist/base.mjs +39758 -2
  4. package/dist/types/index.d.ts +21 -20
  5. package/dist/types/layouts/animatedlayout/AnimatedLayout.d.ts +2 -2
  6. package/dist/types/layouts/d3forcelayout/constants.d.ts +0 -1
  7. package/dist/types/layouts/d3forcelayout/d3ForceLayout.d.ts +3 -3
  8. package/dist/types/layouts/forcedirectedlayout/PhysLayout.d.ts +275 -0
  9. package/dist/types/layouts/forcedirectedlayout/clustering/ClusterByMerger.d.ts +62 -0
  10. package/dist/types/layouts/forcedirectedlayout/clustering/SolarMerger.d.ts +56 -0
  11. package/dist/types/layouts/forcedirectedlayout/clustering/clustering-utils.d.ts +143 -0
  12. package/dist/types/layouts/forcedirectedlayout/{physlayout/solarmerger → clustering}/types.d.ts +11 -1
  13. package/dist/types/layouts/forcedirectedlayout/shaders/multilevel-fragment.d.ts +2 -0
  14. package/dist/types/layouts/forcedirectedlayout/shaders/multilevel-repulsive-fragment.d.ts +2 -0
  15. package/dist/types/layouts/forcedirectedlayout/shaders/multilevel-update-fragment.d.ts +2 -0
  16. package/dist/types/layouts/forcedirectedlayout/shaders/sim-vertex.d.ts +2 -0
  17. package/dist/types/layouts/freeLayout/FreeLayout.d.ts +2 -4
  18. package/dist/types/layouts/gridLayout/GridLayout.d.ts +2 -6
  19. package/dist/types/layouts/hierarchicallayout/HierarchicalLayout.d.ts +5 -0
  20. package/dist/types/modules/ExternalCallbackHandler.d.ts +4 -10
  21. package/dist/types/modules/NvlController.d.ts +87 -7
  22. package/dist/types/modules/Shader.d.ts +1 -1
  23. package/dist/types/modules/dataset.d.ts +4 -2
  24. package/dist/types/modules/state/types.d.ts +114 -23
  25. package/dist/types/renderers/ClusterOverlay.d.ts +73 -0
  26. package/dist/types/renderers/ClusterOverlay.test.d.ts +1 -0
  27. package/dist/types/renderers/domrenderer/canvasrenderer/CanvasRenderer.d.ts +1 -1
  28. package/dist/types/renderers/domrenderer/shared/ImageCache.d.ts +1 -0
  29. package/dist/types/renderers/domrenderer/shared/types.d.ts +1 -2
  30. package/dist/types/renderers/webglrenderer/Controller.d.ts +27 -6
  31. package/dist/types/renderers/webglrenderer/IconLayerRegistry.d.ts +14 -0
  32. package/dist/types/renderers/webglrenderer/IconLayerRegistry.test.d.ts +1 -0
  33. package/dist/types/renderers/webglrenderer/Renderer.d.ts +43 -3
  34. package/dist/types/renderers/webglrenderer/node-animation-fragment.d.ts +1 -1
  35. package/dist/types/renderers/webglrenderer/node-animation-vertex.d.ts +1 -1
  36. package/dist/types/renderers/webglrenderer/node-fragment-point.d.ts +1 -1
  37. package/dist/types/renderers/webglrenderer/node-vertex-point.d.ts +1 -1
  38. package/dist/types/renderers/webglrenderer/rel-fragment-line.d.ts +1 -1
  39. package/dist/types/renderers/webglrenderer/rel-glsl-utils.d.ts +1 -0
  40. package/dist/types/renderers/webglrenderer/rel-vertex-arrow.d.ts +2 -0
  41. package/dist/types/renderers/webglrenderer/rel-vertex-line.d.ts +1 -1
  42. package/dist/types/renderers/webglrenderer/viewport-box-fragment.d.ts +1 -1
  43. package/dist/types/renderers/webglrenderer/viewport-box-vertex.d.ts +1 -1
  44. package/dist/types/types/graph-element.d.ts +9 -1
  45. package/dist/types/types/nvl-window-functions.d.ts +2 -2
  46. package/dist/types/utils/canvasManagement.d.ts +7 -1
  47. package/dist/types/utils/clusterGeometry.d.ts +14 -0
  48. package/dist/types/utils/clusterGeometry.test.d.ts +1 -0
  49. package/dist/types/utils/clusterHitRegions.d.ts +42 -0
  50. package/dist/types/utils/clusterHitRegions.test.d.ts +1 -0
  51. package/dist/types/utils/constants.d.ts +6 -0
  52. package/dist/types/utils/geometry.d.ts +44 -1
  53. package/dist/types/utils/{hittest.d.ts → hitTest.d.ts} +30 -4
  54. package/dist/types/utils/hitTest.test.d.ts +1 -0
  55. package/dist/types/utils/jsDriverResultTransformer.d.ts +11 -9
  56. package/dist/types/utils/layoutSeeding.d.ts +7 -0
  57. package/dist/types/utils/layoutSeeding.test.d.ts +1 -0
  58. package/dist/types/utils/webWorkerUtils.d.ts +1 -1
  59. package/dist/types/utils/zoomFunctions.d.ts +9 -2
  60. package/package.json +13 -15
  61. package/skills/neo4j-nvl-skill/README.md +39 -0
  62. package/skills/neo4j-nvl-skill/SKILL.md +456 -0
  63. package/skills/neo4j-nvl-skill/references/api-surface.md +596 -0
  64. package/skills/neo4j-nvl-skill/references/troubleshooting.md +317 -0
  65. package/dist/base.mjs.LICENSE.txt +0 -21
  66. package/dist/types/layouts/forcedirectedlayout/ForceCytoLayout.d.ts +0 -22
  67. package/dist/types/layouts/forcedirectedlayout/cosebilkentlayout/CoseBilkentLayout.d.ts +0 -33
  68. package/dist/types/layouts/forcedirectedlayout/physlayout/PhysLayout.d.ts +0 -174
  69. package/dist/types/layouts/forcedirectedlayout/physlayout/shaders/multilevel-fragment.d.ts +0 -2
  70. package/dist/types/layouts/forcedirectedlayout/physlayout/shaders/multilevel-repulsive-fragment.d.ts +0 -2
  71. package/dist/types/layouts/forcedirectedlayout/physlayout/shaders/multilevel-update-fragment.d.ts +0 -2
  72. package/dist/types/layouts/forcedirectedlayout/physlayout/shaders/sim-vertex.d.ts +0 -2
  73. package/dist/types/layouts/forcedirectedlayout/physlayout/shaders/workaround-fragment.d.ts +0 -2
  74. package/dist/types/layouts/forcedirectedlayout/physlayout/solarmerger/SolarMerger.d.ts +0 -21
  75. package/dist/types/layouts/forcedirectedlayout/physlayout/solarmerger/solar-placer.d.ts +0 -2
  76. /package/dist/types/layouts/forcedirectedlayout/{physlayout/PhysLayout.test.d.ts → PhysLayout.test.d.ts} +0 -0
  77. /package/dist/types/layouts/forcedirectedlayout/{physlayout/solarmerger/SolarMerger.bench.d.ts → clustering/ClusterByMerger.test.d.ts} +0 -0
  78. /package/dist/types/layouts/forcedirectedlayout/{physlayout/solarmerger/SolarMerger.test.d.ts → clustering/SolarMerger.bench.d.ts} +0 -0
  79. /package/dist/types/layouts/forcedirectedlayout/{physlayout/solarmerger/solar-placer.test.d.ts → clustering/SolarMerger.test.d.ts} +0 -0
  80. /package/dist/types/{utils/hittest.test.d.ts → layouts/forcedirectedlayout/clustering/clustering-utils.test.d.ts} +0 -0
  81. /package/dist/types/layouts/forcedirectedlayout/{physlayout/glwrapper.d.ts → glwrapper.d.ts} +0 -0
@@ -0,0 +1,317 @@
1
+ # NVL — Troubleshooting & Gotchas
2
+
3
+ ## Container renders nothing / 0×0
4
+
5
+ **Cause:** Parent `<div>` has no explicit height. NVL inherits `0` → graph invisible.
6
+
7
+ ```html
8
+ <!-- ❌ -->
9
+ <div id="viz"></div>
10
+
11
+ <!-- ✅ -->
12
+ <div id="viz" style="width: 100%; height: 600px;"></div>
13
+ ```
14
+
15
+ React: wrap `<InteractiveNvlWrapper>` or `<BasicNvlWrapper>` in a sized parent OR pass `style={{ width, height }}` directly (the wrappers forward `HTMLProps<HTMLDivElement>`).
16
+
17
+ ---
18
+
19
+ ## Driver `EagerResult` rejected by NVL
20
+
21
+ **Cause:** Default `executeQuery` returns `{ records, summary, keys }` — records are not `Node`/`Relationship` shapes.
22
+
23
+ ```javascript
24
+ // ❌
25
+ const res = await driver.executeQuery('MATCH (a)-[r]-(b) RETURN a,r,b LIMIT 25')
26
+ new NVL(container, res.records, []) // ids missing, shape wrong
27
+
28
+ // ✅
29
+ import { nvlResultTransformer } from '@neo4j-nvl/base'
30
+ const { nodes, relationships } = await driver.executeQuery(
31
+ 'MATCH (a)-[r]-(b) RETURN a,r,b LIMIT 25', {},
32
+ { database: 'neo4j', resultTransformer: nvlResultTransformer }
33
+ )
34
+ new NVL(container, nodes, relationships)
35
+ ```
36
+
37
+ For driver setup → `neo4j-driver-javascript-skill`.
38
+
39
+ ---
40
+
41
+ ## Web Worker construction blocked
42
+
43
+ **Cause:** NVL's `@neo4j-nvl/layout-workers` spawns Web Workers for the force-directed and hierarchical layouts. Some environments block worker construction — strict CSP (`worker-src 'none'`), sandboxed iframes, custom runtimes, or older build tools that don't bundle workers correctly.
44
+
45
+ **Fix:** Disable workers — NVL has a synchronous fallback (slower for large graphs, identical output). Applies to any build tool (Vite, Webpack, Rollup, esbuild, Parcel, etc.):
46
+
47
+ ```javascript
48
+ const nvl = new NVL(container, nodes, rels, { disableWebWorkers: true })
49
+ ```
50
+
51
+ React:
52
+
53
+ ```tsx
54
+ <InteractiveNvlWrapper nvlOptions={{ disableWebWorkers: true }} ... />
55
+ ```
56
+
57
+ Most modern bundlers handle worker construction natively — try the default setup first, fall back to `disableWebWorkers: true` only when worker errors actually appear.
58
+
59
+ ---
60
+
61
+ ## Renderer trade-off (Canvas vs WebGL)
62
+
63
+ | Renderer | Practical ceiling | Captions | Hit-test |
64
+ |---|---|---|---|
65
+ | Canvas (default) | ~1,000 nodes | Full styling | Pixel-perfect |
66
+ | WebGL | 100,000+ nodes | Not supported | Approximate |
67
+
68
+ NVL 2.0's GPU renderer and physics engine require **WebGL2**. WebGL1 is not supported. WebGL rendering now supports node icons and relationship arrowheads, but not captions.
69
+
70
+ ```javascript
71
+ // At construction
72
+ new NVL(container, nodes, rels, { renderer: 'webgl' })
73
+
74
+ // At runtime
75
+ nvl.setRenderer('webgl') // or 'canvas'
76
+ ```
77
+
78
+ Pick based on node count and whether captions are required. Switching at runtime triggers a re-render. Set `disableWebGL: true` to explicitly use non-WebGL rendering and layout implementations.
79
+
80
+ ---
81
+
82
+ ## WebGL captions/labels are missing
83
+
84
+ **Cause:** Expected NVL 2.0 behavior. The WebGL renderer does not support captions.
85
+
86
+ **Fix:** Use Canvas when captions are required:
87
+
88
+ ```javascript
89
+ nvl.setRenderer('canvas')
90
+ ```
91
+
92
+ ---
93
+
94
+ ## React `layout` or `layoutOptions` has no effect
95
+
96
+ **Cause:** NVL 2.0 removed the deprecated standalone React wrapper props.
97
+
98
+ ```tsx
99
+ // ❌ removed in 2.0
100
+ <InteractiveNvlWrapper layout="hierarchical" layoutOptions={{ direction: 'down' }} />
101
+
102
+ // ✅ put both options in nvlOptions
103
+ <InteractiveNvlWrapper
104
+ nvlOptions={{ layout: 'hierarchical', layoutOptions: { direction: 'down' } }}
105
+ />
106
+ ```
107
+
108
+ ---
109
+
110
+ ## `onZoom` callback no longer fires
111
+
112
+ **Cause:** NVL 2.0 removed `onZoom`.
113
+
114
+ **Fix:** Use `onZoomAndPan`, which includes the complete viewport state:
115
+
116
+ ```javascript
117
+ zoom.updateCallback('onZoomAndPan', (zoomLevel, panX, panY, event) => {
118
+ console.log({ zoomLevel, panX, panY })
119
+ })
120
+ ```
121
+
122
+ For React, set `mouseEventCallbacks={{ onZoomAndPan: callback }}`.
123
+
124
+ ---
125
+
126
+ ## Force-directed clusters do not appear
127
+
128
+ **Cause:** Force-directed clustering options must be nested under `layoutOptions.clustering`; hierarchical `clusterBy` is placed directly under `layoutOptions`.
129
+
130
+ ```javascript
131
+ // Force-directed
132
+ const options = {
133
+ layout: 'forceDirected',
134
+ layoutOptions: {
135
+ clustering: {
136
+ clusterBy: (nodeId) => categoryByNodeId[nodeId],
137
+ drawOutline: true,
138
+ drawHalo: true
139
+ }
140
+ }
141
+ }
142
+
143
+ // Hierarchical
144
+ const hierarchicalOptions = {
145
+ layout: 'hierarchical',
146
+ layoutOptions: {
147
+ direction: 'down',
148
+ clusterBy: (nodeId) => categoryByNodeId[nodeId]
149
+ }
150
+ }
151
+ ```
152
+
153
+ The callback must return the same string or number for nodes in one cluster. Return `undefined` for independent nodes. In React, keep the callback stable with `useCallback`; otherwise changing its identity can cause repeated cluster evaluation.
154
+
155
+ ---
156
+
157
+ ## Cluster click callback misses clicks
158
+
159
+ **Cause:** `onClusterClick`, `onClusterDoubleClick`, and `onClusterRightClick` apply to empty space inside a force-directed cluster hull. A node or relationship under the pointer takes priority.
160
+
161
+ **Fix:** Use node/relationship callbacks for direct element hits and cluster callbacks for hull space. For hover, inspect `hitElements.clusters` from `onHover`:
162
+
163
+ ```javascript
164
+ hover.updateCallback('onHover', (_element, hitElements) => {
165
+ const cluster = hitElements.clusters[0]?.data
166
+ if (cluster) console.log(cluster.id, cluster.nodeIds)
167
+ })
168
+ ```
169
+
170
+ ---
171
+
172
+ ## `onWebGLContextLost` fires
173
+
174
+ **Cause:** GPU dropped the context (tab backgrounded too long, driver crash, OS swap).
175
+
176
+ **Fix:** Implement `onWebGLContextLost` callback — call `nvl.setRenderer('canvas')` as recovery, or `nvl.restart(options, true)` to reinitialize while keeping positions:
177
+
178
+ ```javascript
179
+ const nvl = new NVL(container, nodes, rels,
180
+ { renderer: 'webgl' },
181
+ { onWebGLContextLost: () => nvl.restart({ renderer: 'canvas' }, true) }
182
+ )
183
+ ```
184
+
185
+ ---
186
+
187
+ ## Telemetry — opting out
188
+
189
+ **Cause:** NVL ships Segment Analytics; runs by default.
190
+
191
+ **Fix:**
192
+
193
+ ```javascript
194
+ new NVL(container, nodes, rels, { disableTelemetry: true })
195
+ ```
196
+
197
+ Mandatory in regulated / air-gapped / customer-data-sensitive deployments.
198
+
199
+ ---
200
+
201
+ ## Memory leak after route change (React)
202
+
203
+ **Cause:** Custom `useRef` + manual `new NVL(...)` not torn down on unmount.
204
+
205
+ **Fix:** Always call `nvl.destroy()` in cleanup:
206
+
207
+ ```tsx
208
+ useEffect(() => {
209
+ const nvl = new NVL(ref.current!, nodes, rels)
210
+ return () => nvl.destroy()
211
+ }, [])
212
+ ```
213
+
214
+ `<InteractiveNvlWrapper>` / `<BasicNvlWrapper>` handle this automatically — prefer them over manual refs.
215
+
216
+ For vanilla, destroy all interaction handlers BEFORE the NVL instance:
217
+
218
+ ```javascript
219
+ for (const h of handlers) h.destroy()
220
+ nvl.destroy()
221
+ ```
222
+
223
+ ---
224
+
225
+ ## Layout never settles / `isLayoutMoving` stays true
226
+
227
+ **Cause:** Graph has competing forces or is unsolvable; or workers disabled and synchronous fallback is throttled.
228
+
229
+ **Fix options:**
230
+ - Pin anchor nodes: `nvl.pinNode(id)` for known fixed positions
231
+ - Cap iterations: `nvlOptions: { layoutTimeLimit: 3000 }` (ms)
232
+ - Switch layout: `nvl.setLayout('hierarchical')` for DAG-like data
233
+ - Reduce nodes (paginate query, increase `LIMIT`)
234
+
235
+ ---
236
+
237
+ ## Selection callback fires twice
238
+
239
+ **Cause:** `selectOnClick` toggled mid-render OR both base handler + wrapper handling clicks.
240
+
241
+ **Fix:** Set `interactionOptions={{ selectOnClick: true }}` once at mount and leave it; don't flip the value across renders. If using vanilla `ClickInteraction`, do not also register a manual `container.addEventListener('click', ...)`.
242
+
243
+ ---
244
+
245
+ ## Hit test misses near node edge
246
+
247
+ **Cause:** Default `hitNodeMarginWidth: 0` requires pointer strictly inside the node circle.
248
+
249
+ **Fix:** Widen the hit margin:
250
+
251
+ ```javascript
252
+ const { nvlTargets } = nvl.getHits(evt, ['node', 'relationship'], { hitNodeMarginWidth: 8 })
253
+ ```
254
+
255
+ For pan-on-relationship sensitivity, set `excludeNodeMargin: true` in `PanInteraction` options.
256
+
257
+ To include cluster hulls when supplying an explicit target list, add `'cluster'`. Cluster hits are included by default when the target list is omitted:
258
+
259
+ ```javascript
260
+ const { nvlTargets } = nvl.getHits(evt, ['node', 'relationship', 'cluster'])
261
+ ```
262
+
263
+ ---
264
+
265
+ ## Layout / hierarchy looks wrong
266
+
267
+ **Cause:** Default `'forceDirected'` not suited for tree / pipeline / circular data.
268
+
269
+ **Fix:** Pick the right layout up front:
270
+
271
+ | Data shape | Layout |
272
+ |---|---|
273
+ | Generic network | `'forceDirected'` |
274
+ | Tree / DAG | `'hierarchical'` (set `direction` in `layoutOptions`) |
275
+ | Sorted ring | `'circular'` (provide `sortFunction`) |
276
+ | Manual positioning | `'free'` |
277
+ | Snapped grid | `'grid'` |
278
+ | Force-directed via d3-force | `'d3Force'` |
279
+
280
+ ---
281
+
282
+ ## License pitfall
283
+
284
+ **Cause:** NVL is licensed under the Neo4j Visualization Library License — for use with Neo4j products only.
285
+
286
+ **Implication:** Cannot ship NVL inside an application that uses a non-Neo4j graph backend (Neptune, JanusGraph, ArangoDB, etc.) as its primary data store. Refer to the LICENSE.txt in `@neo4j-nvl/base` and confirm with legal before redistribution.
287
+
288
+ ---
289
+
290
+ ## Image export returns blank PNG
291
+
292
+ **Cause:** Called `saveToFile()` before `onLayoutDone` fires — initial positions still settling.
293
+
294
+ **Fix:** Trigger export from the callback:
295
+
296
+ ```javascript
297
+ const nvl = new NVL(container, nodes, rels, {},
298
+ { onLayoutDone: () => nvl.saveToFile({ filename: 'graph.png' }) })
299
+ ```
300
+
301
+ Or `getImageDataUrl()` for in-memory data URL.
302
+
303
+ ---
304
+
305
+ ## `restart()` vs `addAndUpdateElementsInGraph()`
306
+
307
+ **`restart()`** rebuilds internal state; expensive; resets selection unless `retainPositions=true` is passed AND positions are preserved. Use for option changes (renderer, layout, styling).
308
+
309
+ **`addAndUpdateElementsInGraph()`** is the correct API for streaming data changes. Use for live updates.
310
+
311
+ ```javascript
312
+ // ❌ data update via restart — full rebuild every tick
313
+ nvl.restart()
314
+
315
+ // ✅ diff update
316
+ nvl.addAndUpdateElementsInGraph(newNodes, newRels)
317
+ ```
@@ -1,21 +0,0 @@
1
- /*!
2
- * The buffer module from node.js, for the browser.
3
- *
4
- * @author Feross Aboukhadijeh <https://feross.org>
5
- * @license MIT
6
- */
7
-
8
- /*! ieee754. BSD-3-Clause License. Feross Aboukhadijeh <https://feross.org/opensource> */
9
-
10
- /*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/babel/babel/blob/main/packages/babel-helpers/LICENSE */
11
-
12
- /*! safe-buffer. MIT License. Feross Aboukhadijeh <https://feross.org/opensource> */
13
-
14
- /**
15
- * @license
16
- * Lodash <https://lodash.com/>
17
- * Copyright OpenJS Foundation and other contributors <https://openjsf.org/>
18
- * Released under MIT license <https://lodash.com/license>
19
- * Based on Underscore.js 1.8.3 <http://underscorejs.org/LICENSE>
20
- * Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
21
- */
@@ -1,22 +0,0 @@
1
- export declare const ForceDirectedLayoutType = "forceDirected";
2
- export declare const CoseBilkentType = "coseBilkent";
3
- export declare class ForceCytoLayout {
4
- private readonly physLayout;
5
- private readonly coseBilkentLayout;
6
- private readonly state;
7
- private currentLayoutType;
8
- private enableCytoscape;
9
- private currentLayout;
10
- constructor(config: any);
11
- setOptions(options: any): void;
12
- private getLayout;
13
- private setLayout;
14
- copyLayoutPositions(nodeList: any, prevLayout: any, nextLayout: any): void;
15
- update(refreshPositions?: boolean): void;
16
- getShouldUpdate(): boolean;
17
- getComputing(): boolean;
18
- updateNodes(positionList: any): void;
19
- getNodePositions(data: any): import("../..").Node[];
20
- terminateUpdate(): void;
21
- destroy(): void;
22
- }
@@ -1,33 +0,0 @@
1
- import type { NvlState } from '../../../modules/state/types';
2
- import type { Node, Relationship } from '../../../types/graph-element';
3
- import AnimatedLayout from '../../animatedlayout/AnimatedLayout';
4
- type LayoutData = {
5
- elements: {
6
- group: string;
7
- data: {
8
- id: string;
9
- };
10
- }[];
11
- spacingFactor: number;
12
- };
13
- export declare class CoseBilkentLayout extends AnimatedLayout {
14
- private stateDisposers;
15
- cytoCompleteCallback: () => void;
16
- computing: boolean;
17
- pendingLayoutData: LayoutData;
18
- worker: SharedWorker;
19
- private workersDisabled;
20
- constructor(config: {
21
- state: NvlState;
22
- cytoCompleteCallback?: () => void;
23
- animationCompleteCallback?: () => void;
24
- });
25
- setOptions(): void;
26
- update(refreshPositions?: boolean, updateMobXNodes?: Node[], updateMobXRelationships?: Relationship[]): void;
27
- getShouldUpdate(): boolean;
28
- getComputing(): boolean;
29
- layout(nodes: Node[], relationships: Relationship[]): void;
30
- terminateUpdate(): void;
31
- destroy(): void;
32
- }
33
- export {};
@@ -1,174 +0,0 @@
1
- import type { ForceDirectedOptions, NvlState } from '../../../modules/state/types';
2
- import type { Node, Relationship } from '../../../types/graph-element';
3
- import type { Point } from '../../../utils/geometry';
4
- export declare class PhysLayout {
5
- private physVbo;
6
- private physSmallVbo;
7
- private physProjection;
8
- private physSmallProjection;
9
- private gl;
10
- private useReadpixelWorkaround;
11
- private averageNodeSize;
12
- private shouldUpdate;
13
- private iterationCount;
14
- private lastSpeedValues;
15
- private rollingAvgGraphSpeed;
16
- private nodeVariation;
17
- private nodeCenterPoint;
18
- private peakIterationMultiplier;
19
- private stateDisposers;
20
- private state;
21
- private dpr;
22
- private intelWorkaround;
23
- private simulationStopVelocitySquared;
24
- private gravity;
25
- private force;
26
- private nodeIdToIndex;
27
- private nodeIndexToId;
28
- private flatRelationshipKeys;
29
- private numNodes;
30
- private solarMerger;
31
- private subGraphs;
32
- private nodeSortMap;
33
- private firstUpdate;
34
- private curPhysData;
35
- private apprxRepForceShader;
36
- private levelsClusterTexture;
37
- private levelsFinestIndexTexture;
38
- private initalLevelTexture;
39
- private levelsData;
40
- private collisionDetectionMultiplier;
41
- private physShader;
42
- private physData;
43
- private workaroundData;
44
- private pinData;
45
- private updateData;
46
- private updateShader;
47
- private workaroundShader;
48
- private physPositions;
49
- private springTexture;
50
- private sizeTexture;
51
- private offsetTexture;
52
- private pinTexture;
53
- private addedNodes;
54
- private updateTexture;
55
- private vaoExt;
56
- private physVao;
57
- private physSmallVao;
58
- private updateVao;
59
- private workaroundVao;
60
- constructor(config: ForceDirectedOptions & {
61
- webGLContext: WebGLRenderingContext;
62
- state: NvlState;
63
- });
64
- /**
65
- * Sets the layout options.
66
- * @param options - The object with the options.
67
- * @param initial - Whether this is the first setting of options.
68
- */
69
- setOptions(options?: ForceDirectedOptions, initial?: boolean): void;
70
- /**
71
- * Sets graph data to be used
72
- * @param {{ nodes: Node[]; rels: Relationship[] }} data the graph object
73
- * @returns The top level graph of the subgraphs created by the solar merger.
74
- */
75
- setData(data: {
76
- nodes: Node[];
77
- rels: Relationship[];
78
- }): import("./solarmerger/types").CoarsenedGraph;
79
- /**
80
- * Updates the node's positions for the next step in the physics layout iterations if the layout needs to update
81
- * @param refreshPositions whether the entire layout should be reheated
82
- * @returns Whether the layout should update in the following step.
83
- */
84
- update(refreshPositions?: boolean): boolean;
85
- /**
86
- * Stops the layout
87
- */
88
- terminateUpdate(): void;
89
- /**
90
- * Returns whether the current layout needs to be updated in the next iteration step.
91
- * @returns Whether the layout should update in the following step.
92
- */
93
- getShouldUpdate(): boolean;
94
- /**
95
- * Returns whether the current layout is computing.
96
- * Always returns false because the force-directed layout has no computing step
97
- * @returns Whether the layout is computing.
98
- */
99
- getComputing(): boolean;
100
- /**
101
- * Adds the current x,y coordinates for each node to a given list of nodes.
102
- * @param nodeList - The list of nodes to get positions for.
103
- * @returns The list of nodes containing the x,y coordinates on each node object.
104
- */
105
- getNodePositions(nodeList: Node[]): (Node & Point)[];
106
- /**
107
- * Reheats the layout
108
- * @param fullData - The entire graph object.
109
- */
110
- reheat(fullData: {
111
- nodes: Node[];
112
- rels?: Relationship[];
113
- }): void;
114
- /**
115
- * Updates the existing nodes of a graph with a given list of updates
116
- * @param nodeList - The list of updates.
117
- */
118
- updateNodes(nodeList: Node[]): void;
119
- /**
120
- * Adds and removes nodes from the graph based on given updates
121
- * @param fullData - The entire graph object.
122
- * @param fullData.nodes - The nodes in the graph.
123
- * @param fullData.rels - The relationships in the graph.
124
- * @param nodeChanges - The changes in the graph.
125
- * @param nodeChanges.adds - A list of nodes to be added.
126
- * @param nodeChanges.removes - A list of nodes to be removed.
127
- */
128
- addRemoveData(fullData: {
129
- nodes: Node[];
130
- rels: Relationship[];
131
- }, nodeChanges: {
132
- adds: Record<string, Node>;
133
- removes: Record<string, Node>;
134
- }, relationshipChanges: {
135
- adds: Record<string, Relationship>;
136
- removes: Record<string, Relationship>;
137
- }): import("./solarmerger/types").CoarsenedGraph;
138
- /**
139
- * Destroys the class and all buffers and textures.
140
- */
141
- destroy(): void;
142
- /**
143
- * Takes the new flat key set of relationships and the current relationship changes
144
- * and checks whether removed relationships are still in the new flat key set.
145
- * and whether added relationships were already in the old flat key set.
146
- * @param newFlatKeySet The new flat key set of relationships.
147
- * @param relationshipChanges The changes in the relationships.
148
- * @returns Whether the relationship flat map has changed.
149
- */
150
- private hasRelationshipFlatMapChanged;
151
- private dumpTexture;
152
- private getScaleNumber;
153
- private getBaseLength;
154
- private checkForUpdates;
155
- private getNodePosition;
156
- private getMaxSpeedSquared;
157
- private getMedianSpeedSquared;
158
- private getPhysData;
159
- private newTexture;
160
- private newFramebuffer;
161
- private checkCompatibility;
162
- private printGlStats;
163
- private adjustToGlSize;
164
- private setupSize;
165
- private setupSprings;
166
- private setupPhysics;
167
- private setupPhysicsForNodes;
168
- private setupPhysicsForCoarse;
169
- private setupPinData;
170
- private setupUpdates;
171
- private setupReadpixelWorkaround;
172
- private doReadpixelWorkaround;
173
- private definePhysicsArrays;
174
- }
@@ -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;
@@ -1,2 +0,0 @@
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";
2
- export default _default;
@@ -1,2 +0,0 @@
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}";
2
- export default _default;
@@ -1,2 +0,0 @@
1
- declare const _default: "precision lowp float;\n\nuniform sampler2D u_physData;\nuniform float u_index;\n\nfloat shift_right (float v, float amt) { \n v = floor(v) + 0.5; \n return floor(v / exp2(amt)); \n}\n\nfloat shift_left (float v, float amt) { \n return floor(v * exp2(amt) + 0.5); \n}\n\nfloat mask_last (float v, float bits) { \n return mod(v, shift_left(1.0, bits)); \n}\n\nfloat extract_bits (float num, float from, float to) { \n from = floor(from + 0.5); to = floor(to + 0.5); \n return mask_last(shift_right(num, from), to - from); \n}\n\n// From https://stackoverflow.com/questions/17981163/webgl-read-pixels-from-floating-point-render-target\nvec4 encode_float (float val) { \n if (val == 0.0) return vec4(0, 0, 0, 0); \n float sign = val > 0.0 ? 0.0 : 1.0; \n val = abs(val); \n float exponent = floor(log2(val)); \n float biased_exponent = exponent + 127.0; \n float fraction = ((val / exp2(exponent)) - 1.0) * 8388608.0; \n float t = biased_exponent / 2.0; \n float last_bit_of_biased_exponent = fract(t) * 2.0; \n float remaining_bits_of_biased_exponent = floor(t); \n float byte4 = extract_bits(fraction, 0.0, 8.0) / 255.0; \n float byte3 = extract_bits(fraction, 8.0, 16.0) / 255.0; \n float byte2 = (last_bit_of_biased_exponent * 128.0 + extract_bits(fraction, 16.0, 23.0)) / 255.0; \n float byte1 = (sign * 128.0 + remaining_bits_of_biased_exponent) / 255.0; \n return vec4(byte4, byte3, byte2, byte1); \n}\n\nvoid main(void) {\n vec4 texData = texture2D(u_physData, gl_FragCoord.xy / 255.0);\n float data;\n if (u_index == 0.0) {\n data = texData.x;\n } else if (u_index == 1.0) {\n data = texData.y;\n } else if (u_index == 2.0) {\n data = texData.z;\n } else if (u_index == 3.0) {\n data = texData.w;\n }\n\n gl_FragColor = encode_float(data);\n}\n";
2
- export default _default;
@@ -1,21 +0,0 @@
1
- import type { Node, Relationship } from '../../../../types/graph-element';
2
- import type { CoarsenedGraph, SolarRelationship } from './types';
3
- export default class SolarMerger {
4
- graph: {
5
- nodes: Node[];
6
- relationships: number[][];
7
- idToRel: Record<string, SolarRelationship>;
8
- };
9
- subGraphs: CoarsenedGraph[];
10
- sunMap: Record<string, string>;
11
- relIdMap: string[][];
12
- nodeSortMap?: Record<number, number>;
13
- constructor(inputGraph: {
14
- nodes: Node[];
15
- rels: Relationship[];
16
- }, nodeIdToIndex: Record<string, number>);
17
- private constructGraphObjects;
18
- coarsenTo(targetSize: number): CoarsenedGraph;
19
- coarsenBy(levelToCoarsenBy: number): CoarsenedGraph;
20
- private coarsen;
21
- }
@@ -1,2 +0,0 @@
1
- import type { Point } from '../../../../utils/geometry';
2
- export declare const calculatePositions: (centre: Point, r: number, noOfChildren: number) => any[];