@loradb/lora-graph-canvas 0.10.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.
- package/LICENSE +87 -0
- package/README.md +191 -0
- package/THIRD_PARTY.md +40 -0
- package/dist/LoraGraphCanvas.d.ts +9 -0
- package/dist/LoraGraphCanvas.stories.d.ts +23 -0
- package/dist/engines/3d-force-graph/index.d.ts +1 -0
- package/dist/engines/3d-force-graph/kapsule.d.ts +12 -0
- package/dist/engines/createEngineUnified.d.ts +13 -0
- package/dist/engines/propBindings.d.ts +29 -0
- package/dist/engines/rafAnim.d.ts +13 -0
- package/dist/engines/types.d.ts +86 -0
- package/dist/hooks/useAccessorOverrides.d.ts +62 -0
- package/dist/hooks/useAutoIndexNeighbors.d.ts +6 -0
- package/dist/hooks/useClickToleranceShim.d.ts +46 -0
- package/dist/hooks/useGraphClipboard.d.ts +47 -0
- package/dist/hooks/useGraphData.d.ts +42 -0
- package/dist/hooks/useGraphEngine.d.ts +23 -0
- package/dist/hooks/useGraphForces.d.ts +12 -0
- package/dist/hooks/useGraphKeybindings.d.ts +27 -0
- package/dist/hooks/useGraphSelection.d.ts +14 -0
- package/dist/hooks/useHoverState.d.ts +67 -0
- package/dist/hooks/useImperativeGraphHandle.d.ts +22 -0
- package/dist/hooks/useLabelRenderer.d.ts +47 -0
- package/dist/hooks/useLinkLabelRenderer.d.ts +56 -0
- package/dist/hooks/useMarqueeAndCursor.d.ts +59 -0
- package/dist/hooks/usePerfTierDefaults.d.ts +13 -0
- package/dist/hooks/useResizeObserver.d.ts +7 -0
- package/dist/hooks/useShiftHeld.d.ts +5 -0
- package/dist/index.cjs +685 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +11309 -0
- package/dist/index.js.map +1 -0
- package/dist/internal/accessor-fn.d.ts +2 -0
- package/dist/internal/bezier.d.ts +16 -0
- package/dist/internal/canvas-color-tracker.d.ts +13 -0
- package/dist/internal/debounce.d.ts +5 -0
- package/dist/internal/float-tooltip.d.ts +14 -0
- package/dist/internal/kapsule-link.d.ts +24 -0
- package/dist/internal/kapsule.d.ts +43 -0
- package/dist/internal/throttle.d.ts +6 -0
- package/dist/internal/tween.d.ts +31 -0
- package/dist/style.css +1 -0
- package/dist/theme/presets.d.ts +8 -0
- package/dist/tools/ContextMenu.d.ts +17 -0
- package/dist/tools/GraphToolbar.d.ts +18 -0
- package/dist/tools/GroupLegend.d.ts +11 -0
- package/dist/tools/HoverTooltip.d.ts +15 -0
- package/dist/tools/MarqueeOverlay.d.ts +13 -0
- package/dist/tools/ModeToggle.d.ts +10 -0
- package/dist/tools/OptionsMenu.d.ts +28 -0
- package/dist/tools/SelectionPanel.d.ts +18 -0
- package/dist/tools/icons.d.ts +23 -0
- package/dist/tools/tools.d.ts +37 -0
- package/dist/types.d.ts +375 -0
- package/dist/utils/accessor.d.ts +36 -0
- package/dist/utils/download.d.ts +4 -0
- package/dist/utils/geometry.d.ts +8 -0
- package/dist/utils/grid.d.ts +9 -0
- package/dist/utils/ids.d.ts +3 -0
- package/dist/utils/perfTier.d.ts +29 -0
- package/dist/utils/spriteLabel.d.ts +61 -0
- package/dist/utils/themeStyle.d.ts +5 -0
- package/package.json +105 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
Business Source License 1.1
|
|
2
|
+
|
|
3
|
+
Parameters
|
|
4
|
+
|
|
5
|
+
Licensor: LoraDB, Inc.
|
|
6
|
+
Licensed Work: LoraDB
|
|
7
|
+
Change Date: 2029-04-19
|
|
8
|
+
Change License: Apache License, Version 2.0
|
|
9
|
+
|
|
10
|
+
Additional Use Grant: You may use, copy, modify, create derivative works of,
|
|
11
|
+
distribute, and make production use of the Licensed Work
|
|
12
|
+
for internal business purposes and non-production
|
|
13
|
+
purposes, provided that you do not use the Licensed Work
|
|
14
|
+
to offer, operate, or make available a database-as-a-
|
|
15
|
+
service, hosted API, managed database platform, or any
|
|
16
|
+
substantially similar hosted service for third parties.
|
|
17
|
+
|
|
18
|
+
The Additional Use Grant does not permit using the
|
|
19
|
+
Licensed Work to provide a commercial or non-commercial
|
|
20
|
+
hosted service that allows third parties to access LoraDB
|
|
21
|
+
functionality, including as part of a competing managed
|
|
22
|
+
database platform, backend-as-a-service, application
|
|
23
|
+
platform, developer platform, or resale offering.
|
|
24
|
+
|
|
25
|
+
Business Source License
|
|
26
|
+
|
|
27
|
+
Terms
|
|
28
|
+
|
|
29
|
+
The Licensor hereby grants you the right to copy, modify, create derivative
|
|
30
|
+
works, redistribute, and make non-production use of the Licensed Work. The
|
|
31
|
+
Licensor may make an Additional Use Grant, above, permitting limited production
|
|
32
|
+
use.
|
|
33
|
+
|
|
34
|
+
Effective on the Change Date, or the fourth anniversary of the first publicly
|
|
35
|
+
available distribution of a specific version of the Licensed Work under this
|
|
36
|
+
License, whichever comes first, the Licensor hereby grants you rights under
|
|
37
|
+
the terms of the Change License, and the rights granted in the paragraph
|
|
38
|
+
above terminate.
|
|
39
|
+
|
|
40
|
+
If your use of the Licensed Work does not comply with the requirements
|
|
41
|
+
currently in effect as described in this License, you must purchase a
|
|
42
|
+
commercial license from the Licensor, its affiliated entities, or authorized
|
|
43
|
+
resellers, or you must refrain from using the Licensed Work.
|
|
44
|
+
|
|
45
|
+
All copies of the original and modified Licensed Work, and derivative works
|
|
46
|
+
of the Licensed Work, are subject to this License. This License applies
|
|
47
|
+
separately for each version of the Licensed Work and the Change Date may vary
|
|
48
|
+
for each version of the Licensed Work released by Licensor.
|
|
49
|
+
|
|
50
|
+
You must conspicuously display this License on each original or modified copy
|
|
51
|
+
of the Licensed Work. If you receive the Licensed Work in original or modified
|
|
52
|
+
form from a third party, the terms and conditions set forth in this License
|
|
53
|
+
apply to your use of that work.
|
|
54
|
+
|
|
55
|
+
Any use of the Licensed Work in violation of this License will automatically
|
|
56
|
+
terminate your rights under this License for the current and all other
|
|
57
|
+
versions of the Licensed Work.
|
|
58
|
+
|
|
59
|
+
This License does not grant you any right in any trademark or logo of Licensor
|
|
60
|
+
or its affiliates.
|
|
61
|
+
|
|
62
|
+
TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON
|
|
63
|
+
AN "AS IS" BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS,
|
|
64
|
+
EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION WARRANTIES OF MERCHANTABILITY,
|
|
65
|
+
FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND TITLE.
|
|
66
|
+
|
|
67
|
+
MariaDB hereby grants you permission to use this License's text to license
|
|
68
|
+
your works, and to refer to it using the trademark "Business Source License",
|
|
69
|
+
as long as you comply with the Covenants of Licensor below.
|
|
70
|
+
|
|
71
|
+
Covenants of Licensor
|
|
72
|
+
|
|
73
|
+
In consideration of the right to use this License's text and the "Business
|
|
74
|
+
Source License" name and trademark, Licensor covenants to MariaDB, and to all
|
|
75
|
+
other recipients of the licensed work to be provided by Licensor:
|
|
76
|
+
|
|
77
|
+
1. To specify as the Change License the GPL Version 2.0 or any later version,
|
|
78
|
+
or a license that is compatible with GPL Version 2.0 or a later version,
|
|
79
|
+
where "compatible" means that software provided under the Change License can
|
|
80
|
+
be included in a program with software provided under GPL Version 2.0 or a
|
|
81
|
+
later version. Licensor may specify additional Change Licenses without
|
|
82
|
+
limitation.
|
|
83
|
+
2. To either: (a) specify an additional grant of rights to use that does not
|
|
84
|
+
impose any additional restriction on the right granted in this License, as
|
|
85
|
+
the Additional Use Grant; or (b) insert the text "None".
|
|
86
|
+
3. To specify a Change Date.
|
|
87
|
+
4. Not to modify this License in any other way.
|
package/README.md
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
# @loradb/lora-graph-canvas
|
|
2
|
+
|
|
3
|
+
React graph canvas for LoraDB. One component, `<LoraGraphCanvas />`, that
|
|
4
|
+
wraps `force-graph` (2D HTML5 canvas) and `3d-force-graph` (WebGL via
|
|
5
|
+
Three.js). Switch between 2D and 3D at runtime without re-mounting your
|
|
6
|
+
data, and use the built-in tool palette to build, edit, select, and
|
|
7
|
+
delete nodes interactively.
|
|
8
|
+
|
|
9
|
+
```tsx
|
|
10
|
+
import { LoraGraphCanvas } from "@loradb/lora-graph-canvas";
|
|
11
|
+
import "@loradb/lora-graph-canvas/styles.css";
|
|
12
|
+
|
|
13
|
+
<LoraGraphCanvas
|
|
14
|
+
defaultData={{
|
|
15
|
+
nodes: [{ id: "a" }, { id: "b" }],
|
|
16
|
+
links: [{ source: "a", target: "b" }],
|
|
17
|
+
}}
|
|
18
|
+
nodeLabel="id"
|
|
19
|
+
nodeAutoColorBy="group"
|
|
20
|
+
/>;
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Install
|
|
24
|
+
|
|
25
|
+
```sh
|
|
26
|
+
yarn add @loradb/lora-graph-canvas three
|
|
27
|
+
# or
|
|
28
|
+
npm install @loradb/lora-graph-canvas three
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
`three` is a required peer dependency — the package itself does not bundle
|
|
32
|
+
Three.js, so 2D-only consumers can dedupe with the rest of the app's
|
|
33
|
+
Three usage, and 3D consumers can pin a specific version.
|
|
34
|
+
|
|
35
|
+
## Built-in tools
|
|
36
|
+
|
|
37
|
+
The toolbar ships with twelve tools, controllable individually:
|
|
38
|
+
|
|
39
|
+
| Tool | Shortcut | What it does |
|
|
40
|
+
| ------------ | -------- | -------------------------------------------------- |
|
|
41
|
+
| Select | `V` | Click / shift-click nodes to select them. |
|
|
42
|
+
| Pan | `H` | Pan-only cursor. |
|
|
43
|
+
| Add node | `N` | Click on the canvas to drop a node. |
|
|
44
|
+
| Add link | `L` | Click two nodes to connect them. |
|
|
45
|
+
| Delete | `⌫` / `⌦` | Delete the current selection (cascades links). |
|
|
46
|
+
| Fit | `F` | `zoomToFit` to the current graph bbox. |
|
|
47
|
+
| Zoom in/out | `+` `-` | Step the zoom level. |
|
|
48
|
+
| Pause/Resume | — | Stop / start the d3 simulation. |
|
|
49
|
+
| Screenshot | — | Download a PNG of the canvas. |
|
|
50
|
+
| Toggle 2D/3D | `3` | Swap engines, preserving data and selection. |
|
|
51
|
+
|
|
52
|
+
```tsx
|
|
53
|
+
// Pick a subset…
|
|
54
|
+
<LoraGraphCanvas tools={["select", "add-node", "delete", "toggle-mode"]} />
|
|
55
|
+
|
|
56
|
+
// …or hide the whole bar and drive everything from the ref:
|
|
57
|
+
<LoraGraphCanvas tools={false} ref={ref} />
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Ref handle
|
|
61
|
+
|
|
62
|
+
```tsx
|
|
63
|
+
import { useRef } from "react";
|
|
64
|
+
import {
|
|
65
|
+
LoraGraphCanvas,
|
|
66
|
+
type LoraGraphCanvasHandle,
|
|
67
|
+
} from "@loradb/lora-graph-canvas";
|
|
68
|
+
|
|
69
|
+
const ref = useRef<LoraGraphCanvasHandle>(null);
|
|
70
|
+
|
|
71
|
+
ref.current?.addNode({ id: "x", label: "hello" });
|
|
72
|
+
ref.current?.addLink({ source: "x", target: "y" });
|
|
73
|
+
ref.current?.removeNode("x"); // also removes attached links
|
|
74
|
+
ref.current?.fit(400, 40);
|
|
75
|
+
ref.current?.setMode("3d");
|
|
76
|
+
const blob = await ref.current?.screenshot();
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Available methods: data (`getData`, `setData`, `addNode`, `addNodes`,
|
|
80
|
+
`updateNode`, `removeNode`, `removeNodes`, `addLink`, `addLinks`,
|
|
81
|
+
`removeLink`, `clear`); selection (`getSelection`, `setSelection`,
|
|
82
|
+
`selectAll`, `clearSelection`); view (`getMode`, `setMode`, `fit`,
|
|
83
|
+
`centerAt`, `zoom`, `zoomIn`, `zoomOut`); engine (`pause`, `resume`,
|
|
84
|
+
`reheat`, `screenshot`); escape hatches (`engine2D`, `engine3D`).
|
|
85
|
+
|
|
86
|
+
## Controlled vs uncontrolled
|
|
87
|
+
|
|
88
|
+
```tsx
|
|
89
|
+
// Uncontrolled — internal state owns the graph, host gets a notification:
|
|
90
|
+
<LoraGraphCanvas defaultData={initialData} onDataChange={save} />
|
|
91
|
+
|
|
92
|
+
// Controlled — host owns the graph:
|
|
93
|
+
<LoraGraphCanvas data={dataFromState} onDataChange={setDataState} />
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
The same dichotomy applies to `mode` (`defaultMode` vs `mode`).
|
|
97
|
+
|
|
98
|
+
## Theming
|
|
99
|
+
|
|
100
|
+
The chrome (toolbar, context menu, tooltips) is driven by `--lgc-*` CSS
|
|
101
|
+
variables. Override the ones you want either through the `theme` prop or
|
|
102
|
+
by attaching CSS to the `.lora-graph-canvas` container. The engine's own
|
|
103
|
+
canvas reads `backgroundColor` from a regular prop, independent of the
|
|
104
|
+
theme.
|
|
105
|
+
|
|
106
|
+
```tsx
|
|
107
|
+
import { LoraGraphCanvas, darkTheme } from "@loradb/lora-graph-canvas";
|
|
108
|
+
|
|
109
|
+
<LoraGraphCanvas
|
|
110
|
+
backgroundColor="#0e1014"
|
|
111
|
+
theme={{ ...darkTheme, accent: "#ff6699" }}
|
|
112
|
+
/>;
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Two presets are exported: `lightTheme` and `darkTheme`.
|
|
116
|
+
|
|
117
|
+
## Performance knobs
|
|
118
|
+
|
|
119
|
+
For large graphs, cap `cooldownTicks` (default ∞) and increase
|
|
120
|
+
`warmupTicks` to spend more time computing layout off-screen before the
|
|
121
|
+
first paint:
|
|
122
|
+
|
|
123
|
+
```tsx
|
|
124
|
+
<LoraGraphCanvas cooldownTicks={50} warmupTicks={20} />
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
For graphs above ~10k nodes, switch the layout engine in 3D mode:
|
|
128
|
+
|
|
129
|
+
```tsx
|
|
130
|
+
<LoraGraphCanvas mode="3d" forceEngine="ngraph" />
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## Opt-in UX flags
|
|
134
|
+
|
|
135
|
+
Each is a single boolean prop:
|
|
136
|
+
|
|
137
|
+
| Prop | What it does |
|
|
138
|
+
| --- | --- |
|
|
139
|
+
| `focusOnClick` | Click a node → animated camera focus; click again to restore. |
|
|
140
|
+
| `highlightNeighborsOnHover` | Hover a node → it + its neighbours light up in the accent color. Pair with `autoIndexNeighbors` so the component builds the neighbour index for you, or stash `_neighbors` / `_links` yourself. |
|
|
141
|
+
| `autoIndexNeighbors` | After every data change, build `_neighbors` and `_links` arrays on each node — required by the highlight flag if you don't provide them. |
|
|
142
|
+
| `collideNodes` | Inject `d3-force-3d`'s `forceCollide` so circles don't overlap. Pass a number to override the radius. |
|
|
143
|
+
| `showGrid` | Faint background grid that adapts to the zoom level. 2D only. Pass `{ spacing, color }` to customise. |
|
|
144
|
+
| `showLegend` | Bottom-left widget enumerating `nodeAutoColorBy` groups with a colour swatch; click a group to toggle its visibility (drives `nodeVisibility` automatically). |
|
|
145
|
+
| `enableRename` | (Default true.) Double-click a node to rename it inline. Set false to suppress. |
|
|
146
|
+
| `enableClipboard` | (Default true.) ⌘C / ⌘X / ⌘V keybindings + matching ref methods. |
|
|
147
|
+
|
|
148
|
+
```tsx
|
|
149
|
+
<LoraGraphCanvas
|
|
150
|
+
data={graph}
|
|
151
|
+
nodeAutoColorBy="group"
|
|
152
|
+
focusOnClick
|
|
153
|
+
highlightNeighborsOnHover
|
|
154
|
+
autoIndexNeighbors
|
|
155
|
+
collideNodes
|
|
156
|
+
showGrid
|
|
157
|
+
showLegend
|
|
158
|
+
/>
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
## Animated particles for events
|
|
162
|
+
|
|
163
|
+
The kapsule emits an animated particle along a link via `emitParticle`.
|
|
164
|
+
Wire it through the ref to visualise events (data flow, message sent,
|
|
165
|
+
etc.):
|
|
166
|
+
|
|
167
|
+
```tsx
|
|
168
|
+
const ref = useRef<LoraGraphCanvasHandle>(null);
|
|
169
|
+
useEffect(() => {
|
|
170
|
+
socket.on("message", (msg) => {
|
|
171
|
+
const link = graph.links.find((l) => l.id === msg.linkId);
|
|
172
|
+
if (link) ref.current?.emitParticle(link);
|
|
173
|
+
});
|
|
174
|
+
}, []);
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## Custom forces
|
|
178
|
+
|
|
179
|
+
Inject d3-force primitives through `d3Force(name, fn)`:
|
|
180
|
+
|
|
181
|
+
```tsx
|
|
182
|
+
import { forceRadial } from "d3-force-3d";
|
|
183
|
+
|
|
184
|
+
ref.current?.d3Force("radial", forceRadial(200));
|
|
185
|
+
ref.current?.reheat();
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
## License
|
|
189
|
+
|
|
190
|
+
BUSL-1.1. Third-party attributions for `force-graph` and `3d-force-graph`
|
|
191
|
+
(MIT, © Vasco Asturiano) live in `THIRD_PARTY.md`.
|
package/THIRD_PARTY.md
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Third-party notices
|
|
2
|
+
|
|
3
|
+
This package depends on the following open-source projects. Their license
|
|
4
|
+
texts are reproduced below in full.
|
|
5
|
+
|
|
6
|
+
## force-graph
|
|
7
|
+
|
|
8
|
+
- Copyright (c) Vasco Asturiano
|
|
9
|
+
- License: MIT
|
|
10
|
+
- Source: https://github.com/vasturiano/force-graph
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
MIT License
|
|
14
|
+
|
|
15
|
+
Permission is hereby granted, free of charge, to any person obtaining a
|
|
16
|
+
copy of this software and associated documentation files (the "Software"),
|
|
17
|
+
to deal in the Software without restriction, including without limitation
|
|
18
|
+
the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
19
|
+
and/or sell copies of the Software, and to permit persons to whom the
|
|
20
|
+
Software is furnished to do so, subject to the following conditions:
|
|
21
|
+
|
|
22
|
+
The above copyright notice and this permission notice shall be included
|
|
23
|
+
in all copies or substantial portions of the Software.
|
|
24
|
+
|
|
25
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
|
26
|
+
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
27
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
28
|
+
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
|
29
|
+
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
|
30
|
+
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
|
31
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## 3d-force-graph
|
|
35
|
+
|
|
36
|
+
- Copyright (c) Vasco Asturiano
|
|
37
|
+
- License: MIT
|
|
38
|
+
- Source: https://github.com/vasturiano/3d-force-graph
|
|
39
|
+
|
|
40
|
+
The MIT license text above applies identically.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { LinkObject, LoraGraphCanvasHandle, LoraGraphCanvasProps, NodeObject } from './types';
|
|
2
|
+
declare function LoraGraphCanvasInner<N extends NodeObject = NodeObject, L extends LinkObject = LinkObject>(props: LoraGraphCanvasProps<N, L>, ref: React.Ref<LoraGraphCanvasHandle<N, L>>): import("react/jsx-runtime").JSX.Element;
|
|
3
|
+
/** React component exporting both a default-instantiated and a generic
|
|
4
|
+
* signature. Consumers can pass `<LoraGraphCanvas<MyNode, MyLink> ... />`
|
|
5
|
+
* to constrain the node / link shape. */
|
|
6
|
+
export declare const LoraGraphCanvas: <N extends NodeObject = NodeObject, L extends LinkObject = LinkObject>(props: LoraGraphCanvasProps<N, L> & {
|
|
7
|
+
ref?: React.Ref<LoraGraphCanvasHandle<N, L>>;
|
|
8
|
+
}) => ReturnType<typeof LoraGraphCanvasInner>;
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
import { LoraGraphCanvas } from '.';
|
|
3
|
+
declare const meta: Meta<typeof LoraGraphCanvas>;
|
|
4
|
+
export default meta;
|
|
5
|
+
type Story = StoryObj<typeof LoraGraphCanvas>;
|
|
6
|
+
export declare const Basic: Story;
|
|
7
|
+
export declare const ThreeDimensional: Story;
|
|
8
|
+
export declare const ModeToggle: Story;
|
|
9
|
+
export declare const BuildAGraphStory: Story;
|
|
10
|
+
export declare const HeadlessStory: Story;
|
|
11
|
+
export declare const LargeGraphStory: Story;
|
|
12
|
+
export declare const StressGraphStory: Story;
|
|
13
|
+
export declare const DagStoryStory: Story;
|
|
14
|
+
export declare const Theming: Story;
|
|
15
|
+
export declare const HighlightNeighbors: Story;
|
|
16
|
+
export declare const ClickToFocus: Story;
|
|
17
|
+
export declare const Grid: Story;
|
|
18
|
+
export declare const Collide: Story;
|
|
19
|
+
export declare const Legend: Story;
|
|
20
|
+
export declare const Beeswarm: Story;
|
|
21
|
+
export declare const EmitParticle: Story;
|
|
22
|
+
export declare const RandomGraphStory: Story;
|
|
23
|
+
export declare const LabeledStory: Story;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from './kapsule';
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { WebGLRendererParameters } from 'three';
|
|
2
|
+
import { KapsuleClassCtor } from '../../internal/kapsule';
|
|
3
|
+
export interface ForceGraph3DConfigOptions {
|
|
4
|
+
controlType?: "trackball" | "orbit" | "fly";
|
|
5
|
+
rendererConfig?: WebGLRendererParameters;
|
|
6
|
+
/** Additional renderers (e.g. CSS2DRenderer for HTML overlays).
|
|
7
|
+
* Typed loosely — three.js no longer exports a base `Renderer`
|
|
8
|
+
* interface in newer revisions. */
|
|
9
|
+
extraRenderers?: unknown[];
|
|
10
|
+
}
|
|
11
|
+
declare const ForceGraph3D: KapsuleClassCtor;
|
|
12
|
+
export default ForceGraph3D;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { CreateEngineOptions, GraphEngine } from './types';
|
|
2
|
+
import { GraphMode, LinkObject, LoraGraphCanvasProps, NodeObject } from '../types';
|
|
3
|
+
export interface UnifiedEngine<N extends NodeObject = NodeObject, L extends LinkObject = LinkObject> extends GraphEngine<N, L> {
|
|
4
|
+
/** Switch presentation mode in place. Animates the camera + the
|
|
5
|
+
* per-node z constraints; does not destroy the engine. */
|
|
6
|
+
setMode(mode: GraphMode, durationMs?: number): void;
|
|
7
|
+
}
|
|
8
|
+
export declare function createEngineUnified<N extends NodeObject, L extends LinkObject>(mount: HTMLElement, opts: CreateEngineOptions<N, L> & {
|
|
9
|
+
initialMode: GraphMode;
|
|
10
|
+
}, handlerRef: {
|
|
11
|
+
current: LoraGraphCanvasProps<N, L>;
|
|
12
|
+
}): UnifiedEngine<N, L>;
|
|
13
|
+
export type EngineUnified = ReturnType<typeof createEngineUnified>;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { LinkObject, LoraGraphCanvasProps, NodeObject } from '../types';
|
|
2
|
+
/** A kapsule prop the engine exposes as a chainable setter. */
|
|
3
|
+
export interface PropBinding<P> {
|
|
4
|
+
/** Name of the prop on `LoraGraphCanvasProps` we read from. */
|
|
5
|
+
key: keyof LoraGraphCanvasProps<NodeObject, LinkObject>;
|
|
6
|
+
/** Name of the chainable setter on the engine instance. */
|
|
7
|
+
setter: string;
|
|
8
|
+
/** Whether this binding is supported in the current engine mode. */
|
|
9
|
+
supports: (mode: "2d" | "3d") => boolean;
|
|
10
|
+
/** Optional transformer applied before passing the value to the setter. */
|
|
11
|
+
transform?: (value: unknown, props: P) => unknown;
|
|
12
|
+
}
|
|
13
|
+
/** Bindings shared between 2D and 3D engines. Order doesn't matter; the
|
|
14
|
+
* adapter just walks the list each render. */
|
|
15
|
+
export declare const SHARED_BINDINGS: PropBinding<unknown>[];
|
|
16
|
+
/** Walk the bindings, calling each engine setter for props whose value
|
|
17
|
+
* changed (using Object.is). The engine instance is treated as a loose
|
|
18
|
+
* record of chainable setters so this works for both kapsule types. */
|
|
19
|
+
export declare function applyDiffedProps<N extends NodeObject, L extends LinkObject>(engine: Record<string, (value: unknown) => unknown>, props: LoraGraphCanvasProps<N, L>, prev: LoraGraphCanvasProps<N, L>, mode: "2d" | "3d"): void;
|
|
20
|
+
/** Event-handler bindings. These are wired once at engine construction;
|
|
21
|
+
* the React layer forwards through a stable trampoline so latest props
|
|
22
|
+
* always win without re-binding. */
|
|
23
|
+
export declare const EVENT_BINDINGS: readonly ["onNodeClick", "onNodeRightClick", "onNodeHover", "onNodeDrag", "onNodeDragEnd", "onLinkClick", "onLinkRightClick", "onLinkHover", "onBackgroundClick", "onBackgroundRightClick", "onEngineTick", "onEngineStop"];
|
|
24
|
+
/** 2D-only event bindings (zoom + render-frame callbacks). The 3D
|
|
25
|
+
* kapsule doesn't expose these — they're driven by Three.js controls
|
|
26
|
+
* there. We wire them in the same trampoline way for consistency. */
|
|
27
|
+
export declare const EVENT_BINDINGS_2D: readonly ["onZoom", "onZoomEnd", "onRenderFramePre", "onRenderFramePost"];
|
|
28
|
+
export type EventName = (typeof EVENT_BINDINGS)[number];
|
|
29
|
+
export type EventName2D = (typeof EVENT_BINDINGS_2D)[number];
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** A tiny RAF-driven tween we use instead of the kapsule's built-in
|
|
2
|
+
* tween group. The kapsule's tweens live inside a private state
|
|
3
|
+
* object — there's no public API for cancelling them once started.
|
|
4
|
+
* Doing the animation ourselves lets us stop it the instant the
|
|
5
|
+
* user interacts with the canvas.
|
|
6
|
+
*
|
|
7
|
+
* Each `runAnim` returns a `cancel()` function; calling it freezes
|
|
8
|
+
* the animation at the current frame (no further `step` invocations).
|
|
9
|
+
* Returns null when running in an environment without
|
|
10
|
+
* `requestAnimationFrame` (jsdom in unit tests). */
|
|
11
|
+
export declare function runAnim(durationMs: number, step: (t: number) => void, onDone?: () => void): () => void;
|
|
12
|
+
export declare function easeOutQuad(t: number): number;
|
|
13
|
+
export declare function lerp(a: number, b: number, t: number): number;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { GraphData, GraphMode, LinkObject, NodeObject, LoraGraphCanvasProps } from '../types';
|
|
2
|
+
/** Camera state snapshot. The shape differs per mode so we can
|
|
3
|
+
* faithfully restore in both 2D (pan + zoom) and 3D (position +
|
|
4
|
+
* lookAt). */
|
|
5
|
+
export type CameraState = {
|
|
6
|
+
mode: "2d";
|
|
7
|
+
x: number;
|
|
8
|
+
y: number;
|
|
9
|
+
k: number;
|
|
10
|
+
} | {
|
|
11
|
+
mode: "3d";
|
|
12
|
+
x: number;
|
|
13
|
+
y: number;
|
|
14
|
+
z: number;
|
|
15
|
+
lookAt: {
|
|
16
|
+
x: number;
|
|
17
|
+
y: number;
|
|
18
|
+
z: number;
|
|
19
|
+
};
|
|
20
|
+
};
|
|
21
|
+
/** Subset of the kapsule API both 2D and 3D engines implement. Each
|
|
22
|
+
* adapter normalises naming so the React layer never has to special-case
|
|
23
|
+
* the dimension. */
|
|
24
|
+
export interface GraphEngine<N extends NodeObject = NodeObject, L extends LinkObject = LinkObject> {
|
|
25
|
+
mode: GraphMode;
|
|
26
|
+
setGraphData(data: GraphData<N, L>): void;
|
|
27
|
+
getGraphData(): GraphData<N, L>;
|
|
28
|
+
fit(durationMs?: number, padding?: number): void;
|
|
29
|
+
centerAt(x: number, y: number, z?: number, durationMs?: number): void;
|
|
30
|
+
zoom(scale: number, durationMs?: number): void;
|
|
31
|
+
getZoom(): number;
|
|
32
|
+
screen2Graph(x: number, y: number, distance?: number): {
|
|
33
|
+
x: number;
|
|
34
|
+
y: number;
|
|
35
|
+
z?: number;
|
|
36
|
+
};
|
|
37
|
+
graph2Screen(x: number, y: number, z?: number): {
|
|
38
|
+
x: number;
|
|
39
|
+
y: number;
|
|
40
|
+
z?: number;
|
|
41
|
+
};
|
|
42
|
+
getGraphBbox(): {
|
|
43
|
+
x: [number, number];
|
|
44
|
+
y: [number, number];
|
|
45
|
+
z?: [number, number];
|
|
46
|
+
};
|
|
47
|
+
pause(): void;
|
|
48
|
+
resume(): void;
|
|
49
|
+
reheat(): void;
|
|
50
|
+
resize(width: number, height: number): void;
|
|
51
|
+
/** Get / set / clear a d3-force by name. Pass `null` to remove. */
|
|
52
|
+
d3Force(name: string, fn?: unknown | null): unknown;
|
|
53
|
+
/** Emit a one-off animated particle along the given link. */
|
|
54
|
+
emitParticle(link: L): void;
|
|
55
|
+
/** Snap any in-flight camera tween (centerAt / zoom / cameraPosition)
|
|
56
|
+
* to its current state. Used to interrupt a focus animation when
|
|
57
|
+
* the user starts a new interaction. No-op when nothing is
|
|
58
|
+
* animating. */
|
|
59
|
+
stopAnimation(): void;
|
|
60
|
+
/** Animate the camera so the given target point is centered, while
|
|
61
|
+
* preserving the current viewing angle. In 3D the camera flies
|
|
62
|
+
* along its current vector from the target so the user's orbit
|
|
63
|
+
* is kept; in 2D it's a `centerAt` + `zoom`. */
|
|
64
|
+
focusOn(target: {
|
|
65
|
+
x: number;
|
|
66
|
+
y: number;
|
|
67
|
+
z?: number;
|
|
68
|
+
}, opts?: {
|
|
69
|
+
distance?: number;
|
|
70
|
+
zoom?: number;
|
|
71
|
+
durationMs?: number;
|
|
72
|
+
}): void;
|
|
73
|
+
/** Snapshot the current camera so it can be restored later. */
|
|
74
|
+
getCameraState(): CameraState;
|
|
75
|
+
/** Restore a snapshot produced by `getCameraState`. */
|
|
76
|
+
setCameraState(state: CameraState, durationMs?: number): void;
|
|
77
|
+
applyProps(props: LoraGraphCanvasProps<N, L>, prev: LoraGraphCanvasProps<N, L>): void;
|
|
78
|
+
getCanvasElement(): HTMLCanvasElement | null;
|
|
79
|
+
destroy(): void;
|
|
80
|
+
}
|
|
81
|
+
export interface CreateEngineOptions<N extends NodeObject = NodeObject, L extends LinkObject = LinkObject> {
|
|
82
|
+
initialProps: LoraGraphCanvasProps<N, L>;
|
|
83
|
+
initialData: GraphData<N, L>;
|
|
84
|
+
width: number;
|
|
85
|
+
height: number;
|
|
86
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { Accessor, GraphMode, LinkObject, NodeObject } from '../types';
|
|
2
|
+
export type NodePointerAreaPaint<N extends NodeObject> = (node: N, color: string, ctx: CanvasRenderingContext2D, globalScale: number) => void;
|
|
3
|
+
export interface UseAccessorOverridesParams<N extends NodeObject, L extends LinkObject> {
|
|
4
|
+
mode: GraphMode;
|
|
5
|
+
accentColor: string;
|
|
6
|
+
nodeColor?: Accessor<string, N>;
|
|
7
|
+
linkColor?: Accessor<string, L>;
|
|
8
|
+
linkWidth?: Accessor<number, L>;
|
|
9
|
+
nodeVal?: Accessor<number, N>;
|
|
10
|
+
nodeRelSize?: number;
|
|
11
|
+
nodePointerAreaPaint?: NodePointerAreaPaint<N>;
|
|
12
|
+
nodeAutoColorBy?: Accessor<string | null, N>;
|
|
13
|
+
nodeVisibility?: Accessor<boolean, N>;
|
|
14
|
+
selectedNodeSet: ReadonlySet<string | number>;
|
|
15
|
+
selectedLinkSet: ReadonlySet<string | number>;
|
|
16
|
+
highlightNeighborsOnHover: boolean;
|
|
17
|
+
highlightedNodeIds: ReadonlySet<string | number>;
|
|
18
|
+
highlightedLinkIds: ReadonlySet<string | number>;
|
|
19
|
+
hoverNodeId: string | number | null;
|
|
20
|
+
/** Direct-link hover. Distinct from `highlightedLinkIds`, which
|
|
21
|
+
* only fires when a node is hovered and its neighbour links light
|
|
22
|
+
* up. When the user hovers a link itself, only `hoverLinkId` is
|
|
23
|
+
* set — both signals need to flow into the size/colour wrappers
|
|
24
|
+
* so the link bumps up on direct hover, not just node-neighbour
|
|
25
|
+
* hover. */
|
|
26
|
+
hoverLinkId: string | number | null;
|
|
27
|
+
hiddenGroups: ReadonlySet<string>;
|
|
28
|
+
}
|
|
29
|
+
export interface AccessorOverrides<N extends NodeObject, L extends LinkObject> {
|
|
30
|
+
nodeColor: Accessor<string, N> | undefined;
|
|
31
|
+
linkColor: Accessor<string, L> | undefined;
|
|
32
|
+
linkWidth: Accessor<number, L> | undefined;
|
|
33
|
+
nodeVal: Accessor<number, N> | undefined;
|
|
34
|
+
nodePointerAreaPaint: NodePointerAreaPaint<N> | undefined;
|
|
35
|
+
nodeVisibility: ((node: N) => boolean) | undefined;
|
|
36
|
+
}
|
|
37
|
+
/** Wrap the host's color / width / value / visibility accessors so the
|
|
38
|
+
* current selection + hover-highlight + legend-filter overlay on top
|
|
39
|
+
* without losing the underlying styling.
|
|
40
|
+
*
|
|
41
|
+
* Each wrapper is conditional in two ways:
|
|
42
|
+
*
|
|
43
|
+
* 1. When there's nothing to overlay (no selection, no hover, no
|
|
44
|
+
* legend filter) we return the host's accessor *untouched*. The
|
|
45
|
+
* kapsule never enters our closure on hot paths in that case.
|
|
46
|
+
*
|
|
47
|
+
* 2. When an overlay is active, the wrapper identity flips on every
|
|
48
|
+
* selection/hover state change. This is REQUIRED — three-forcegraph's
|
|
49
|
+
* link/node digest (see `three-forcegraph.mjs:1185`) only re-evaluates
|
|
50
|
+
* accessors when one of its tracked props changes identity. Keeping
|
|
51
|
+
* the wrapper "stable" through selection changes — as an earlier
|
|
52
|
+
* iteration of this hook did — caused the kapsule to cache the colour
|
|
53
|
+
* and width of whichever link the user first clicked and never update
|
|
54
|
+
* them on subsequent clicks, despite the React state being correct.
|
|
55
|
+
*
|
|
56
|
+
* The closure body still reads the latest state from a ref, but the
|
|
57
|
+
* memo deps include every overlay signal so the identity flips with
|
|
58
|
+
* each tick of the state machine. The kapsule's own internal digest
|
|
59
|
+
* is smart enough to only rebuild geometries/materials whose value
|
|
60
|
+
* actually changed (cheap on small per-click selection deltas), so
|
|
61
|
+
* the cost of identity churn here is bounded. */
|
|
62
|
+
export declare function useAccessorOverrides<N extends NodeObject, L extends LinkObject>(params: UseAccessorOverridesParams<N, L>): AccessorOverrides<N, L>;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { GraphData, LinkObject, NodeObject } from '../types';
|
|
2
|
+
/** When enabled, walk the graph once on every data change and stash
|
|
3
|
+
* `_neighbors` (Node[]) and `_links` (Link[]) arrays on each node. The
|
|
4
|
+
* hover-highlight code reads from these refs directly so the per-frame
|
|
5
|
+
* work stays O(1). */
|
|
6
|
+
export declare function useAutoIndexNeighbors<N extends NodeObject, L extends LinkObject>(enabled: boolean, data: GraphData<N, L>): void;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
export interface ClickToleranceShimOptions {
|
|
2
|
+
/** Distance in CSS pixels the cursor may travel from the press
|
|
3
|
+
* position before the gesture is committed as a drag. */
|
|
4
|
+
tolerancePx?: number;
|
|
5
|
+
/** Milliseconds after `pointerdown` during which every move is
|
|
6
|
+
* suppressed regardless of distance, so press-induced jitter never
|
|
7
|
+
* promotes a click into a drag. */
|
|
8
|
+
pressGraceMs?: number;
|
|
9
|
+
}
|
|
10
|
+
/** Click-vs-drag tolerance shim.
|
|
11
|
+
*
|
|
12
|
+
* The upstream kapsule (force-graph + 3d-force-graph) flips
|
|
13
|
+
* `isPointerDragging` to true on the *first* pointermove after
|
|
14
|
+
* pointerdown for any mouse event — even a 1px jitter — which then
|
|
15
|
+
* suppresses the click / right-click handler on pointerup. That
|
|
16
|
+
* makes background-click deselect, node selection, and right-click
|
|
17
|
+
* context menu all feel fragile.
|
|
18
|
+
*
|
|
19
|
+
* We add a movement dead-zone by intercepting pointer **and** mouse
|
|
20
|
+
* move events at the window level in the capture phase. Two
|
|
21
|
+
* independent thresholds combine to keep clicks from accidentally
|
|
22
|
+
* becoming drags:
|
|
23
|
+
*
|
|
24
|
+
* 1. **Press-grace window** — for the first `pressGraceMs` after
|
|
25
|
+
* `pointerdown`, every move is swallowed regardless of distance.
|
|
26
|
+
* This covers the involuntary jitter spike that fires right
|
|
27
|
+
* after the button physically engages, which on a HiDPI trackpad
|
|
28
|
+
* can easily exceed 6–8px in a single event.
|
|
29
|
+
* 2. **Distance dead-zone** — past the grace window, moves within
|
|
30
|
+
* `tolerancePx` of the press position are still swallowed. Once
|
|
31
|
+
* the cursor crosses that radius the gesture commits to "drag"
|
|
32
|
+
* and we stop intercepting, so pan / orbit / drag-node remain
|
|
33
|
+
* snappy for the rest of the gesture.
|
|
34
|
+
*
|
|
35
|
+
* Suppressing moves stops them propagating to:
|
|
36
|
+
* - the kapsule's container-level `pointermove` listener (so its
|
|
37
|
+
* `isPointerDragging` flag stays false and the click handler
|
|
38
|
+
* still fires on `pointerup`);
|
|
39
|
+
* - d3-zoom's `mousemove.zoom` listener on `window` (so a slight
|
|
40
|
+
* pan doesn't shift a node under the cursor between pointerdown
|
|
41
|
+
* and pointerup — that was the bug where clicking empty space to
|
|
42
|
+
* deselect would silently toggle whichever node slid under the
|
|
43
|
+
* cursor mid-gesture);
|
|
44
|
+
* - Three.js OrbitControls in 3D (same idea — no camera nudge
|
|
45
|
+
* during a click). */
|
|
46
|
+
export declare function useClickToleranceShim(mount: HTMLElement | null, options?: number | ClickToleranceShimOptions): void;
|