@graphty/graphty-element 2.0.1 → 2.2.1
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 +3 -3
- package/dist/ai.js +4 -4
- package/dist/catalog.js +5 -5
- package/dist/chunks/{AiManager-awS0MNr1.js → AiManager-Dp1mOkPm.js} +5 -5
- package/dist/chunks/{Algorithm-BdcqHKps.js → Algorithm-RQ629NLb.js} +210 -92
- package/dist/chunks/{DataSource-DK4GZBKg.js → DataSource-DEg3igzS.js} +3 -3
- package/dist/chunks/{GraphSession-D87eymV8.js → GraphSession-D3AV8tpF.js} +2527 -2248
- package/dist/chunks/{GraphtyError-B3eKs4yg.js → GraphtyError-BwcnblTH.js} +10 -8
- package/dist/chunks/{GraphtyLogger-CK03SnJi.js → GraphtyLogger-5KEttFUo.js} +1 -1
- package/dist/chunks/{NodeStyle-CS7dj20m.js → NodeStyle-Cup9O1lu.js} +2 -1
- package/dist/chunks/{VoiceInputAdapter-CcnCmxo5.js → VoiceInputAdapter-Bbze1r8k.js} +1 -1
- package/dist/chunks/{XRPivotCameraController-UjMsbran.js → XRPivotCameraController-8Il3KxFm.js} +2 -2
- package/dist/chunks/{cameras-BeIAlMWR.js → cameras-CeJYi-Na.js} +17 -18
- package/dist/chunks/{capability-check-DcKFwS6q.js → capability-check-EUCOfQLP.js} +1 -1
- package/dist/chunks/{detect-B-YbbP7i.js → detect-CqN0mC6u.js} +3 -3
- package/dist/chunks/{format-detection-DohSEZnb.js → format-detection-C8vbCSlS.js} +1 -1
- package/dist/chunks/{index-uohvGegX.js → index-Cz47b_vU.js} +1957 -1318
- package/dist/chunks/{paletteRegistry-B-oS4YxP.js → paletteRegistry-NrWOKT-A.js} +17 -17
- package/dist/chunks/{registry-DU-e49Y2.js → registry-CSba5QGJ.js} +1 -1
- package/dist/chunks/{scales-DvGid5Ln.js → scales-ulKMZPfG.js} +2206 -1357
- package/dist/custom-elements.json +1 -1
- package/dist/extend.js +26 -27
- package/dist/graphty-catalog.json +7 -4
- package/dist/graphty.bundle.js +36572 -34379
- package/dist/graphty.js +66 -64
- package/dist/index.d.ts +1 -1
- package/dist/logging.js +2 -2
- package/dist/schema.js +1 -1
- package/dist/session.d.ts +2 -1
- package/dist/session.js +37 -33
- package/dist/src/Edge.d.ts +1 -1
- package/dist/src/Graph.d.ts +42 -8
- package/dist/src/Node.d.ts +3 -4
- package/dist/src/acceleration/AccelerationController.d.ts +27 -0
- package/dist/src/acceleration/index.d.ts +1 -1
- package/dist/src/acceleration/narrow.d.ts +56 -0
- package/dist/src/acceleration/types.d.ts +88 -7
- package/dist/src/algorithms/Algorithm.d.ts +96 -2
- package/dist/src/algorithms/BFSAlgorithm.d.ts +9 -0
- package/dist/src/algorithms/DijkstraAlgorithm.d.ts +2 -8
- package/dist/src/algorithms/PageRankAlgorithm.d.ts +8 -0
- package/dist/src/algorithms/utils/graphUtils.d.ts +13 -0
- package/dist/src/cameras/OrbitCameraController.d.ts +1 -0
- package/dist/src/catalog/layouts.d.ts +13 -1
- package/dist/src/config/GraphBehavior.d.ts +16 -0
- package/dist/src/config/GraphStyle.d.ts +1 -1
- package/dist/src/config/StyleTemplate.d.ts +10 -0
- package/dist/src/data/CSVDataSource.d.ts +1 -0
- package/dist/src/data/csv-variant-detection.d.ts +11 -0
- package/dist/src/errors/codes.d.ts +14 -1
- package/dist/src/events.d.ts +17 -4
- package/dist/src/graphty-element.d.ts +52 -4
- package/dist/src/layout/ForceAtlas2LayoutEngine.d.ts +28 -45
- package/dist/src/layout/LayoutEngine.d.ts +7 -7
- package/dist/src/layout/SimulationLayoutEngine.d.ts +458 -0
- package/dist/src/layout/SpringElectricalLayoutEngine.d.ts +32 -0
- package/dist/src/layout/SpringLayoutEngine.d.ts +22 -32
- package/dist/src/managers/DataManager.d.ts +2 -0
- package/dist/src/managers/EventManager.d.ts +7 -0
- package/dist/src/managers/GraphContext.d.ts +6 -0
- package/dist/src/managers/LabelDeclutter.d.ts +97 -0
- package/dist/src/managers/LayoutManager.d.ts +98 -4
- package/dist/src/managers/RenderManager.d.ts +7 -0
- package/dist/src/managers/UpdateManager.d.ts +1 -1
- package/dist/src/meshes/NodeEffects.d.ts +29 -5
- package/dist/src/meshes/RichTextLabel.d.ts +17 -0
- package/dist/src/session/catalog.d.ts +2 -2
- package/dist/src/session/query.d.ts +83 -0
- package/dist/src/session/runs/types.d.ts +10 -4
- package/dist/src/session/selection/targets.d.ts +20 -3
- package/dist/src/session/styles/StylesApi.d.ts +18 -0
- package/dist/src/session/styles/channels.d.ts +0 -2
- package/dist/src/session/styles/legend.d.ts +9 -0
- package/dist/src/session/styles/predicate.d.ts +11 -0
- package/dist/src/session/styles/sources.d.ts +16 -0
- package/dist/src/session/types.d.ts +38 -2
- package/dist/src/testing/fakeAccelerator.d.ts +323 -0
- package/dist/webgpu.d.ts +23 -1
- package/dist/webgpu.js +70 -32
- package/package.json +10 -8
- package/dist/chunks/types-Dwm9waL2.js +0 -7
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { type Camera, type Scene } from "@babylonjs/core";
|
|
2
|
+
import type { Node } from "../Node";
|
|
3
|
+
import type { GraphContext } from "./GraphContext";
|
|
4
|
+
/**
|
|
5
|
+
* Hides node labels that would be drawn on top of each other, when the graph's behaviour
|
|
6
|
+
* configuration asks for it (`labels.declutter`, off by default).
|
|
7
|
+
*
|
|
8
|
+
* Every node label is its own billboarded plane, so two labelled nodes that land near each other
|
|
9
|
+
* on screen draw their words over one another. The pass projects each drawn label's WORDS -- not
|
|
10
|
+
* its padded plane -- to a screen rectangle, orders the labels by priority -- a selected node
|
|
11
|
+
* first, then the node with more edges, then the node id so the answer is stable -- and keeps a
|
|
12
|
+
* label only if its rectangle meets no label already kept. Kept rectangles are bucketed in a
|
|
13
|
+
* screen grid, so each label is tested against its neighbours rather than against every label.
|
|
14
|
+
*
|
|
15
|
+
* THE PASS RUNS ONLY WHEN SOMETHING THAT DECIDES PLACEMENT CHANGED: the camera's view or
|
|
16
|
+
* projection, the viewport size, a label added, rebuilt or removed, a node moved, shown, hidden
|
|
17
|
+
* or (de)selected, an edge added or removed, or the setting itself. A still scene costs one cheap
|
|
18
|
+
* walk over the labelled nodes per frame and no projection at all.
|
|
19
|
+
*
|
|
20
|
+
* A label that loses is hidden with `isVisible`, never with `setEnabled` and never through a
|
|
21
|
+
* style. `setEnabled` is the visibility mask's switch (`Node.applyRenderState`), and a style is
|
|
22
|
+
* the reader's answer to what a node looks like; this is a placement decision, so it has its own
|
|
23
|
+
* switch. Turning the setting off shows every label again.
|
|
24
|
+
*
|
|
25
|
+
* ONE PER SCENE, created by the first node that draws a label, and kept on `scene.metadata`
|
|
26
|
+
* the way `NodeEffects` keeps the glow layer.
|
|
27
|
+
*/
|
|
28
|
+
export declare class LabelDeclutter {
|
|
29
|
+
private readonly scene;
|
|
30
|
+
private readonly context;
|
|
31
|
+
/** How many full passes have run. Read by tests to prove a still scene is not re-measured. */
|
|
32
|
+
passes: number;
|
|
33
|
+
private readonly observer;
|
|
34
|
+
private readonly entries;
|
|
35
|
+
/** Reused every pass: the candidates in priority order. */
|
|
36
|
+
private readonly order;
|
|
37
|
+
/** Reused every pass: kept labels by grid cell. Emptied, not rebuilt. */
|
|
38
|
+
private readonly grid;
|
|
39
|
+
private columns;
|
|
40
|
+
private rows;
|
|
41
|
+
private dirty;
|
|
42
|
+
private wasOn;
|
|
43
|
+
private camera;
|
|
44
|
+
private readonly view;
|
|
45
|
+
private readonly projection;
|
|
46
|
+
private width;
|
|
47
|
+
private height;
|
|
48
|
+
private edgeVersion;
|
|
49
|
+
private degrees;
|
|
50
|
+
private readonly transform;
|
|
51
|
+
private readonly toPixels;
|
|
52
|
+
private readonly worldToPixels;
|
|
53
|
+
private readonly viewport;
|
|
54
|
+
private readonly right;
|
|
55
|
+
private readonly up;
|
|
56
|
+
private readonly centre;
|
|
57
|
+
private readonly corner;
|
|
58
|
+
private readonly centreOnScreen;
|
|
59
|
+
private readonly cornerOnScreen;
|
|
60
|
+
private constructor();
|
|
61
|
+
/**
|
|
62
|
+
* Make sure the scene has a declutter pass, and add a node that is drawing a label to it.
|
|
63
|
+
* @param scene - The scene the labels are drawn in.
|
|
64
|
+
* @param context - The graph: its configuration says whether to declutter, its edges give
|
|
65
|
+
* the degree order.
|
|
66
|
+
* @param node - The node drawing a label.
|
|
67
|
+
*/
|
|
68
|
+
static track(scene: Scene, context: GraphContext, node: Node): void;
|
|
69
|
+
/** Stop running before frames. */
|
|
70
|
+
dispose(): void;
|
|
71
|
+
/** Before every frame: decide whether placement could have changed, and if so, place. */
|
|
72
|
+
run(): void;
|
|
73
|
+
/**
|
|
74
|
+
* One full pass: decide which labels are drawn.
|
|
75
|
+
* @param camera - The camera the frame is drawn from.
|
|
76
|
+
*/
|
|
77
|
+
place(camera: Camera): void;
|
|
78
|
+
/**
|
|
79
|
+
* Whether anything that decides placement differs from what the last pass saw.
|
|
80
|
+
* @param camera - The camera the frame is drawn from.
|
|
81
|
+
* @returns True when the labels have to be placed again.
|
|
82
|
+
*/
|
|
83
|
+
private changed;
|
|
84
|
+
/**
|
|
85
|
+
* Record what this pass sees of one node, and drop it if its label is gone.
|
|
86
|
+
* @param entry - The node.
|
|
87
|
+
* @returns Its label plane, or null when it has none.
|
|
88
|
+
*/
|
|
89
|
+
private observe;
|
|
90
|
+
private remember;
|
|
91
|
+
private isClear;
|
|
92
|
+
private keep;
|
|
93
|
+
/** The setting was turned off: every label is drawn again. */
|
|
94
|
+
private showAll;
|
|
95
|
+
/** How many edges each node has, counted again only when an edge is added or removed. */
|
|
96
|
+
private refreshDegrees;
|
|
97
|
+
}
|
|
@@ -1,11 +1,37 @@
|
|
|
1
|
+
import type { SimulationType } from "@graphty/layout";
|
|
2
|
+
import type { GraphLayoutBehavior } from "../config/GraphBehavior";
|
|
1
3
|
import type { Edge } from "../Edge";
|
|
2
4
|
import { LayoutEngine } from "../layout/LayoutEngine";
|
|
5
|
+
import { type SimulationEngineOptions } from "../layout/SimulationLayoutEngine";
|
|
3
6
|
import type { Node } from "../Node";
|
|
4
7
|
import type { Styles } from "../Styles";
|
|
5
8
|
import type { DataManager } from "./DataManager";
|
|
6
9
|
import type { EventManager } from "./EventManager";
|
|
7
10
|
import type { GraphContext } from "./GraphContext";
|
|
8
11
|
import type { Manager } from "./interfaces";
|
|
12
|
+
/**
|
|
13
|
+
* Turn what the consumer asked a simulation layout for into what the bridge runs on.
|
|
14
|
+
*
|
|
15
|
+
* `scalingFactor` travels twice over, and deliberately. The simulation gets it as `scale`, where
|
|
16
|
+
* it sizes the seed and, in ForceAtlas2, the units the arrangement is computed in; the BRIDGE gets
|
|
17
|
+
* it as the radius it publishes that arrangement at, which is the only one of the two a reader
|
|
18
|
+
* sees in the picture. A layout that publishes no such control takes
|
|
19
|
+
* {@link DEFAULT_SCALING_FACTOR}. What the bridge is given is {@link publishedRadius}, every
|
|
20
|
+
* size multiplier the layout publishes rolled into one, because the refit fits the arrangement to
|
|
21
|
+
* that radius and leaves no other way for a reader to change how big the graph comes out.
|
|
22
|
+
*
|
|
23
|
+
* The four knobs the frame loop needs -- `iterationsPerStep`, `maxInFlight`, `settleThreshold` and
|
|
24
|
+
* `settleWindow` -- are NOT in any layout's published schema, because they are the same four for
|
|
25
|
+
* every simulation and belong to how the element drives one rather than to the arrangement it
|
|
26
|
+
* draws. They are read from the options a caller passed, where a per-layout value overrides the
|
|
27
|
+
* behaviour configuration, and `behavior.layout` is where a host sets them once for every layout.
|
|
28
|
+
* @param type - Which simulation is being configured.
|
|
29
|
+
* @param options - The merged layout options, as the consumer set them.
|
|
30
|
+
* @param behavior - `behavior.layout`, which the frame-loop knobs default from.
|
|
31
|
+
* @returns The resolved options.
|
|
32
|
+
* @throws A `ZodError` when an option is outside the range its published schema declares.
|
|
33
|
+
*/
|
|
34
|
+
export declare function resolveSimulationOptions(type: SimulationType, options: Record<string, unknown>, behavior: GraphLayoutBehavior): SimulationEngineOptions;
|
|
9
35
|
/**
|
|
10
36
|
* Manages layout engines and their lifecycle
|
|
11
37
|
* Coordinates layout updates and transitions
|
|
@@ -34,10 +60,27 @@ export declare class LayoutManager implements Manager {
|
|
|
34
60
|
*/
|
|
35
61
|
get running(): boolean;
|
|
36
62
|
/**
|
|
37
|
-
* Sets the running state of the layout
|
|
63
|
+
* Sets the running state of the layout.
|
|
64
|
+
*
|
|
65
|
+
* Going false -> true is "play", and a simulation that had SETTLED does nothing when it is
|
|
66
|
+
* stepped, so a reader who pressed play -- or let go of a node they dragged -- would watch a
|
|
67
|
+
* still graph. Resuming therefore reheats a settled simulation: the settle count starts
|
|
68
|
+
* again and the next frame moves nodes. Going true -> false only stops `step()` being
|
|
69
|
+
* called; batches already in flight land in the position array by themselves, nothing is
|
|
70
|
+
* disposed and nothing is released.
|
|
38
71
|
*/
|
|
39
72
|
set running(value: boolean);
|
|
40
73
|
private graphContext;
|
|
74
|
+
/**
|
|
75
|
+
* The graph's acceleration controller, once a context has arrived. A manager built without a
|
|
76
|
+
* graph -- which is what the manager's own unit tests build -- never gets one, and a
|
|
77
|
+
* simulation layout refuses to start on one.
|
|
78
|
+
*/
|
|
79
|
+
private acceleration;
|
|
80
|
+
/** How to stop listening to the controller. */
|
|
81
|
+
private unsubscribeAcceleration;
|
|
82
|
+
/** How to stop listening for a new snapshot. */
|
|
83
|
+
private unsubscribeSnapshot;
|
|
41
84
|
/**
|
|
42
85
|
* Creates an instance of LayoutManager
|
|
43
86
|
* @param eventManager - Event manager for emitting layout events
|
|
@@ -47,9 +90,33 @@ export declare class LayoutManager implements Manager {
|
|
|
47
90
|
constructor(eventManager: EventManager, dataManager: DataManager, styles: Styles);
|
|
48
91
|
/**
|
|
49
92
|
* Set the GraphContext for error reporting
|
|
93
|
+
*
|
|
94
|
+
* This is also where the acceleration controller first becomes reachable: the manager is
|
|
95
|
+
* built before the graph finishes constructing itself, so it cannot be handed one.
|
|
50
96
|
* @param context - GraphContext instance
|
|
51
97
|
*/
|
|
52
98
|
setGraphContext(context: GraphContext): void;
|
|
99
|
+
/**
|
|
100
|
+
* Rebuild a running simulation on whatever the controller holds now.
|
|
101
|
+
*
|
|
102
|
+
* Every transition reaches here: an accelerator attached, detached, lost or recovered. The
|
|
103
|
+
* try is load-bearing. Under the `required` policy a detach cannot reach a CPU simulation --
|
|
104
|
+
* the controller's `plan()` throws `E_NO_ACCELERATOR` instead, which is the point of the
|
|
105
|
+
* policy -- and the throw has to be REPORTED here rather than escaping into the controller's
|
|
106
|
+
* listener loop, whose own catch would log it and continue. The layout then stops, and the
|
|
107
|
+
* next transition that attaches an accelerator brings it back.
|
|
108
|
+
*/
|
|
109
|
+
private onAccelerationChange;
|
|
110
|
+
/**
|
|
111
|
+
* Hand a running simulation the snapshot that has just replaced the one it was laying out.
|
|
112
|
+
* @param event - The freeze, carrying the snapshot every consumer must switch to.
|
|
113
|
+
*/
|
|
114
|
+
private onSnapshotReplaced;
|
|
115
|
+
/**
|
|
116
|
+
* Report a failure that happened to a layout already running, on the element's error channel.
|
|
117
|
+
* @param error - Whatever was thrown.
|
|
118
|
+
*/
|
|
119
|
+
private reportSimulationFailure;
|
|
53
120
|
/**
|
|
54
121
|
* Update the styles reference when a new style template is loaded
|
|
55
122
|
* @param styles - New styles instance
|
|
@@ -103,6 +170,9 @@ export declare class LayoutManager implements Manager {
|
|
|
103
170
|
* @param type - The layout that failed.
|
|
104
171
|
* @param error - Whatever was thrown.
|
|
105
172
|
* @param phase - What the element was doing, in a word that fits "the layout could not be ...".
|
|
173
|
+
* `"stepped"` is the one that happens to a layout already running: a simulation's batch
|
|
174
|
+
* rejected minutes after set-up finished, and calling that "initialised" reads as a failure
|
|
175
|
+
* to start.
|
|
106
176
|
* @returns The error to throw.
|
|
107
177
|
*/
|
|
108
178
|
private reportLayoutFailure;
|
|
@@ -120,7 +190,19 @@ export declare class LayoutManager implements Manager {
|
|
|
120
190
|
* `preSteps` is what makes a screenshot of a physics layout the same picture twice: an
|
|
121
191
|
* unstepped force layout is a graph in mid-flight, and how far it has flown depends on when
|
|
122
192
|
* the picture was taken.
|
|
193
|
+
*
|
|
194
|
+
* A SIMULATION CANNOT BE STEPPED IN A TIGHT SYNCHRONOUS LOOP, which is why this is
|
|
195
|
+
* asynchronous and why both the path that spends the pre-steps when a layout is built and the
|
|
196
|
+
* path that pays owed ones later come through here rather than each writing their own loop. A
|
|
197
|
+
* simulation's step is a BATCH: an accelerated one is fire-and-forget, and a simulation that
|
|
198
|
+
* already has a few batches in flight returns the oldest of them instead of submitting
|
|
199
|
+
* another, so a loop that calls `step()` a thousand times without waiting has all but the
|
|
200
|
+
* first couple coalesced away and draws a graph the consumer asked to be far more arranged
|
|
201
|
+
* than it is -- silently, because a coalesced batch is not an error. So a simulation is
|
|
202
|
+
* awaited one chunk at a time, the chunk being the largest batch the GPU package takes, and
|
|
203
|
+
* every other engine keeps the plain loop, whose `step()` returns having done the work.
|
|
123
204
|
* @param engine - the engine to settle.
|
|
205
|
+
* @returns A promise that resolves once every pre-step has been taken and published.
|
|
124
206
|
*/
|
|
125
207
|
private spendPreSteps;
|
|
126
208
|
/**
|
|
@@ -136,15 +218,27 @@ export declare class LayoutManager implements Manager {
|
|
|
136
218
|
*
|
|
137
219
|
* So the count is owed rather than spent, and paid here: on the first frame at which there is
|
|
138
220
|
* a node to move. This runs inside the render loop's update, which is called before
|
|
139
|
-
* `scene.render()`, so
|
|
140
|
-
* waits for a node rather than for a particular call, it does not
|
|
141
|
-
* arrived in one batch, in ten, or from a fetch that finished a
|
|
221
|
+
* `scene.render()`, so an engine that steps on the spot really is stepped before the frame is
|
|
222
|
+
* drawn -- and because it waits for a node rather than for a particular call, it does not
|
|
223
|
+
* matter whether the data arrived in one batch, in ten, or from a fetch that finished a
|
|
224
|
+
* second later. A simulation's batches land over the next few frames instead, because no
|
|
225
|
+
* caller inside a render loop can wait for a device; what {@link LayoutManager.spendPreSteps}
|
|
226
|
+
* guarantees for one is that every owed iteration is computed rather than coalesced away.
|
|
142
227
|
*/
|
|
143
228
|
private runPreStepsOnceThereIsSomethingToStep;
|
|
144
229
|
/**
|
|
145
230
|
* Step the layout engine forward
|
|
146
231
|
*/
|
|
147
232
|
step(): void;
|
|
233
|
+
/**
|
|
234
|
+
* Step the layout ONCE, whatever the frame loop's multiplier is.
|
|
235
|
+
*
|
|
236
|
+
* The name is the difference a reader of `UpdateManager` needs: a simulation layout does its
|
|
237
|
+
* whole frame's work in one call -- the batch it submits computes `iterationsPerStep`
|
|
238
|
+
* iterations -- so stepping it `stepMultiplier` times a frame would queue work the device
|
|
239
|
+
* cannot retire.
|
|
240
|
+
*/
|
|
241
|
+
stepBatch(): void;
|
|
148
242
|
/**
|
|
149
243
|
* Get node position from layout engine
|
|
150
244
|
* @param node - Node to get position for
|
|
@@ -23,6 +23,13 @@ export declare class RenderManager implements Manager {
|
|
|
23
23
|
private renderLoopActive;
|
|
24
24
|
private updateCallback?;
|
|
25
25
|
private resizeHandler;
|
|
26
|
+
/**
|
|
27
|
+
* Stands in for Babylon's own pointer handling, which calls preventDefault and then
|
|
28
|
+
* `canvas.focus()` on every pointer down and up. That focus call scrolls the host page to the
|
|
29
|
+
* canvas. This one does the same thing without scrolling.
|
|
30
|
+
* @param evt - The pointer down or up event on the canvas
|
|
31
|
+
*/
|
|
32
|
+
private focusOnPointer;
|
|
26
33
|
/**
|
|
27
34
|
* Creates a new render manager for Babylon.js scene and rendering
|
|
28
35
|
* @param canvas - HTML canvas element for rendering
|
|
@@ -330,7 +330,7 @@ export declare class UpdateManager implements Manager {
|
|
|
330
330
|
/**
|
|
331
331
|
* Update the graph for the current frame.
|
|
332
332
|
*
|
|
333
|
-
* The pass itself is
|
|
333
|
+
* The pass itself is the private `runUpdatePass()`; what is added here is the one
|
|
334
334
|
* question a consumer cares about and the pass has several exits from -- whether the state it
|
|
335
335
|
* leaves behind is a finished picture.
|
|
336
336
|
*/
|
|
@@ -114,14 +114,38 @@ export declare class NodeEffects {
|
|
|
114
114
|
* material. Unfreezing to set `emissiveColor` would make every glowing style pay a material
|
|
115
115
|
* re-bind per frame and would change the node's lit appearance as a side effect.
|
|
116
116
|
*
|
|
117
|
-
*
|
|
118
|
-
*
|
|
119
|
-
*
|
|
120
|
-
* one layer per strength, which costs a full-screen pass each and was not worth it here.
|
|
117
|
+
* STRENGTH IS PER SOURCE MESH, like colour, through {@link NodeEffects.syncGlowStrengths}.
|
|
118
|
+
* It used to be written to `glowLayer.intensity`, which belongs to the one layer the scene
|
|
119
|
+
* shares, so the last glowing style applied set the strength of every glowing node.
|
|
121
120
|
* @param mesh - The mesh to apply the effect to
|
|
122
121
|
* @param effect - The effect configuration from the node style
|
|
123
122
|
*/
|
|
124
123
|
static applyGlowEffect(mesh: AbstractMesh, effect: NodeStyleConfig["effect"] | undefined): void;
|
|
124
|
+
/**
|
|
125
|
+
* The glow strength each glowing source mesh asked for, kept on scene.metadata beside the
|
|
126
|
+
* glow colours.
|
|
127
|
+
* @param scene - The Babylon.js scene
|
|
128
|
+
* @returns The per-source-mesh strengths
|
|
129
|
+
*/
|
|
130
|
+
private static glowStrengths;
|
|
131
|
+
/**
|
|
132
|
+
* Draw every glowing source mesh at its own strength, with one layer.
|
|
133
|
+
*
|
|
134
|
+
* Babylon multiplies a per-mesh `setEffectIntensity` into the glow colour as the glow map is
|
|
135
|
+
* drawn, and the layer's `intensity` scales the blurred result. The glow map is an 8-bit
|
|
136
|
+
* texture, so a per-mesh factor above 1 clamps and a strength of 3 would read the same as 1.
|
|
137
|
+
* So the layer carries the LARGEST strength on screen and each mesh carries its share of it,
|
|
138
|
+
* which is never above 1.
|
|
139
|
+
*
|
|
140
|
+
* Only source meshes that still draw a node count toward the largest strength. MeshCache
|
|
141
|
+
* never evicts a source mesh, so a strength that is no longer used (a slider dragged from 100
|
|
142
|
+
* back to 0.1) leaves a mesh with no instances behind; counted, it would hold the layer at 100
|
|
143
|
+
* and round the live glow away in the 8-bit map. It keeps its entry, because a node that goes
|
|
144
|
+
* back to that strength reuses the cached mesh. A disposed mesh is dropped.
|
|
145
|
+
* @param glowLayer - The scene's glow layer
|
|
146
|
+
* @param scene - The Babylon.js scene
|
|
147
|
+
*/
|
|
148
|
+
private static syncGlowStrengths;
|
|
125
149
|
/**
|
|
126
150
|
* Remove a mesh from the highlight layer.
|
|
127
151
|
*
|
|
@@ -149,7 +173,7 @@ export declare class NodeEffects {
|
|
|
149
173
|
*/
|
|
150
174
|
static disposeHighlightLayer(scene: Scene): void;
|
|
151
175
|
/**
|
|
152
|
-
* Dispose the glow layer for a scene, and forget the per-style colours with it.
|
|
176
|
+
* Dispose the glow layer for a scene, and forget the per-style colours and strengths with it.
|
|
153
177
|
*
|
|
154
178
|
* The colour map is keyed by mesh uniqueId, so it MUST die with the layer: leaving it behind
|
|
155
179
|
* would let a later mesh that happens to reuse a uniqueId inherit a dead style's glow colour.
|
|
@@ -42,6 +42,8 @@ export declare class RichTextLabel {
|
|
|
42
42
|
private parsedContent;
|
|
43
43
|
private actualDimensions;
|
|
44
44
|
private contentArea;
|
|
45
|
+
/** The size of the words alone, in the same units as `actualDimensions`. */
|
|
46
|
+
private textSize;
|
|
45
47
|
private totalBorderWidth;
|
|
46
48
|
private pointerInfo;
|
|
47
49
|
private _progressValue;
|
|
@@ -112,6 +114,21 @@ export declare class RichTextLabel {
|
|
|
112
114
|
* @returns The Babylon.js mesh or null if not created
|
|
113
115
|
*/
|
|
114
116
|
get labelMesh(): Mesh | null;
|
|
117
|
+
/**
|
|
118
|
+
* Where the words sit on the label's plane, without its margins, padding, borders or pointer.
|
|
119
|
+
*
|
|
120
|
+
* Each edge is a fraction of the plane: `left`/`right` across it from its left edge, and
|
|
121
|
+
* `top`/`bottom` down it from its top edge, so a label whose words fill it reads
|
|
122
|
+
* `{ left: 0, right: 1, top: 0, bottom: 1 }`. Two labels whose planes overlap only in their
|
|
123
|
+
* padding do not draw over each other's words; this is what tells them apart.
|
|
124
|
+
* @returns The words' rectangle as fractions of the plane.
|
|
125
|
+
*/
|
|
126
|
+
get textBounds(): {
|
|
127
|
+
left: number;
|
|
128
|
+
right: number;
|
|
129
|
+
top: number;
|
|
130
|
+
bottom: number;
|
|
131
|
+
};
|
|
115
132
|
/**
|
|
116
133
|
* Gets the label's unique identifier
|
|
117
134
|
* @returns The label ID
|
|
@@ -29,8 +29,8 @@
|
|
|
29
29
|
* already been run here. So a session's catalogue is the shared tables with its own `metrics()`
|
|
30
30
|
* closed over its own graph.
|
|
31
31
|
*
|
|
32
|
-
* The rest of the graph-dependent half -- resolved option bounds, expression validation --
|
|
33
|
-
*
|
|
32
|
+
* The rest of the graph-dependent half -- resolved option bounds, expression validation -- is not
|
|
33
|
+
* wired to the session's query engine yet.
|
|
34
34
|
*/
|
|
35
35
|
import type { AlgorithmDescriptor } from "../catalog/types";
|
|
36
36
|
import { type MetricsSource } from "./metrics";
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file The session's one query engine: `{ where }` predicates and text search.
|
|
3
|
+
*
|
|
4
|
+
* A scope, a selection, a visibility filter and an edge filter all accept an expression, and a
|
|
5
|
+
* selection also accepts typed text. Every one of them reaches THIS object, and this object
|
|
6
|
+
* evaluates an expression with the compiler the style layers use (`./styles/predicate`) over the
|
|
7
|
+
* selector source the style layers read (`./styles/sources`). That is the point of it: a layer
|
|
8
|
+
* selector and a scope with the same text match the same elements, because they are the same
|
|
9
|
+
* code reading the same values. A second evaluator would drift from the first in exactly the
|
|
10
|
+
* places nobody tests -- JMESPath truthiness, a column default, an edge's endpoints.
|
|
11
|
+
*
|
|
12
|
+
* A path nothing answers matches nothing and is REPORTED through `unresolvedPathsOf`, never
|
|
13
|
+
* swallowed: "0 matched" and "you misspelled the attribute" must not look the same.
|
|
14
|
+
*
|
|
15
|
+
* Nothing here reaches Babylon.js, Lit or the DOM.
|
|
16
|
+
*/
|
|
17
|
+
import type { GraphSnapshot } from "@graphty/graph-format";
|
|
18
|
+
import type { EdgeId, NodeId, Path, Query } from "../catalog/types";
|
|
19
|
+
import type { SelectionMatch, SelectionSearchHit, SelectionTextMode } from "./selection";
|
|
20
|
+
import { type SelectorSource, type SelectorTarget } from "./styles/predicate";
|
|
21
|
+
/** Everything the engine reads. */
|
|
22
|
+
interface QueryEngineParts {
|
|
23
|
+
/**
|
|
24
|
+
* The snapshot the dense indices address, read on every query.
|
|
25
|
+
* @returns The snapshot as it stands now.
|
|
26
|
+
*/
|
|
27
|
+
readonly snapshot: () => GraphSnapshot;
|
|
28
|
+
/** Where values are read: the same source the style layers read. */
|
|
29
|
+
readonly elements: Required<Pick<SelectorSource, "edgeIdOf" | "nodeIdOf">> & SelectorSource;
|
|
30
|
+
/**
|
|
31
|
+
* Whether this session answers a path for one kind of element. Absent, no path is reported.
|
|
32
|
+
* @param path - The path as the query spelled it.
|
|
33
|
+
* @param target - Which kind of element.
|
|
34
|
+
* @returns True when something in the session answers it.
|
|
35
|
+
*/
|
|
36
|
+
readonly answers?: (path: Path, target: SelectorTarget) => boolean;
|
|
37
|
+
/**
|
|
38
|
+
* The node attribute paths a text search reads, beside the id.
|
|
39
|
+
* @returns The paths, such as `data.label`.
|
|
40
|
+
*/
|
|
41
|
+
readonly searchPaths: () => readonly Path[];
|
|
42
|
+
}
|
|
43
|
+
/** The session's query engine. */
|
|
44
|
+
export interface QueryEngine {
|
|
45
|
+
/**
|
|
46
|
+
* The nodes an expression matches.
|
|
47
|
+
* @param where - The expression.
|
|
48
|
+
* @returns The node ids, in index order.
|
|
49
|
+
*/
|
|
50
|
+
nodes(where: Query): NodeId[];
|
|
51
|
+
/**
|
|
52
|
+
* The edges an expression matches.
|
|
53
|
+
* @param where - The expression.
|
|
54
|
+
* @returns The edge ids, in index order.
|
|
55
|
+
*/
|
|
56
|
+
edges(where: Query): EdgeId[];
|
|
57
|
+
/**
|
|
58
|
+
* Both halves an expression matches, and the paths it names that nothing answers.
|
|
59
|
+
* @param where - The expression.
|
|
60
|
+
* @returns The match.
|
|
61
|
+
*/
|
|
62
|
+
select(where: Query): SelectionMatch;
|
|
63
|
+
/**
|
|
64
|
+
* The paths an expression names that no node and no edge in this session answers.
|
|
65
|
+
* @param where - The expression.
|
|
66
|
+
* @returns The unresolved paths.
|
|
67
|
+
*/
|
|
68
|
+
unresolvedPathsOf(where: Query): readonly Path[];
|
|
69
|
+
/**
|
|
70
|
+
* The nodes a text search finds.
|
|
71
|
+
* @param text - What was typed.
|
|
72
|
+
* @param mode - How to match it.
|
|
73
|
+
* @returns The hits, in index order.
|
|
74
|
+
*/
|
|
75
|
+
find(text: string, mode: SelectionTextMode): SelectionSearchHit[];
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Build the query engine over a session's selector source.
|
|
79
|
+
* @param parts - The snapshot, the value source, the path directory and the searchable paths.
|
|
80
|
+
* @returns The engine.
|
|
81
|
+
*/
|
|
82
|
+
export declare function createQueryEngine(parts: QueryEngineParts): QueryEngine;
|
|
83
|
+
export {};
|
|
@@ -465,10 +465,16 @@ export interface Run<T = RunResult> extends PromiseLike<T> {
|
|
|
465
465
|
readonly shape: ResultShape;
|
|
466
466
|
/** What qualifies the numbers. */
|
|
467
467
|
readonly caveats: Caveats;
|
|
468
|
-
/**
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
468
|
+
/**
|
|
469
|
+
* The result, once there is one. Awaiting the run is the other way to get it.
|
|
470
|
+
*
|
|
471
|
+
* Spelled `?: T | undefined` rather than `?: T` because the implementation answers with a
|
|
472
|
+
* getter, and under a consumer's `exactOptionalPropertyTypes` a getter that can return
|
|
473
|
+
* undefined does not satisfy a property that can only be absent or present.
|
|
474
|
+
*/
|
|
475
|
+
readonly result?: T | undefined;
|
|
476
|
+
/** Why it failed, when it failed. Optional-or-undefined for the reason {@link Run.result} gives. */
|
|
477
|
+
readonly error?: GraphtyError | undefined;
|
|
472
478
|
/** The frozen, structured-cloneable snapshot of everything above. */
|
|
473
479
|
readonly record: RunRecord;
|
|
474
480
|
/** The journal entry this run's command wrote, or null until it lands. */
|
|
@@ -32,7 +32,15 @@ import type { EdgeId, NodeId, Path, Query, Scope } from "../../catalog/types";
|
|
|
32
32
|
import type { ResultsApi, RunRef } from "../results/types";
|
|
33
33
|
import { ElementMask, type MaskIdSpace } from "../scope/ElementMask";
|
|
34
34
|
import type { ScopeResolver } from "../scope/ScopeApi";
|
|
35
|
-
/**
|
|
35
|
+
/**
|
|
36
|
+
* How a text target decides whether an element matched.
|
|
37
|
+
*
|
|
38
|
+
* - `substring`: the id or an attribute value contains the text, ignoring case.
|
|
39
|
+
* - `exact`: the id or an attribute value is the text, exactly.
|
|
40
|
+
* - `regex`: the id or an attribute value matches the text as a regular expression.
|
|
41
|
+
* - `attribute`: the text is `key:value`, and the node's `key` (or its id, for `id`) is the value,
|
|
42
|
+
* ignoring case. A key no node carries is searched as plain substring text.
|
|
43
|
+
*/
|
|
36
44
|
export type SelectionTextMode = "substring" | "exact" | "regex" | "attribute";
|
|
37
45
|
/** Which way a neighbourhood target follows an edge. */
|
|
38
46
|
export type SelectionDirection = "in" | "out" | "all";
|
|
@@ -70,14 +78,23 @@ export interface NeighborhoodTarget {
|
|
|
70
78
|
* second ranking that could disagree with the one on screen.
|
|
71
79
|
*/
|
|
72
80
|
export type SelectionTarget = ElementIdTarget | NeighborhoodTarget
|
|
73
|
-
/** Every element a predicate matches. */
|
|
81
|
+
/** Every element a predicate matches, narrowed to a scope when one is named. */
|
|
74
82
|
| {
|
|
75
83
|
readonly where: Query;
|
|
84
|
+
readonly scope?: Scope;
|
|
76
85
|
}
|
|
77
|
-
/**
|
|
86
|
+
/**
|
|
87
|
+
* Every node a text search finds, narrowed to a scope when one is named.
|
|
88
|
+
*
|
|
89
|
+
* With no `mode`, the text may carry one as a prefix: `exact:`, `regex:`, or `<attribute>:`
|
|
90
|
+
* (`id:a17`, `type:person`), and anything else is a case-insensitive substring search over
|
|
91
|
+
* the node's id and its attribute values. See {@link SelectionTextMode}. A leading `=` makes
|
|
92
|
+
* the rest an expression, exactly as `{ where }` reads it, which selects edges as well.
|
|
93
|
+
*/
|
|
78
94
|
| {
|
|
79
95
|
readonly text: string;
|
|
80
96
|
readonly mode?: SelectionTextMode;
|
|
97
|
+
readonly scope?: Scope;
|
|
81
98
|
}
|
|
82
99
|
/** A pasted list of ids, which may name nodes, edges, or nothing at all. */
|
|
83
100
|
| {
|
|
@@ -494,6 +494,24 @@ export interface StylesSources {
|
|
|
494
494
|
*/
|
|
495
495
|
readonly onChange?: (change: StyleChange) => void;
|
|
496
496
|
}
|
|
497
|
+
/**
|
|
498
|
+
* What a highlight paints when its caller names no style.
|
|
499
|
+
*
|
|
500
|
+
* A highlight is drawn OVER the default indigo node, the default darkgrey edge and the whitesmoke
|
|
501
|
+
* background, so it is chosen against those three rather than on its own. Okabe-Ito vermilion is
|
|
502
|
+
* at least Delta E 16 from each of them at normal vision and under all three kinds of colour
|
|
503
|
+
* blindness, and 3.5:1 against the background. The blue this replaced was Delta E 13 from the
|
|
504
|
+
* default node and under 4 for tritanopia, so a route through default nodes disappeared into
|
|
505
|
+
* them. `test/catalog/default-palette-quality.test.ts` measures it.
|
|
506
|
+
*
|
|
507
|
+
* Colour alone is not enough for an edge: a thin line at the default width reads as a thin line
|
|
508
|
+
* whatever its colour, so a highlighted edge is also drawn three times as wide. A node gets no
|
|
509
|
+
* size, because a size here would flatten whatever size encoding the layers beneath it drew.
|
|
510
|
+
*/
|
|
511
|
+
export declare const DEFAULT_HIGHLIGHT: {
|
|
512
|
+
readonly color: "#D55E00";
|
|
513
|
+
readonly edgeWidth: number;
|
|
514
|
+
};
|
|
497
515
|
/**
|
|
498
516
|
* Build the style stack one session holds.
|
|
499
517
|
* @param sources - What a selector compiles against, the element's own layers, the scales, the
|
|
@@ -21,8 +21,6 @@
|
|
|
21
21
|
* compile error rather than a silent no-op.
|
|
22
22
|
* - `edge.curvature` is a switch, not an amount. The edge renderer offsets its control points by
|
|
23
23
|
* a fixed fraction of the edge length, so "curve this edge" is the only thing it can be told.
|
|
24
|
-
* - `node.glowStrength` is drawn, but Babylon keeps a glow's intensity on the LAYER rather than
|
|
25
|
-
* on a mesh, so two glowing styles on screen share whichever strength was applied last.
|
|
26
24
|
* - `node.outline` is a colour and no width, for the same reason in the same shape: the stroke
|
|
27
25
|
* is drawn by a highlight LAYER whose blur size belongs to the layer, so every outline on
|
|
28
26
|
* screen is one width. This is the sentence the element's natural-language layer reads out
|
|
@@ -184,6 +184,15 @@ export interface LegendSources {
|
|
|
184
184
|
* @returns The words, or undefined when nothing in the session names that path.
|
|
185
185
|
*/
|
|
186
186
|
readonly field?: (path: Path, target: SelectorTarget) => FieldWords | undefined;
|
|
187
|
+
/**
|
|
188
|
+
* Whether a layer above selects every element a layer below it selects.
|
|
189
|
+
*
|
|
190
|
+
* Absent, only a layer that selects everything is known to cover one below it.
|
|
191
|
+
* @param above - The higher layer.
|
|
192
|
+
* @param below - The lower layer.
|
|
193
|
+
* @returns True only when every element `below` selects is also selected by `above`.
|
|
194
|
+
*/
|
|
195
|
+
readonly covers?: (above: Layer, below: Layer) => boolean;
|
|
187
196
|
}
|
|
188
197
|
/**
|
|
189
198
|
* Read the legend out of the encoding model.
|
|
@@ -104,6 +104,17 @@ export interface SelectorSource {
|
|
|
104
104
|
* @returns The id at that row.
|
|
105
105
|
*/
|
|
106
106
|
readonly edgeIdOf?: (index: number) => EdgeId;
|
|
107
|
+
/**
|
|
108
|
+
* The dense indices that carry a value for one column, ascending, when the source can list
|
|
109
|
+
* them without a walk over every element. A session answers it for a run's column.
|
|
110
|
+
*
|
|
111
|
+
* Absent, or answering undefined, nothing asks the question another way: the legend then
|
|
112
|
+
* reports a layer as painted over only by a layer that selects everything.
|
|
113
|
+
* @param path - The column path.
|
|
114
|
+
* @param target - Whether the asking layer paints nodes or edges.
|
|
115
|
+
* @returns The indices, or undefined when the column cannot be enumerated.
|
|
116
|
+
*/
|
|
117
|
+
readonly measured?: (path: Path, target: SelectorTarget) => ArrayLike<number> | undefined;
|
|
107
118
|
}
|
|
108
119
|
/**
|
|
109
120
|
* One target's half of a {@link SelectorSource}, resolved once so the predicate never chooses.
|
|
@@ -132,6 +132,22 @@ export interface SessionSelectorSource extends SelectorSource {
|
|
|
132
132
|
*/
|
|
133
133
|
readonly measured: (path: Path, target: SelectorTarget) => ArrayLike<number> | undefined;
|
|
134
134
|
}
|
|
135
|
+
/**
|
|
136
|
+
* The id of the node at one end of an edge, for the attribute keys that name an endpoint.
|
|
137
|
+
*
|
|
138
|
+
* An edge's endpoints are graph structure, so the importer removes the keys they arrived under
|
|
139
|
+
* (`src`/`dst`, or whatever the id paths name) from the record a selector reads, to keep the data
|
|
140
|
+
* table from showing them twice. Without this, `data.source == 'A'` matched no edge at all and
|
|
141
|
+
* said nothing -- the empty answer looked exactly like a correct zero. So `source` and `target`
|
|
142
|
+
* are read from the snapshot for an edge whose record holds nothing under that key; an edge that
|
|
143
|
+
* really carries a `source` attribute (a provenance field, with its endpoints under `src`/`dst`)
|
|
144
|
+
* keeps it.
|
|
145
|
+
* @param graph - The snapshot the index addresses.
|
|
146
|
+
* @param index - The dense (logical) edge index, already bounded by the caller.
|
|
147
|
+
* @param key - The attribute key, without the `data.` prefix.
|
|
148
|
+
* @returns The endpoint's node id, or undefined when the key names no endpoint.
|
|
149
|
+
*/
|
|
150
|
+
export declare function edgeEndpointOf(graph: GraphSnapshot, index: number, key: string): NodeId | undefined;
|
|
135
151
|
/**
|
|
136
152
|
* Build the selector source for one session.
|
|
137
153
|
*
|