@cosmos.gl/graph 2.2.1 → 2.3.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.
@@ -55,6 +55,11 @@ export declare class Points extends CoreModule {
55
55
  trackPointsByIndices(indices?: number[] | undefined): void;
56
56
  getTrackedPositionsMap(): Map<number, [number, number]>;
57
57
  getSampledPointPositionsMap(): Map<number, [number, number]>;
58
+ getSampledPoints(): {
59
+ indices: number[];
60
+ positions: number[];
61
+ };
62
+ getTrackedPositionsArray(): number[];
58
63
  private swapFbo;
59
64
  private rescaleInitialNodePositions;
60
65
  }
@@ -1,9 +1,11 @@
1
1
  export declare const defaultPointColor = "#b3b3b3";
2
2
  export declare const defaultGreyoutPointOpacity: undefined;
3
3
  export declare const defaultGreyoutPointColor: undefined;
4
+ export declare const defaultPointOpacity = 1;
4
5
  export declare const defaultPointSize = 4;
5
6
  export declare const defaultLinkColor = "#666666";
6
7
  export declare const defaultGreyoutLinkOpacity = 0.1;
8
+ export declare const defaultLinkOpacity = 1;
7
9
  export declare const defaultLinkWidth = 1;
8
10
  export declare const defaultBackgroundColor = "#222222";
9
11
  export declare const defaultConfigValues: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cosmos.gl/graph",
3
- "version": "2.2.1",
3
+ "version": "2.3.0",
4
4
  "description": "GPU-based force graph layout and rendering",
5
5
  "jsdelivr": "dist/index.min.js",
6
6
  "main": "dist/index.js",
@@ -53,6 +53,7 @@
53
53
  "@types/d3-selection": "^3.0.2",
54
54
  "@types/d3-transition": "^3.0.1",
55
55
  "@types/d3-zoom": "^3.0.1",
56
+ "@types/dompurify": "^3.0.5",
56
57
  "@typescript-eslint/eslint-plugin": "^5.30.5",
57
58
  "@typescript-eslint/parser": "^5.30.5",
58
59
  "@zerollup/ts-transform-paths": "^1.7.18",
@@ -84,6 +85,7 @@
84
85
  "d3-selection": "^3.0.0",
85
86
  "d3-transition": "^3.0.1",
86
87
  "d3-zoom": "^3.0.0",
88
+ "dompurify": "^3.2.6",
87
89
  "gl-bench": "^1.0.42",
88
90
  "gl-matrix": "^3.4.3",
89
91
  "random": "^4.1.0",
package/src/config.ts CHANGED
@@ -4,9 +4,11 @@ import {
4
4
  defaultPointColor,
5
5
  defaultGreyoutPointOpacity,
6
6
  defaultGreyoutPointColor,
7
+ defaultPointOpacity,
7
8
  defaultPointSize,
8
9
  defaultLinkColor,
9
10
  defaultGreyoutLinkOpacity,
11
+ defaultLinkOpacity,
10
12
  defaultLinkWidth,
11
13
  defaultBackgroundColor,
12
14
  defaultConfigValues,
@@ -74,6 +76,15 @@ export interface GraphConfigInterface {
74
76
  * Default value: `4`
75
77
  */
76
78
  pointSize?: number;
79
+
80
+ /**
81
+ * Universal opacity value applied to all points.
82
+ * This value multiplies with individual point alpha values (if set via setPointColors).
83
+ * Useful for dynamically controlling opacity of all points without updating individual RGBA arrays.
84
+ * Default value: `1.0`
85
+ */
86
+ pointOpacity?: number;
87
+
77
88
  /**
78
89
  * Scale factor for the point size.
79
90
  * Default value: `1`
@@ -128,6 +139,14 @@ export interface GraphConfigInterface {
128
139
  */
129
140
  linkColor?: string | [number, number, number, number];
130
141
 
142
+ /**
143
+ * Universal opacity value applied to all links.
144
+ * This value multiplies with individual link alpha values (if set via setLinkColors).
145
+ * Useful for dynamically controlling opacity of all links without updating individual RGBA arrays.
146
+ * Default value: `1.0`
147
+ */
148
+ linkOpacity?: number;
149
+
131
150
  /**
132
151
  * Greyed out link opacity value when the selection is active.
133
152
  * Default value: `0.1`
@@ -517,6 +536,7 @@ export class GraphConfig implements GraphConfigInterface {
517
536
  public pointGreyoutOpacity = defaultGreyoutPointOpacity
518
537
  public pointGreyoutColor = defaultGreyoutPointColor
519
538
  public pointSize = defaultPointSize
539
+ public pointOpacity = defaultPointOpacity
520
540
  public pointSizeScale = defaultConfigValues.pointSizeScale
521
541
  public hoveredPointCursor = defaultConfigValues.hoveredPointCursor
522
542
  public renderHoveredPointRing = defaultConfigValues.renderHoveredPointRing
@@ -524,6 +544,7 @@ export class GraphConfig implements GraphConfigInterface {
524
544
  public focusedPointRingColor = defaultConfigValues.focusedPointRingColor
525
545
  public focusedPointIndex = defaultConfigValues.focusedPointIndex
526
546
  public linkColor = defaultLinkColor
547
+ public linkOpacity = defaultLinkOpacity
527
548
  public linkGreyoutOpacity = defaultGreyoutLinkOpacity
528
549
  public linkWidth = defaultLinkWidth
529
550
  public linkWidthScale = defaultConfigValues.linkWidthScale
package/src/helper.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { color as d3Color } from 'd3-color'
2
2
  import regl from 'regl'
3
+ import DOMPurify from 'dompurify'
3
4
 
4
5
  export const isFunction = <T>(a: T): boolean => typeof a === 'function'
5
6
  export const isArray = <T>(a: unknown | T[]): a is T[] => Array.isArray(a)
@@ -50,3 +51,24 @@ export function clamp (num: number, min: number, max: number): number {
50
51
  export function isNumber (value: number | undefined | null | typeof NaN): boolean {
51
52
  return value !== undefined && value !== null && !Number.isNaN(value)
52
53
  }
54
+
55
+ /**
56
+ * Sanitizes HTML content to prevent XSS attacks using DOMPurify
57
+ *
58
+ * This function is used internally to sanitize HTML content before setting innerHTML,
59
+ * such as in attribution text. It uses a safe default configuration that allows
60
+ * only common safe HTML elements and attributes.
61
+ *
62
+ * @param html The HTML string to sanitize
63
+ * @param options Optional DOMPurify configuration options to override defaults
64
+ * @returns Sanitized HTML string safe for innerHTML usage
65
+ */
66
+ export function sanitizeHtml (html: string, options?: DOMPurify.Config): string {
67
+ return DOMPurify.sanitize(html, {
68
+ // Default configuration: allow common safe HTML elements and attributes
69
+ ALLOWED_TAGS: ['a', 'b', 'i', 'em', 'strong', 'span', 'div', 'p', 'br'],
70
+ ALLOWED_ATTR: ['href', 'target', 'class', 'id', 'style'],
71
+ ALLOW_DATA_ATTR: false,
72
+ ...options,
73
+ })
74
+ }
package/src/index.ts CHANGED
@@ -5,7 +5,7 @@ import { D3ZoomEvent } from 'd3-zoom'
5
5
  import { D3DragEvent } from 'd3-drag'
6
6
  import regl from 'regl'
7
7
  import { GraphConfig, GraphConfigInterface } from '@/graph/config'
8
- import { getRgbaColor, readPixels } from '@/graph/helper'
8
+ import { getRgbaColor, readPixels, sanitizeHtml } from '@/graph/helper'
9
9
  import { ForceCenter } from '@/graph/modules/ForceCenter'
10
10
  import { ForceGravity } from '@/graph/modules/ForceGravity'
11
11
  import { ForceLink, LinkDirection } from '@/graph/modules/ForceLink'
@@ -322,6 +322,17 @@ export class Graph {
322
322
  this._needsPointColorUpdate = true
323
323
  }
324
324
 
325
+ /**
326
+ * Gets the current colors of the graph points.
327
+ *
328
+ * @returns {Float32Array} A Float32Array representing the colors of points in the format [r1, g1, b1, a1, r2, g2, b2, a2, ..., rn, gn, bn, an],
329
+ * where each color is in RGBA format. Returns an empty Float32Array if no point colors are set.
330
+ */
331
+ public getPointColors (): Float32Array {
332
+ if (this._isDestroyed) return new Float32Array()
333
+ return this.graph.pointColors ?? new Float32Array()
334
+ }
335
+
325
336
  /**
326
337
  * Sets the sizes for the graph points.
327
338
  *
@@ -335,6 +346,17 @@ export class Graph {
335
346
  this._needsPointSizeUpdate = true
336
347
  }
337
348
 
349
+ /**
350
+ * Gets the current sizes of the graph points.
351
+ *
352
+ * @returns {Float32Array} A Float32Array representing the sizes of points in the format [size1, size2, ..., sizen],
353
+ * where `n` is the index of the point. Returns an empty Float32Array if no point sizes are set.
354
+ */
355
+ public getPointSizes (): Float32Array {
356
+ if (this._isDestroyed) return new Float32Array()
357
+ return this.graph.pointSizes ?? new Float32Array()
358
+ }
359
+
338
360
  /**
339
361
  * Sets the links for the graph.
340
362
  *
@@ -367,6 +389,17 @@ export class Graph {
367
389
  this._needsLinkColorUpdate = true
368
390
  }
369
391
 
392
+ /**
393
+ * Gets the current colors of the graph links.
394
+ *
395
+ * @returns {Float32Array} A Float32Array representing the colors of links in the format [r1, g1, b1, a1, r2, g2, b2, a2, ..., rn, gn, bn, an],
396
+ * where each color is in RGBA format. Returns an empty Float32Array if no link colors are set.
397
+ */
398
+ public getLinkColors (): Float32Array {
399
+ if (this._isDestroyed) return new Float32Array()
400
+ return this.graph.linkColors ?? new Float32Array()
401
+ }
402
+
370
403
  /**
371
404
  * Sets the widths for the graph links.
372
405
  *
@@ -380,6 +413,17 @@ export class Graph {
380
413
  this._needsLinkWidthUpdate = true
381
414
  }
382
415
 
416
+ /**
417
+ * Gets the current widths of the graph links.
418
+ *
419
+ * @returns {Float32Array} A Float32Array representing the widths of links in the format [width1, width2, ..., widthn],
420
+ * where `n` is the index of the link. Returns an empty Float32Array if no link widths are set.
421
+ */
422
+ public getLinkWidths (): Float32Array {
423
+ if (this._isDestroyed) return new Float32Array()
424
+ return this.graph.linkWidths ?? new Float32Array()
425
+ }
426
+
383
427
  /**
384
428
  * Sets the arrows for the graph links.
385
429
  *
@@ -882,6 +926,17 @@ export class Graph {
882
926
  return this.points.getTrackedPositionsMap()
883
927
  }
884
928
 
929
+ /**
930
+ * Get current X and Y coordinates of the tracked points as an array.
931
+ * @returns Array of point positions in the format [x1, y1, x2, y2, ..., xn, yn] for tracked points only.
932
+ * The positions are ordered by the tracking indices (same order as provided to trackPointPositionsByIndices).
933
+ * Returns an empty array if no points are being tracked.
934
+ */
935
+ public getTrackedPointPositionsArray (): number[] {
936
+ if (this._isDestroyed || !this.points) return []
937
+ return this.points.getTrackedPositionsArray()
938
+ }
939
+
885
940
  /**
886
941
  * For the points that are currently visible on the screen, get a sample of point indices with their coordinates.
887
942
  * The resulting number of points will depend on the `pointSamplingDistance` configuration property,
@@ -893,6 +948,17 @@ export class Graph {
893
948
  return this.points.getSampledPointPositionsMap()
894
949
  }
895
950
 
951
+ /**
952
+ * For the points that are currently visible on the screen, get a sample of point indices and positions.
953
+ * The resulting number of points will depend on the `pointSamplingDistance` configuration property,
954
+ * and the sampled points will be evenly distributed.
955
+ * @returns An object containing arrays of point indices and positions.
956
+ */
957
+ public getSampledPoints (): { indices: number[]; positions: number[] } {
958
+ if (this._isDestroyed || !this.points) return { indices: [], positions: [] }
959
+ return this.points.getSampledPoints()
960
+ }
961
+
896
962
  /**
897
963
  * Gets the X-axis of rescaling function.
898
964
  *
@@ -920,10 +986,15 @@ export class Graph {
920
986
  public start (alpha = 1): void {
921
987
  if (this._isDestroyed) return
922
988
  if (!this.graph.pointsNumber) return
923
- this.store.isSimulationRunning = true
989
+
990
+ // Only start the simulation if alpha > 0
991
+ if (alpha > 0) {
992
+ this.store.isSimulationRunning = true
993
+ this.store.simulationProgress = 0
994
+ this.config.onSimulationStart?.()
995
+ }
996
+
924
997
  this.store.alpha = alpha
925
- this.store.simulationProgress = 0
926
- this.config.onSimulationStart?.()
927
998
  this.stopFrames()
928
999
  this.frame()
929
1000
  }
@@ -1330,7 +1401,12 @@ export class Graph {
1330
1401
  font-size: 0.7rem;
1331
1402
  font-family: inherit;
1332
1403
  `
1333
- this.attributionDivElement.innerHTML = this.config.attribution
1404
+ // Sanitize the attribution HTML content to prevent XSS attacks
1405
+ // Use more permissive settings for attribution since it's controlled by the library user
1406
+ this.attributionDivElement.innerHTML = sanitizeHtml(this.config.attribution, {
1407
+ ALLOWED_TAGS: ['a', 'b', 'i', 'em', 'strong', 'span', 'div', 'p', 'br', 'img'],
1408
+ ALLOWED_ATTR: ['href', 'target', 'class', 'id', 'style', 'src', 'alt', 'title'],
1409
+ })
1334
1410
  this.store.div?.appendChild(this.attributionDivElement)
1335
1411
  }
1336
1412
  }
@@ -5,6 +5,7 @@ varying vec2 pos;
5
5
  varying float arrowLength;
6
6
  varying float useArrow;
7
7
  varying float smoothing;
8
+ varying float arrowWidthFactor;
8
9
 
9
10
  float map(float value, float min1, float max1, float min2, float max2) {
10
11
  return min2 + (value - min1) * (max2 - min2) / (max1 - min1);
@@ -17,7 +18,7 @@ void main() {
17
18
  if (useArrow > 0.5) {
18
19
  float end_arrow = 0.5 + arrowLength / 2.0;
19
20
  float start_arrow = end_arrow - arrowLength;
20
- float arrowWidthDelta = 0.25;
21
+ float arrowWidthDelta = arrowWidthFactor / 2.0;
21
22
  float linkOpacity = rgbaColor.a * smoothstep(0.5 - arrowWidthDelta, 0.5 - arrowWidthDelta - smoothing / 2.0, abs(pos.y));
22
23
  float arrowOpacity = 1.0;
23
24
  if (pos.x > start_arrow && pos.x < start_arrow + arrowLength) {
@@ -15,6 +15,7 @@ uniform float spaceSize;
15
15
  uniform vec2 screenSize;
16
16
  uniform vec2 linkVisibilityDistanceRange;
17
17
  uniform float linkVisibilityMinTransparency;
18
+ uniform float linkOpacity;
18
19
  uniform float greyoutOpacity;
19
20
  uniform float curvedWeight;
20
21
  uniform float curvedLinkControlPointDistance;
@@ -27,6 +28,7 @@ varying vec2 pos;
27
28
  varying float arrowLength;
28
29
  varying float useArrow;
29
30
  varying float smoothing;
31
+ varying float arrowWidthFactor;
30
32
 
31
33
  float map(float value, float min1, float max1, float min2, float max2) {
32
34
  return min2 + (value - min1) * (max2 - min2) / (max1 - min1);
@@ -95,9 +97,11 @@ void main() {
95
97
  float linkWidth = width * widthScale;
96
98
  float k = 2.0;
97
99
  // Arrow width is proportionally larger than the line width
98
- float arrowWidth = max(5.0, linkWidth * k);
100
+ float arrowWidth = linkWidth * k;
99
101
  arrowWidth *= arrowSizeScale;
100
102
 
103
+ float arrowWidthDifference = arrowWidth - linkWidth;
104
+
101
105
  // Calculate arrow width in pixels
102
106
  float arrowWidthPx = calculateArrowWidth(arrowWidth);
103
107
 
@@ -108,9 +112,11 @@ void main() {
108
112
 
109
113
  useArrow = arrow;
110
114
  if (useArrow > 0.5) {
111
- linkWidth *= 2.0;
115
+ linkWidth += arrowWidthDifference;
112
116
  }
113
117
 
118
+ arrowWidthFactor = arrowWidthDifference / linkWidth;
119
+
114
120
  // Calculate final link width in pixels with smoothing
115
121
  float linkWidthPx = calculateLinkWidth(linkWidth);
116
122
  float smoothingPx = 0.5 / transformationMatrix[0][0];
@@ -120,7 +126,7 @@ void main() {
120
126
  // Calculate final color with opacity based on link distance
121
127
  vec3 rgbColor = color.rgb;
122
128
  // Adjust opacity based on link distance
123
- float opacity = color.a * max(linkVisibilityMinTransparency, map(linkDistPx, linkVisibilityDistanceRange.g, linkVisibilityDistanceRange.r, 0.0, 1.0));
129
+ float opacity = color.a * linkOpacity * max(linkVisibilityMinTransparency, map(linkDistPx, linkVisibilityDistanceRange.g, linkVisibilityDistanceRange.r, 0.0, 1.0));
124
130
 
125
131
  // Apply greyed out opacity if either endpoint is greyed out
126
132
  if (greyoutStatusA.r > 0.0 || greyoutStatusB.r > 0.0) {
@@ -69,6 +69,7 @@ export class Lines extends CoreModule {
69
69
  screenSize: () => store.screenSize,
70
70
  linkVisibilityDistanceRange: () => config.linkVisibilityDistanceRange,
71
71
  linkVisibilityMinTransparency: () => config.linkVisibilityMinTransparency,
72
+ linkOpacity: () => config.linkOpacity,
72
73
  greyoutOpacity: () => config.linkGreyoutOpacity,
73
74
  scaleLinksOnZoom: () => config.scaleLinksOnZoom,
74
75
  maxPointSize: () => store.maxPointSize,
@@ -14,6 +14,7 @@ uniform bool scalePointsOnZoom;
14
14
  uniform float pointIndex;
15
15
  uniform float maxPointSize;
16
16
  uniform vec4 color;
17
+ uniform float universalPointOpacity;
17
18
  uniform float greyoutOpacity;
18
19
  uniform bool darkenGreyout;
19
20
  uniform vec4 backgroundColor;
@@ -41,7 +42,7 @@ void main () {
41
42
  vec4 pointPosition = texture2D(positionsTexture, textureCoordinates / pointsTextureSize);
42
43
 
43
44
  rgbColor = color.rgb;
44
- pointOpacity = color.a;
45
+ pointOpacity = color.a * universalPointOpacity;
45
46
  vec4 greyoutStatus = texture2D(pointGreyoutStatusTexture, textureCoordinates / pointsTextureSize);
46
47
  if (greyoutStatus.r > 0.0) {
47
48
  if (greyoutColor[0] != -1.0) {
@@ -15,6 +15,7 @@ uniform float sizeScale;
15
15
  uniform float spaceSize;
16
16
  uniform vec2 screenSize;
17
17
  uniform float greyoutOpacity;
18
+ uniform float pointOpacity;
18
19
  uniform vec4 greyoutColor;
19
20
  uniform vec4 backgroundColor;
20
21
  uniform bool scalePointsOnZoom;
@@ -51,7 +52,7 @@ void main() {
51
52
  gl_PointSize = calculatePointSize(size * sizeScale);
52
53
 
53
54
  rgbColor = color.rgb;
54
- alpha = color.a;
55
+ alpha = color.a * pointOpacity;
55
56
 
56
57
  // Adjust alpha of selected points
57
58
  vec4 greyoutStatus = texture2D(pointGreyoutStatus, (textureCoords + 0.5) / pointsTextureSize);
@@ -232,6 +232,7 @@ export class Points extends CoreModule {
232
232
  transformationMatrix: () => store.transform,
233
233
  spaceSize: () => store.adjustedSpaceSize,
234
234
  screenSize: () => store.screenSize,
235
+ pointOpacity: () => config.pointOpacity,
235
236
  greyoutOpacity: () => config.pointGreyoutOpacity ?? -1,
236
237
  greyoutColor: () => store.greyoutPointColor,
237
238
  backgroundColor: () => store.backgroundColor,
@@ -412,6 +413,7 @@ export class Points extends CoreModule {
412
413
  scalePointsOnZoom: () => config.scalePointsOnZoom,
413
414
  maxPointSize: () => store.maxPointSize,
414
415
  pointGreyoutStatusTexture: () => this.greyoutStatusFbo,
416
+ universalPointOpacity: () => config.pointOpacity,
415
417
  greyoutOpacity: () => config.pointGreyoutOpacity ?? -1,
416
418
  darkenGreyout: () => store.darkenGreyout,
417
419
  backgroundColor: () => store.backgroundColor,
@@ -695,6 +697,47 @@ export class Points extends CoreModule {
695
697
  return positions
696
698
  }
697
699
 
700
+ public getSampledPoints (): { indices: number[]; positions: number[] } {
701
+ const indices: number[] = []
702
+ const positions: number[] = []
703
+ if (!this.sampledPointsFbo) return { indices, positions }
704
+
705
+ this.clearSampledPointsFboCommand?.()
706
+ this.fillSampledPointsFboCommand?.()
707
+ const pixels = readPixels(this.reglInstance, this.sampledPointsFbo as regl.Framebuffer2D)
708
+
709
+ for (let i = 0; i < pixels.length / 4; i++) {
710
+ const index = pixels[i * 4]
711
+ const isNotEmpty = !!pixels[i * 4 + 1]
712
+ const x = pixels[i * 4 + 2]
713
+ const y = pixels[i * 4 + 3]
714
+
715
+ if (isNotEmpty && index !== undefined && x !== undefined && y !== undefined) {
716
+ indices.push(index)
717
+ positions.push(x, y)
718
+ }
719
+ }
720
+
721
+ return { indices, positions }
722
+ }
723
+
724
+ public getTrackedPositionsArray (): number[] {
725
+ const positions: number[] = []
726
+ if (!this.trackedIndices) return positions
727
+ positions.length = this.trackedIndices.length * 2
728
+ const pixels = readPixels(this.reglInstance, this.trackedPositionsFbo as regl.Framebuffer2D)
729
+ for (let i = 0; i < pixels.length / 4; i += 1) {
730
+ const x = pixels[i * 4]
731
+ const y = pixels[i * 4 + 1]
732
+ const index = this.trackedIndices[i]
733
+ if (x !== undefined && y !== undefined && index !== undefined) {
734
+ positions[i * 2] = x
735
+ positions[i * 2 + 1] = y
736
+ }
737
+ }
738
+ return positions
739
+ }
740
+
698
741
  private swapFbo (): void {
699
742
  const temp = this.previousPositionFbo
700
743
  this.previousPositionFbo = this.currentPositionFbo
@@ -13,6 +13,7 @@ import { Meta } from "@storybook/blocks";
13
13
  | pointGreyoutOpacity | Greyed out point opacity value when the selection is active | `undefined` |
14
14
  | pointGreyoutColor | Greyed out point color value when the selection is active | `undefined` |
15
15
  | pointSize | The default size value to use for points when no point sizes are provided or if the size value in the array is `undefined` or `null` | `4` |
16
+ | pointOpacity | Universal opacity value applied to all points. This value multiplies with individual point alpha values (if set via setPointColors). Useful for dynamically controlling opacity of all points without updating individual RGBA arrays. | `1.0` |
16
17
  | pointSizeScale | Scale factor for the point size | `1` |
17
18
  | hoveredPointCursor | Cursor style to use when hovering over a point | `auto` |
18
19
  | renderHoveredPointRing | Turns ring rendering around a point on hover on / off | `false` |
@@ -21,6 +22,7 @@ import { Meta } from "@storybook/blocks";
21
22
  | focusedPointIndex | Set focus on a point by index. A ring will be highlighted around the focused point. When set to `undefined`, no point is focused. | `undefined` |
22
23
  | renderLinks | Turns link rendering on / off | `true` |
23
24
  | linkColor | The default color to use for links when no link colors are provided, or if the color value in the array is `undefined` or `null`. This can be either a hex color string (e.g., '#666666') or an array of RGBA values in the format `[red, green, blue, alpha]` where each value is a number between 0 and 255 | `#666666` |
25
+ | linkOpacity | Universal opacity value applied to all links. This value multiplies with individual link alpha values (if set via setLinkColors). Useful for dynamically controlling opacity of all links without updating individual RGBA arrays. | `1.0` |
24
26
  | linkGreyoutOpacity | Greyed out link opacity value when the selection is active | `0.1` |
25
27
  | linkWidth | The default width value to use for links when no link widths are provided or if the width value in the array is `undefined` or `null` | `1` |
26
28
  | linkWidthScale | Scale factor for the link width | `1` |