@graphty/graphty-element 2.2.0 → 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/dist/graphty.js CHANGED
@@ -1,4 +1,4 @@
1
- import { A as e, a as E, b as r, D as O, E as o, d as S, G as R, e as _, I as t, L, N as C, O as A, R as N, S as n, f as I, g as T, h as g, U as i, i as l } from "./chunks/index-2VIpMJZP.js";
1
+ import { A as e, a as E, b as r, D as O, E as o, d as S, G as R, e as _, I as t, L, N as C, O as A, R as N, S as n, f as I, g as T, h as g, U as i, i as l } from "./chunks/index-Cz47b_vU.js";
2
2
  import { a as p, S as G, b as f } from "./chunks/paletteRegistry-NrWOKT-A.js";
3
3
  import { D as y, E as D } from "./chunks/DataSource-DEg3igzS.js";
4
4
  import { k as P, l as U, g as c, h as m, A as u, C as x, i as B } from "./chunks/Algorithm-RQ629NLb.js";
package/dist/session.js CHANGED
@@ -1,10 +1,10 @@
1
1
  import { R as A, e as R } from "./chunks/types-B7bX5c0K.js";
2
2
  import { A as _, a as C, G as g, i as m, b as L } from "./chunks/GraphtyError-BwcnblTH.js";
3
3
  import { g as U, h as I, i as N } from "./chunks/Algorithm-RQ629NLb.js";
4
- import { D as h } from "./chunks/GraphSession-CtqyVNsw.js";
5
- import { a as y, b as D, c as w, Q as b, R as v, d as G, e as x, S as z, T as F, f as M, i as j, g as H, h as Y, j as k } from "./chunks/GraphSession-CtqyVNsw.js";
4
+ import { D as h } from "./chunks/GraphSession-D3AV8tpF.js";
5
+ import { a as y, b as D, c as w, Q as b, R as v, d as G, e as x, S as z, T as F, f as M, i as j, g as H, h as Y, j as k } from "./chunks/GraphSession-D3AV8tpF.js";
6
6
  import { R as Q, w as X, x as B, y as J, z as K, A as V } from "./chunks/paletteRegistry-NrWOKT-A.js";
7
- import { a as n } from "./chunks/scales-BUS7NWy2.js";
7
+ import { a as n } from "./chunks/scales-ulKMZPfG.js";
8
8
  const u = Object.freeze({
9
9
  /** Above this node count the element draws less visual detail. A shipped default, not measured. */
10
10
  largeGraphThreshold: 1e4,
@@ -178,7 +178,7 @@ export declare class Graph implements GraphContext {
178
178
  *
179
179
  * The settings take effect on the next layout the element runs. `preSteps` is read when a
180
180
  * layout starts, so setting it after a graph has already settled changes nothing that is
181
- * already on screen.
181
+ * already on screen. `labels.declutter` is the exception: it takes effect on the next frame.
182
182
  * @param behavior - The fields to change. Anything omitted keeps its current value.
183
183
  */
184
184
  setLayoutBehavior(behavior: GraphBehaviorConfig): void;
@@ -212,9 +212,8 @@ export declare class Node {
212
212
  * EFFECTS only on the branch that rebuilds the mesh, so an `instance` channel's edit reached
213
213
  * the screen only if some unrelated `mesh` channel happened to change in the same repaint.
214
214
  * That is why a glow was drawn when its colour was in the stack before the first frame and
215
- * ignored when a layer added it afterwards, and why `node.glowStrength` had been declared a
216
- * `mesh` channel: minting a source mesh per strength was the only way to force the rebuild
217
- * that made the strength visible.
215
+ * ignored when a layer added it afterwards. (`node.glowStrength` is a `mesh` channel for a
216
+ * different reason: a glow's strength is set per SOURCE mesh, like its colour.)
218
217
  *
219
218
  * So this method is everything the `instance` role promises, and `paintFrom` calls it on BOTH
220
219
  * of its branches -- once when it has just rebuilt the mesh, because every one of these lives
@@ -27,6 +27,9 @@ export declare const GraphBehaviorOpts: z.ZodObject<{
27
27
  node: z.ZodPrefault<z.ZodObject<{
28
28
  pinOnDrag: z.ZodDefault<z.ZodBoolean>;
29
29
  }, z.core.$strict>>;
30
+ labels: z.ZodPrefault<z.ZodObject<{
31
+ declutter: z.ZodDefault<z.ZodBoolean>;
32
+ }, z.core.$strict>>;
30
33
  fetchNodes: z.ZodOptional<z.ZodCustom<Function, Function>>;
31
34
  fetchEdges: z.ZodOptional<z.ZodCustom<Function, Function>>;
32
35
  }, z.core.$strict>;
@@ -928,6 +928,9 @@ declare const StyleTemplateV1: z.ZodObject<{
928
928
  node: z.ZodPrefault<z.ZodObject<{
929
929
  pinOnDrag: z.ZodDefault<z.ZodBoolean>;
930
930
  }, z.core.$strict>>;
931
+ labels: z.ZodPrefault<z.ZodObject<{
932
+ declutter: z.ZodDefault<z.ZodBoolean>;
933
+ }, z.core.$strict>>;
931
934
  fetchNodes: z.ZodOptional<z.ZodCustom<Function, Function>>;
932
935
  fetchEdges: z.ZodOptional<z.ZodCustom<Function, Function>>;
933
936
  }, z.core.$strict>>;
@@ -1861,6 +1864,9 @@ export declare const StyleTemplate: z.ZodDiscriminatedUnion<[z.ZodObject<{
1861
1864
  node: z.ZodPrefault<z.ZodObject<{
1862
1865
  pinOnDrag: z.ZodDefault<z.ZodBoolean>;
1863
1866
  }, z.core.$strict>>;
1867
+ labels: z.ZodPrefault<z.ZodObject<{
1868
+ declutter: z.ZodDefault<z.ZodBoolean>;
1869
+ }, z.core.$strict>>;
1864
1870
  fetchNodes: z.ZodOptional<z.ZodCustom<Function, Function>>;
1865
1871
  fetchEdges: z.ZodOptional<z.ZodCustom<Function, Function>>;
1866
1872
  }, z.core.$strict>>;
@@ -425,13 +425,16 @@ export declare class Graphty extends LitElement {
425
425
  * unstepped force layout is a graph in mid-flight, and how far it has flown depends on when
426
426
  * the picture was taken. `layout.stepMultiplier`, `layout.minDelta` and
427
427
  * `layout.zoomStepInterval` pace the rest of it, and `node.pinOnDrag` decides whether a node
428
- * a reader drags stays where they put it.
428
+ * a reader drags stays where they put it. `labels.declutter` (off by default) hides a node
429
+ * label whose words would be drawn over another label's, keeping a selected node's label
430
+ * first and then the label of the node with more edges; it takes effect on the next frame.
429
431
  *
430
432
  * Merged over what is already set, so naming one field leaves the others alone.
431
433
  * @since 2.0.0
432
434
  * @example
433
435
  * ```typescript
434
436
  * element.layoutBehavior = { layout: { preSteps: 1000 } };
437
+ * element.layoutBehavior = { labels: { declutter: true } };
435
438
  * ```
436
439
  * @returns The behaviour settings, or undefined when none have been set on this element
437
440
  */
@@ -67,6 +67,8 @@ export declare class DataManager implements Manager {
67
67
  * compiled, answered `undefined` for the edge whose id is `"0"`, and said nothing about it.
68
68
  */
69
69
  edges: Map<string, Edge>;
70
+ /** Goes up on every edge added or removed, so a cache over the edge set knows it is stale. */
71
+ edgeVersion: number;
70
72
  nodeCache: Map<NodeIdType, Node>;
71
73
  edgeCache: EdgeMap;
72
74
  /**
@@ -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
+ }
@@ -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
- * KNOWN LIMIT, stated rather than hidden: `intensity` is a property of the LAYER, not of a
118
- * mesh, so with two glowing styles on screen the last one applied sets the strength for both.
119
- * Colour is per style (see {@link NodeEffects.resolveRenderedMesh}). Per-style strength needs
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
@@ -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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@graphty/graphty-element",
3
- "version": "2.2.0",
3
+ "version": "2.2.1",
4
4
  "description": "A Web Component library for 3D/2D graph visualization built with Lit and Babylon.js",
5
5
  "type": "module",
6
6
  "customElements": "./dist/custom-elements.json",
@@ -185,8 +185,8 @@
185
185
  "toposort": "^2.0.2",
186
186
  "zod": "^3.25.28",
187
187
  "@graphty/algorithms": "^2.0.2",
188
- "@graphty/graph-format": "^1.0.4",
189
- "@graphty/layout": "^1.9.0"
188
+ "@graphty/layout": "^1.9.0",
189
+ "@graphty/graph-format": "^1.0.4"
190
190
  },
191
191
  "overrides": {
192
192
  "storybook": "$storybook"