vortx-gl 1.0.36 → 1.0.38

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 CHANGED
@@ -419,6 +419,22 @@ Visual properties are applied in the following order of precedence:
419
419
  2. **Class Styles**: Properties defined in the stylesheet for the applied classes.
420
420
  3. **Engine Defaults**: Hardcoded fallback values.
421
421
 
422
+ ### 4. Focus (Dim Everything Else)
423
+ Draw a subset normally and dim the rest. Dimmed elements stay interactive (hover, pick, select), and no element style is changed, so clearing restores the previous look.
424
+
425
+ ```typescript
426
+ // Focus two nodes; edges between them are in focus too.
427
+ engine.setFocus(["node-1", "node-2"]);
428
+
429
+ // Or name the edges in focus explicitly.
430
+ engine.setFocus(["node-1", "node-2"], ["edge-7"]);
431
+
432
+ engine.getFocus(); // { nodeIds, edgeIds? } or null
433
+ engine.clearFocus(); // same as setFocus(null) or setFocus([])
434
+ ```
435
+
436
+ Unknown ids are ignored. The dim level is `style.focusDimOpacity` (default `0.15`), read from the config passed to the engine (GraphCanvas `config` prop), e.g. `{ style: { focusDimOpacity: 0.25 } }`.
437
+
422
438
  ---
423
439
 
424
440
  ## ⚡ Energy Flow Animation
@@ -78,6 +78,11 @@ export declare const GraphConfig: {
78
78
  * selected node). Set false to suppress.
79
79
  */
80
80
  readonly showOffscreenSelectionIndicator: true;
81
+ /**
82
+ * Opacity (0–1) of every node and edge outside the current focus set
83
+ * (GraphEngine.setFocus). Unused while no focus is set.
84
+ */
85
+ readonly focusDimOpacity: 0.15;
81
86
  };
82
87
  readonly graphOptions: {
83
88
  /** Cytoscape: text-opacity 1 vs 0 on `.nodes`. */
@@ -10,6 +10,9 @@ export declare class GpuBufferManager {
10
10
  sizeBuffer: WebGLBuffer | null;
11
11
  targetPositionBuffer: WebGLBuffer | null;
12
12
  edgeIndexBuffer: WebGLBuffer | null;
13
+ focusDimBuffer: WebGLBuffer | null;
14
+ focusEdgeIndexBuffer: WebGLBuffer | null;
15
+ dimEdgeIndexBuffer: WebGLBuffer | null;
13
16
  pickingFramebuffer: WebGLFramebuffer | null;
14
17
  pickingTexture: WebGLTexture | null;
15
18
  uMatrixLoc: WebGLUniformLocation | null;
@@ -20,12 +23,28 @@ export declare class GpuBufferManager {
20
23
  uEdgeColorLoc: WebGLUniformLocation | null;
21
24
  uTransitionProgressLoc: WebGLUniformLocation | null;
22
25
  uEdgeTransitionProgressLoc: WebGLUniformLocation | null;
26
+ uFocusDimLoc: WebGLUniformLocation | null;
27
+ uBgColorLoc: WebGLUniformLocation | null;
23
28
  private gl;
24
29
  private canvas;
25
30
  constructor(gl: WebGL2RenderingContext, canvas: HTMLCanvasElement);
26
31
  init(): void;
27
32
  private initShaders;
28
33
  private initBuffers;
34
+ /**
35
+ * Upload the per-node focus dim flags (1 = dimmed, 0 = normal), one float per
36
+ * node, and enable a_dimmed. `null` disables the attribute, which makes every
37
+ * node read 0 (no dim) at zero per-frame cost. The array must cover every
38
+ * drawn node — the caller re-syncs whenever the node count changes.
39
+ */
40
+ syncFocusNodes(dimmed: Float32Array | null): void;
41
+ /** Upload the focused / out-of-focus halves of the edge index buffer. */
42
+ syncFocusEdges(focused: Uint32Array, dimmed: Uint32Array): void;
43
+ /**
44
+ * Draw `count` indices of one focus half with the edge program already in
45
+ * use and its uniforms set, then restore the edge VAO's own index buffer.
46
+ */
47
+ drawFocusEdges(which: "focused" | "dimmed", count: number): void;
29
48
  initPicking(): void;
30
49
  private createProgram;
31
50
  reallocateNodeBuffers(positions: Float32Array, colorData: Float32Array, idData: Float32Array, shapeData: Float32Array, sizeData?: Float32Array): void;
@@ -40,7 +40,23 @@ export declare class SharedOverlayState {
40
40
  /** Toggle for the off-canvas-edge dot+arrow that marks a selected node
41
41
  * outside the viewport. Mirrors GraphConfig.style.showOffscreenSelectionIndicator. */
42
42
  showOffscreenSelectionIndicator: boolean;
43
+ /**
44
+ * Focus (GraphEngine.setFocus): node indices drawn at full opacity. null
45
+ * means no focus, and every pass takes its unchanged fast path.
46
+ */
47
+ focusNodes: Set<number> | null;
48
+ /**
49
+ * Edge ids in focus. null while focusNodes is set means "derive from the
50
+ * endpoints": an edge is in focus when both of its nodes are.
51
+ */
52
+ focusEdges: Set<string> | null;
53
+ /** Opacity of out-of-focus elements. Mirrors GraphConfig.style.focusDimOpacity. */
54
+ focusDimOpacity: number;
43
55
  constructor(canvas: HTMLCanvasElement, ctx: CanvasRenderingContext2D);
56
+ /** True when a focus is set and node `i` is outside it. O(1). */
57
+ isNodeDimmed(i: number): boolean;
58
+ /** True when a focus is set and the edge (u→v, id) is outside it. O(1). */
59
+ isEdgeDimmed(u: number, v: number, id: string | undefined): boolean;
44
60
  /** World→screen projection. Writes to `out` and returns it for chaining. */
45
61
  project(x: number, y: number, viewMatrix: mat3, out: vec2): vec2;
46
62
  /** Grow projection/index buffers if the node count exceeds current capacity. */
@@ -54,6 +54,17 @@ export declare class GraphEngine {
54
54
  resolvedEdgeStyles: Map<string, ResolvedEdgeStyle>;
55
55
  resolvedEdgeSelectStyles: Map<string, ResolvedEdgeSelectStyle>;
56
56
  private _lastBackendData;
57
+ private _focusNodeIds;
58
+ private _focusEdgeIds;
59
+ /** Resolved focused node indices; null = no effective focus (fast path). */
60
+ private _focusNodeIdx;
61
+ private _focusDirty;
62
+ private _focusNodeCount;
63
+ private _focusIdMapRef;
64
+ private _focusEdgeIndicesRef;
65
+ private _focusDimData;
66
+ private _focusEdgeCount;
67
+ private _dimEdgeCount;
57
68
  constructor(canvas: HTMLCanvasElement, overlayCanvas: HTMLCanvasElement, configOverrides?: PartialGraphConfig);
58
69
  destroy(): void;
59
70
  attachMinimap(canvas: HTMLCanvasElement, intervalMs?: number): Minimap;
@@ -292,6 +303,39 @@ export declare class GraphEngine {
292
303
  period?: number;
293
304
  }): void;
294
305
  clearHighlight(): void;
306
+ /**
307
+ * Focus: draw the given nodes (and edges) normally and everything else
308
+ * dimmed to `GraphConfig.style.focusDimOpacity`. Dimmed elements stay fully
309
+ * interactive (hover, pick, select). Purely visual — no node or edge state
310
+ * is mutated, so clearFocus() restores exactly the previous look.
311
+ *
312
+ * @param nodeIds user ids, resolved like selectNodes(); unknown ids are
313
+ * ignored. `null` or an empty list clears the focus.
314
+ * @param edgeIds edge ids in focus. When omitted, an edge is in focus when
315
+ * both of its endpoints are.
316
+ *
317
+ * Ids are kept, not indices: after nodes/edges are added or removed the
318
+ * focus is re-resolved, and ids that no longer exist simply don't match.
319
+ */
320
+ setFocus(nodeIds: ReadonlyArray<string | number> | null, edgeIds?: ReadonlyArray<string>): void;
321
+ /** Remove the focus; every node and edge returns to its normal look. */
322
+ clearFocus(): void;
323
+ /**
324
+ * Current focus as passed to setFocus (canonical node ids, explicit edge
325
+ * ids or undefined), or null when no focus is set.
326
+ */
327
+ getFocus(): {
328
+ nodeIds: Array<string | number>;
329
+ edgeIds?: string[];
330
+ } | null;
331
+ /**
332
+ * Bring the resolved focus sets + GPU focus buffers in line with the
333
+ * current node/edge arrays. O(1) when nothing changed; otherwise
334
+ * O(focus + N + E), which only happens on setFocus or a node/edge set
335
+ * change — never per frame.
336
+ */
337
+ private _syncFocus;
338
+ private _isEdgeFocused;
295
339
  /** Pan/zoom the camera to frame just the given nodes (bbox + margin). */
296
340
  fitToNodes(ids: ReadonlyArray<string | number>): void;
297
341
  setTransitionState(t: number): void;
@@ -1,2 +1,2 @@
1
- export declare const NODE_VS = "#version 300 es\nlayout(location = 0) in vec2 a_position;\nlayout(location = 1) in vec4 a_color;\nlayout(location = 2) in float a_id;\nlayout(location = 3) in vec2 a_targetPosition;\nlayout(location = 4) in float a_shape;\n// Per-node size multiplier. 1.0 = base size, 2.0 = 2x bigger, etc.\n// Sourced from (input.size || input.width || 50) / 50 at ingest.\nlayout(location = 5) in float a_size;\n\nuniform mat3 u_matrix;\nuniform float u_pointSize;\n// GPU's ALIASED_POINT_SIZE_RANGE upper bound, set once at init by the\n// renderer. Clamped here so a node with size > 1 never exceeds the GPU\n// limit and silently mismatches the overlay's idea of where the sprite\n// edge is. Overlay reads the same clamp via getMaxNodePointSize().\nuniform float u_pointSizeMax;\nuniform bool u_pickingMode;\nuniform float u_transitionProgress;\n\nout vec4 v_color;\nflat out uint v_id;\nflat out int v_shape;\n\nvoid main() {\n // Interpolate between current and target position on GPU\n vec2 pos = mix(a_position, a_targetPosition, u_transitionProgress);\n vec3 position = u_matrix * vec3(pos, 1.0);\n gl_Position = vec4(position.xy, 0, 1);\n gl_PointSize = min(u_pointSize * a_size, u_pointSizeMax);\n\n // Visible nodes are forced opaque so edges (drawn before) never leak\n // through the node body. Hidden nodes (alpha < 0.001) still vanish via\n // the discard branch in the fragment shader.\n float alpha = a_color.a < 0.001 ? 0.0 : 1.0;\n v_color = vec4(a_color.rgb, alpha);\n v_id = uint(a_id);\n v_shape = int(a_shape);\n}\n";
1
+ export declare const NODE_VS = "#version 300 es\nlayout(location = 0) in vec2 a_position;\nlayout(location = 1) in vec4 a_color;\nlayout(location = 2) in float a_id;\nlayout(location = 3) in vec2 a_targetPosition;\nlayout(location = 4) in float a_shape;\n// Per-node size multiplier. 1.0 = base size, 2.0 = 2x bigger, etc.\n// Sourced from (input.size || input.width || 50) / 50 at ingest.\nlayout(location = 5) in float a_size;\n// Focus: 1.0 = out of focus (dimmed), 0.0 = normal. The array is only\n// enabled while a focus is set; disabled, WebGL feeds the default generic\n// attribute value (0,0,0,1), so its x component reads 0.0 = no dim.\nlayout(location = 6) in float a_dimmed;\n\nuniform mat3 u_matrix;\nuniform float u_pointSize;\n// GPU's ALIASED_POINT_SIZE_RANGE upper bound, set once at init by the\n// renderer. Clamped here so a node with size > 1 never exceeds the GPU\n// limit and silently mismatches the overlay's idea of where the sprite\n// edge is. Overlay reads the same clamp via getMaxNodePointSize().\nuniform float u_pointSizeMax;\nuniform bool u_pickingMode;\nuniform float u_transitionProgress;\n// Opacity of out-of-focus nodes (GraphConfig.style.focusDimOpacity) and the\n// canvas clear colour they blend toward.\nuniform float u_focusDim;\nuniform vec3 u_bgColor;\n\nout vec4 v_color;\nflat out uint v_id;\nflat out int v_shape;\n\nvoid main() {\n // Interpolate between current and target position on GPU\n vec2 pos = mix(a_position, a_targetPosition, u_transitionProgress);\n vec3 position = u_matrix * vec3(pos, 1.0);\n gl_Position = vec4(position.xy, 0, 1);\n gl_PointSize = min(u_pointSize * a_size, u_pointSizeMax);\n\n // Visible nodes are forced opaque so edges (drawn before) never leak\n // through the node body. Hidden nodes (alpha < 0.001) still vanish via\n // the discard branch in the fragment shader.\n float alpha = a_color.a < 0.001 ? 0.0 : 1.0;\n // A dimmed node is blended toward the background colour instead of being\n // made translucent: same look as alpha = u_focusDim over the clear colour,\n // but the sprite stays opaque, so edges still don't leak through it and\n // the hidden-node alpha test above is untouched.\n vec3 rgb = mix(a_color.rgb, mix(u_bgColor, a_color.rgb, u_focusDim), a_dimmed);\n v_color = vec4(rgb, alpha);\n v_id = uint(a_id);\n v_shape = int(a_shape);\n}\n";
2
2
  export declare const NODE_FS = "#version 300 es\nprecision highp float;\n\nin vec4 v_color;\nflat in uint v_id;\nflat in int v_shape;\n\nuniform bool u_pickingMode;\nout vec4 outColor;\n\nvoid main() {\n vec2 p = gl_PointCoord - vec2(0.5);\n\n // Every shape fills the full pointSize sprite (extent 0.5 from center)\n // so edges + overlay decorations, which assume node radius == pointSize/2,\n // actually reach the visible node border.\n if (v_shape == 0) { // Circle\n if (length(p) > 0.5) discard;\n } else if (v_shape == 1) { // Square\n if (abs(p.x) > 0.5 || abs(p.y) > 0.5) discard;\n } else if (v_shape == 2) { // Triangle\n if (p.y > 0.5 || p.y < -0.5 || abs(p.x) > (0.5 - (p.y + 0.5) * 0.5)) discard;\n } else if (v_shape == 3) { // Diamond\n if (abs(p.x) + abs(p.y) > 0.5) discard;\n } else if (v_shape == 4) { // Round Rectangle\n // Inner half-extent 0.3 + corner radius 0.2 \u2192 still reaches 0.5 at the\n // edges, and the corner radius (0.2\u00B7diameter = 0.4\u00B7radius) now MATCHES\n // drawShapePath's roundRect([r*0.4]) so the GPU fill no longer pokes\n // past the Canvas2D border/shadow at the corners.\n float dist = length(max(abs(p) - 0.3, 0.0));\n if (dist > 0.2) discard;\n } else if (v_shape == 5) { // Cut Rectangle\n if (abs(p.x) > 0.5 || abs(p.y) > 0.5 || abs(p.x) + abs(p.y) > 0.85) discard;\n }\n\n // Hidden nodes (alpha == 0): skip rendering AND picking.\n if (v_color.a < 0.001) discard;\n\n if (u_pickingMode) {\n uint id = v_id + 1u;\n float r = float((id >> 16) & 0xFFu) / 255.0;\n float g = float((id >> 8) & 0xFFu) / 255.0;\n float b = float(id & 0xFFu) / 255.0;\n outColor = vec4(r, g, b, 1.0);\n } else {\n outColor = v_color;\n }\n}\n";
@@ -375,6 +375,12 @@ export interface Combo {
375
375
  * button is also hidden for managed combos.
376
376
  */
377
377
  managed?: boolean;
378
+ /**
379
+ * Plain hull, e.g. to show clusters: no HUD pill and no collapse/edit
380
+ * buttons, and the fill + border come from `color`. Collapse it from code
381
+ * only. Pair with `managed` to keep members from being edited by drag.
382
+ */
383
+ decorative?: boolean;
378
384
  }
379
385
  /**
380
386
  * An item in the radial (circular) context menu.
@@ -758,4 +764,14 @@ export interface OverlayRenderParams extends InteractionState {
758
764
  defaultLabelMaxZoom?: number;
759
765
  config: import('./config').ResolvedGraphConfig;
760
766
  spatialIndex?: import('./spatialIndex').SpatialIndex;
767
+ /**
768
+ * Focus (GraphEngine.setFocus): node indices drawn at full opacity; every
769
+ * other node is dimmed to config.style.focusDimOpacity. null/undefined = no focus.
770
+ */
771
+ focusNodes?: Set<number> | null;
772
+ /**
773
+ * Edge ids in focus. null/undefined while focusNodes is set = an edge is in
774
+ * focus when both of its endpoints are.
775
+ */
776
+ focusEdges?: Set<string> | null;
761
777
  }