@opendata-ai/openchart-vanilla 8.4.0 → 8.5.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/README.md +59 -0
- package/dist/chunk-2OJZBMXM.js +377 -0
- package/dist/chunk-2OJZBMXM.js.map +1 -0
- package/dist/{chunk-45XZOWYC.js → chunk-5KV3ZBBL.js} +58 -437
- package/dist/chunk-5KV3ZBBL.js.map +1 -0
- package/dist/{chunk-HGRNXLDF.js → chunk-5RX6ZQGZ.js} +10 -1
- package/dist/chunk-5RX6ZQGZ.js.map +1 -0
- package/dist/{chunk-UOB422LY.js → chunk-6B5OBGIO.js} +2 -2
- package/dist/chunk-NG7CVCI2.js +58 -0
- package/dist/chunk-NG7CVCI2.js.map +1 -0
- package/dist/{chunk-FETYAVTZ.js → chunk-RYVV3YIG.js} +3 -3
- package/dist/{chunk-LNPRDSK3.js → chunk-UAK6HWOC.js} +2 -2
- package/dist/chunk-WOLX5EZT.js +3577 -0
- package/dist/chunk-WOLX5EZT.js.map +1 -0
- package/dist/{chunk-YGYMB7KB.js → chunk-XGII4ZRT.js} +55 -30
- package/dist/chunk-XGII4ZRT.js.map +1 -0
- package/dist/export-gif.js +3 -3
- package/dist/graph-3d/index.d.ts +272 -0
- package/dist/graph-3d/index.js +1556 -0
- package/dist/graph-3d/index.js.map +1 -0
- package/dist/index.d.ts +4 -252
- package/dist/index.js +27 -3471
- package/dist/index.js.map +1 -1
- package/dist/renderer-registry-B5tW6yAZ.d.ts +376 -0
- package/dist/static.js +4 -3
- package/dist/static.js.map +1 -1
- package/dist/story/index.js +10 -7
- package/dist/story/index.js.map +1 -1
- package/dist/styles.css +1 -1
- package/dist/styles.css.map +1 -1
- package/package.json +26 -5
- package/dist/chunk-45XZOWYC.js.map +0 -1
- package/dist/chunk-HGRNXLDF.js.map +0 -1
- package/dist/chunk-YGYMB7KB.js.map +0 -1
- /package/dist/{chunk-UOB422LY.js.map → chunk-6B5OBGIO.js.map} +0 -0
- /package/dist/{chunk-FETYAVTZ.js.map → chunk-RYVV3YIG.js.map} +0 -0
- /package/dist/{chunk-LNPRDSK3.js.map → chunk-UAK6HWOC.js.map} +0 -0
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
import { GraphSpec } from '@opendata-ai/openchart-core';
|
|
2
|
+
import { a as GraphCamera, b as GraphFlyTarget, g as GraphRendererContext, d as GraphInstance, f as GraphMountOptions } from '../renderer-registry-B5tW6yAZ.js';
|
|
3
|
+
import '@opendata-ai/openchart-engine';
|
|
4
|
+
import '@floating-ui/dom';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Per-frame focus model for the graph: unifies the three emphasis sources
|
|
8
|
+
* (programmatic highlight, search matches, hover-neighborhood) into a single
|
|
9
|
+
* snapshot pair that the renderer crossfades between.
|
|
10
|
+
*
|
|
11
|
+
* Composition, not strict precedence (see plan §5a):
|
|
12
|
+
* - Standing state = highlight ∩ search when both active; if that intersection
|
|
13
|
+
* is empty, search matches win (fresher user intent). This preserves the
|
|
14
|
+
* "filter by topic, then search within it" workflow.
|
|
15
|
+
* - Hover-neighborhood is the transient top layer over whatever the standing
|
|
16
|
+
* state is.
|
|
17
|
+
*
|
|
18
|
+
* The transition tweens between two discrete FocusSnapshots. Rapid hover sweeps
|
|
19
|
+
* retarget mid-flight: `retarget(next, now)` snapshots the endpoint CLOSEST to
|
|
20
|
+
* the current display (`prev = p < 0.5 ? old prev : old next`), so a low-p
|
|
21
|
+
* retarget keeps the old prev — no forward snap, worst-case visual jump halved
|
|
22
|
+
* vs. a naive "always start from current next" rule. Exact per-edge capture
|
|
23
|
+
* would destroy the renderer's tier batching, so this discrete approximation is
|
|
24
|
+
* deliberate.
|
|
25
|
+
*/
|
|
26
|
+
/** The set of emphasis relationships in effect for one steady state. */
|
|
27
|
+
interface FocusSnapshot {
|
|
28
|
+
/** True when any emphasis source is active (something is dimmed). */
|
|
29
|
+
hasActive: boolean;
|
|
30
|
+
/** Nodes connected to the active/hovered set (includes the active nodes). */
|
|
31
|
+
connected: Set<string>;
|
|
32
|
+
/** Search matches, or null when search is inactive. */
|
|
33
|
+
searchMatches: Set<string> | null;
|
|
34
|
+
/** Selected nodes (selection rings; always emphasized). */
|
|
35
|
+
selected: Set<string>;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Emphasis composition for the 3D renderer: turns the focus model into a
|
|
40
|
+
* per-node and per-edge target opacity.
|
|
41
|
+
*
|
|
42
|
+
* The composition rules are NOT re-derived here. `composeStandingFocus` and
|
|
43
|
+
* `layerHoverFocus` from `../graph/focus-transition` are the single source of
|
|
44
|
+
* truth for "highlight ∩ search, hover on top" and are reused verbatim; this
|
|
45
|
+
* module only maps the resulting {@link FocusSnapshot} onto opacity numbers,
|
|
46
|
+
* mirroring the tier rules the 2D canvas renderer applies
|
|
47
|
+
* (`graph/canvas-renderer.ts` `nodeTier` / `edgeTier` / `*TierAlpha`).
|
|
48
|
+
*
|
|
49
|
+
* The one 3D-specific input is `edgeBaseAlpha`: above `LINK_WIDTH_MAX_EDGES`
|
|
50
|
+
* the renderer draws plain lines and maps `edgeWidth` to a resting alpha
|
|
51
|
+
* instead of a cylinder radius, so the resting tier is per-edge rather than a
|
|
52
|
+
* single constant.
|
|
53
|
+
*
|
|
54
|
+
* Pure: no three.js, no DOM.
|
|
55
|
+
*/
|
|
56
|
+
|
|
57
|
+
/** Minimal node shape the composition needs. */
|
|
58
|
+
interface EmphasisNode {
|
|
59
|
+
id: string;
|
|
60
|
+
/** Compiled `nodeOpacity` encoding value; multiplies into the result. */
|
|
61
|
+
opacity: number;
|
|
62
|
+
}
|
|
63
|
+
/** Minimal edge shape the composition needs. Index is the link object key. */
|
|
64
|
+
interface EmphasisEdge {
|
|
65
|
+
source: string;
|
|
66
|
+
target: string;
|
|
67
|
+
}
|
|
68
|
+
interface EmphasisInput {
|
|
69
|
+
nodes: EmphasisNode[];
|
|
70
|
+
edges: EmphasisEdge[];
|
|
71
|
+
/** The composed focus snapshot (standing state with the hover layer on top). */
|
|
72
|
+
focus: FocusSnapshot;
|
|
73
|
+
/** Ids that never dim (the spec's `seedNode`). */
|
|
74
|
+
exemptIds: Set<string>;
|
|
75
|
+
/** Resolved `interaction.hover.dimOpacity`. */
|
|
76
|
+
dimOpacity: number;
|
|
77
|
+
/** Per-edge resting alpha keyed by edge index; defaults to EDGE_ALPHA_DEFAULT. */
|
|
78
|
+
edgeBaseAlpha?: Map<number, number>;
|
|
79
|
+
}
|
|
80
|
+
interface EmphasisTargets {
|
|
81
|
+
/** Target material opacity by node id. */
|
|
82
|
+
nodes: Map<string, number>;
|
|
83
|
+
/** Target material opacity by edge index. */
|
|
84
|
+
edges: Map<number, number>;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Compose the focus snapshot into target opacities.
|
|
88
|
+
*
|
|
89
|
+
* Node rule (mirrors `nodeTier` + `nodeTierAlpha`): nothing active → 1; an
|
|
90
|
+
* exempt id → 1; in the connected set → 1; otherwise `dimOpacity`. An active
|
|
91
|
+
* search multiplies non-matching nodes by {@link SEARCH_NON_MATCH_ALPHA}, and
|
|
92
|
+
* exemption deliberately does NOT apply to search (a seed that doesn't match
|
|
93
|
+
* the query should not pretend to). The compiled `nodeOpacity` multiplies last.
|
|
94
|
+
*
|
|
95
|
+
* Edge rule (mirrors `edgeTier` + `edgeTierAlpha`): nothing active → the edge's
|
|
96
|
+
* resting alpha; both endpoints connected → 1; otherwise `dimOpacity / 3`,
|
|
97
|
+
* which preserves the node-to-edge dim ratio that keeps dense hairballs quiet.
|
|
98
|
+
* Edges are NOT exempted by `exemptIds`: the seed stays lit while its edges dim
|
|
99
|
+
* with everything else.
|
|
100
|
+
*/
|
|
101
|
+
declare function resolveEmphasis(input: EmphasisInput): EmphasisTargets;
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Force wiring for the 3D simulation.
|
|
105
|
+
*
|
|
106
|
+
* 3d-force-graph owns a d3-force-3d simulation on the main thread; we do not
|
|
107
|
+
* drive it and we do not port the 2D web worker here. This module maps the
|
|
108
|
+
* engine's `SimulationConfig` onto that simulation's knobs and supplies the one
|
|
109
|
+
* force d3-force-3d does not ship: the community cluster force, which is a
|
|
110
|
+
* 3-axis port of `forceCluster` in `../graph/simulation.ts`. Both copies must
|
|
111
|
+
* stay in sync.
|
|
112
|
+
*/
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* A community cluster force in three axes: each node is pulled toward the
|
|
116
|
+
* centroid of its own community, scaled by `strength * alpha`. Port of
|
|
117
|
+
* `forceCluster` in `../graph/simulation.ts` with the z axis added.
|
|
118
|
+
*
|
|
119
|
+
* Uses d3's `initialize` hook rather than closing over a node array, so the
|
|
120
|
+
* force keeps working after `graphData()` swaps the node objects.
|
|
121
|
+
*/
|
|
122
|
+
declare function forceCluster3D(strength: number): (alpha: number) => void;
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Label visibility policy for the 3D renderer.
|
|
126
|
+
*
|
|
127
|
+
* Same two-stage shape as 2D: rank, then declutter. This module is the ranking
|
|
128
|
+
* half — forced labels (seed / `alwaysShowLabel` overrides, hovered, selected,
|
|
129
|
+
* search matches) first, then the highest-priority nodes, nearest camera first,
|
|
130
|
+
* up to the budget. The mount does the screen-space overlap pass on the ordered
|
|
131
|
+
* result, which is why the order matters and not just the membership.
|
|
132
|
+
*
|
|
133
|
+
* Pure: no three.js, no DOM. The mount feeds it plain positions.
|
|
134
|
+
*/
|
|
135
|
+
/** A label candidate: a node with a resolved label and a compiled priority. */
|
|
136
|
+
interface LabelCandidate {
|
|
137
|
+
id: string;
|
|
138
|
+
/** Compiled `labelPriority` (0..1, degree-derived). Higher wins. */
|
|
139
|
+
priority: number;
|
|
140
|
+
x: number;
|
|
141
|
+
y: number;
|
|
142
|
+
z: number;
|
|
143
|
+
}
|
|
144
|
+
/** Default 3D label budget beyond the forced set. */
|
|
145
|
+
declare const LABEL_BUDGET_3D = 40;
|
|
146
|
+
/**
|
|
147
|
+
* Node ids whose label sprite may be shown, best first.
|
|
148
|
+
*
|
|
149
|
+
* `forced` ids lead the list and do NOT consume budget. The rest are ranked by
|
|
150
|
+
* `(priority desc, camera distance asc)` and the top `budget` follow.
|
|
151
|
+
*/
|
|
152
|
+
declare function resolveVisibleLabels(nodes: LabelCandidate[], forced: Set<string>, cameraPos: {
|
|
153
|
+
x: number;
|
|
154
|
+
y: number;
|
|
155
|
+
z: number;
|
|
156
|
+
}, budget: number): string[];
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Per-link three.js objects.
|
|
160
|
+
*
|
|
161
|
+
* Same reason as `nodes.ts`: `linkOpacity` is a single global number in
|
|
162
|
+
* 3d-force-graph, so per-edge hover dimming needs each link to own its
|
|
163
|
+
* material. (An rgba `linkColor` IS honoured for per-link opacity — see
|
|
164
|
+
* `../graph-3d/README` notes in the RFC report — but changing it only takes
|
|
165
|
+
* effect on a `refresh()`, which rebuilds every link object. That is exactly
|
|
166
|
+
* what hover must not do.)
|
|
167
|
+
*
|
|
168
|
+
* Two shapes: a `Line` (constant screen width, cheap) and a cylinder `Mesh`
|
|
169
|
+
* (true world-space width, needed when `edgeWidth` is encoded). Above
|
|
170
|
+
* `LINK_WIDTH_MAX_EDGES` the mount stays on lines and maps `edgeWidth` to a
|
|
171
|
+
* resting alpha instead.
|
|
172
|
+
*
|
|
173
|
+
* Positioning: three-forcegraph's own tick loop positions custom `Line` and
|
|
174
|
+
* `Mesh` link objects generically (it branches on `obj.type` and, for meshes,
|
|
175
|
+
* on the geometry already being a `CylinderGeometry`). The mount still installs
|
|
176
|
+
* a `linkPositionUpdate` because dashed lines need `computeLineDistances()`
|
|
177
|
+
* after every move, which the library's path does not call.
|
|
178
|
+
*/
|
|
179
|
+
|
|
180
|
+
/** Above this edge count `edgeWidth` maps to opacity steps, not cylinder radius. */
|
|
181
|
+
declare const LINK_WIDTH_MAX_EDGES = 2000;
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* The 3D graph renderer: a `GraphInstance` backed by `3d-force-graph`.
|
|
185
|
+
*
|
|
186
|
+
* Registered against `dimensions: 3` by `./index`. `createGraph()` builds the
|
|
187
|
+
* shared shell (wrapper, chrome, legend slot, tooltip manager, resize wiring)
|
|
188
|
+
* and hands it here; this module owns the WebGL scene, the d3-force-3d
|
|
189
|
+
* simulation the library drives, the per-node and per-link three.js objects,
|
|
190
|
+
* emphasis state, the label budget, tooltip anchoring, and disposal.
|
|
191
|
+
*
|
|
192
|
+
* Three library facts shape almost everything below and are easy to get wrong:
|
|
193
|
+
*
|
|
194
|
+
* 1. `nodeOpacity` and `linkOpacity` are GLOBAL numbers, not accessors. Every
|
|
195
|
+
* per-object opacity (dim, highlight, entrance, search) therefore needs a
|
|
196
|
+
* custom object with its own material. See `nodes.ts` / `links.ts`.
|
|
197
|
+
* 2. `refresh()` rebuilds every node and link object. It must never run on
|
|
198
|
+
* hover; the hover path only writes `material.opacity`.
|
|
199
|
+
* 3. `rendererConfig` and `controlType` are CONSTRUCTOR options, not chained
|
|
200
|
+
* setters.
|
|
201
|
+
*
|
|
202
|
+
* Differences from 2D that are deliberate and documented in RFC 27: no
|
|
203
|
+
* keyboard navigation, no node drag, no cursor repulsion or springy drag, no
|
|
204
|
+
* wall-clock warmup budget, and a structural `update()` reheats globally
|
|
205
|
+
* (`graphData()` restarts the layout at alpha 1) instead of applying 2D's local
|
|
206
|
+
* impulse.
|
|
207
|
+
*/
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* The camera distance that `getCamera().k === 1` refers to. 3D has no zoom
|
|
211
|
+
* scalar, so `k` is expressed as `FIT_DISTANCE / cameraDistance`: a `k` of 2 is
|
|
212
|
+
* twice as close as the reference framing, matching the 2D sign convention.
|
|
213
|
+
*/
|
|
214
|
+
declare const FIT_DISTANCE = 1000;
|
|
215
|
+
/** Camera standoff from a node for `zoomToNode`, in scene units. */
|
|
216
|
+
declare const NODE_FOCUS_DISTANCE = 120;
|
|
217
|
+
/**
|
|
218
|
+
* A camera pose: the shared `GraphCamera` with the pose fields narrowed to
|
|
219
|
+
* required, since the 3D renderer always reports them.
|
|
220
|
+
*/
|
|
221
|
+
interface Camera3D extends GraphCamera {
|
|
222
|
+
position: {
|
|
223
|
+
x: number;
|
|
224
|
+
y: number;
|
|
225
|
+
z: number;
|
|
226
|
+
};
|
|
227
|
+
target: {
|
|
228
|
+
x: number;
|
|
229
|
+
y: number;
|
|
230
|
+
z: number;
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
/** `flyTo` accepts either the 2D shape or a full 3D pose from `getCamera()`. */
|
|
234
|
+
type FlyTarget3D = GraphFlyTarget;
|
|
235
|
+
declare function createGraph3DRenderer(ctx: GraphRendererContext): GraphInstance;
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* `@opendata-ai/openchart-vanilla/graph-3d` — the WebGL graph renderer.
|
|
239
|
+
*
|
|
240
|
+
* Importing this module registers the 3D renderer for `dimensions: 3`; after
|
|
241
|
+
* that, `createGraph()` picks it up on its own. That side effect is the whole
|
|
242
|
+
* point of the separate subpath: three.js (~30MB unpacked) never enters the
|
|
243
|
+
* default bundle, and `createGraph()` never dynamically imports a renderer,
|
|
244
|
+
* because mount is synchronous and every framework wrapper assumes it is.
|
|
245
|
+
*
|
|
246
|
+
* ```ts
|
|
247
|
+
* import { createGraph } from '@opendata-ai/openchart-vanilla';
|
|
248
|
+
* import '@opendata-ai/openchart-vanilla/graph-3d';
|
|
249
|
+
*
|
|
250
|
+
* createGraph(el, { type: 'graph', dimensions: 3, nodes, edges });
|
|
251
|
+
* ```
|
|
252
|
+
*
|
|
253
|
+
* `three`, `3d-force-graph` and `three-spritetext` are optional peer
|
|
254
|
+
* dependencies; install them alongside openchart to use this entry point, and
|
|
255
|
+
* check `npm ls three` shows exactly one copy (two copies fail at runtime with
|
|
256
|
+
* `Cannot read properties of undefined (reading 'VERTEX')`).
|
|
257
|
+
*
|
|
258
|
+
* SSR: the libraries touch `window` at import, so import this module from a
|
|
259
|
+
* client-only path (a `useEffect` dynamic import in React Router / Next).
|
|
260
|
+
*/
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Mount a 3D graph without the caller having to set `dimensions: 3` themselves.
|
|
264
|
+
*
|
|
265
|
+
* A thin convenience over `createGraph()`: the spec is forced to three
|
|
266
|
+
* dimensions and everything else — shell, compilation, node-count gate, handle
|
|
267
|
+
* — is the shared path. A graph above `MAX_3D_NODES` still falls back to the 2D
|
|
268
|
+
* renderer with a warning, exactly as `createGraph()` would.
|
|
269
|
+
*/
|
|
270
|
+
declare function createGraph3D(container: HTMLElement, spec: GraphSpec, options?: GraphMountOptions): GraphInstance;
|
|
271
|
+
|
|
272
|
+
export { type Camera3D, type EmphasisInput, type EmphasisTargets, FIT_DISTANCE, type FlyTarget3D, LABEL_BUDGET_3D, LINK_WIDTH_MAX_EDGES, type LabelCandidate, NODE_FOCUS_DISTANCE, createGraph3D, createGraph3DRenderer, forceCluster3D, resolveEmphasis, resolveVisibleLabels };
|