@cosmos.gl/graph 2.3.1-beta.1 → 2.4.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.
Files changed (36) hide show
  1. package/.eslintrc +61 -0
  2. package/CHARTER.md +69 -0
  3. package/GOVERNANCE.md +21 -0
  4. package/dist/index.d.ts +46 -15
  5. package/dist/index.js +2986 -2701
  6. package/dist/index.js.map +1 -1
  7. package/dist/index.min.js +180 -62
  8. package/dist/index.min.js.map +1 -1
  9. package/dist/modules/GraphData/index.d.ts +18 -2
  10. package/dist/modules/Points/atlas-utils.d.ts +24 -0
  11. package/dist/modules/Points/index.d.ts +21 -2
  12. package/dist/modules/Store/index.d.ts +6 -1
  13. package/dist/stories/create-story.d.ts +5 -1
  14. package/dist/stories/shapes/image-example/index.d.ts +5 -0
  15. package/dist/stories/shapes.stories.d.ts +1 -0
  16. package/package.json +4 -4
  17. package/src/config.ts +1 -0
  18. package/src/declaration.d.ts +5 -0
  19. package/src/index.ts +119 -67
  20. package/src/modules/GraphData/index.ts +68 -6
  21. package/src/modules/Points/atlas-utils.ts +137 -0
  22. package/src/modules/Points/draw-highlighted.vert +3 -3
  23. package/src/modules/Points/draw-points.frag +106 -14
  24. package/src/modules/Points/draw-points.vert +51 -25
  25. package/src/modules/Points/find-points-on-area-selection.frag +6 -5
  26. package/src/modules/Points/index.ts +121 -13
  27. package/src/modules/Store/index.ts +11 -3
  28. package/src/stories/3. api-reference.mdx +48 -1
  29. package/src/stories/create-story.ts +32 -5
  30. package/src/stories/shapes/image-example/icons/box.png +0 -0
  31. package/src/stories/shapes/image-example/icons/lego.png +0 -0
  32. package/src/stories/shapes/image-example/icons/s.png +0 -0
  33. package/src/stories/shapes/image-example/icons/swift.png +0 -0
  34. package/src/stories/shapes/image-example/icons/toolbox.png +0 -0
  35. package/src/stories/shapes/image-example/index.ts +239 -0
  36. package/src/stories/shapes.stories.ts +12 -0
@@ -7,13 +7,17 @@ export declare enum PointShape {
7
7
  Pentagon = 4,
8
8
  Hexagon = 5,
9
9
  Star = 6,
10
- Cross = 7
10
+ Cross = 7,
11
+ None = 8
11
12
  }
12
13
  export declare class GraphData {
13
14
  inputPointPositions: Float32Array | undefined;
14
15
  inputPointColors: Float32Array | undefined;
15
16
  inputPointSizes: Float32Array | undefined;
16
17
  inputPointShapes: Float32Array | undefined;
18
+ inputImageData: ImageData[] | undefined;
19
+ inputPointImageIndices: Float32Array | undefined;
20
+ inputPointImageSizes: Float32Array | undefined;
17
21
  inputLinkColors: Float32Array | undefined;
18
22
  inputLinkWidths: Float32Array | undefined;
19
23
  inputLinkStrength: Float32Array | undefined;
@@ -24,6 +28,8 @@ export declare class GraphData {
24
28
  pointColors: Float32Array | undefined;
25
29
  pointSizes: Float32Array | undefined;
26
30
  pointShapes: Float32Array | undefined;
31
+ pointImageIndices: Float32Array | undefined;
32
+ pointImageSizes: Float32Array | undefined;
27
33
  inputLinks: Float32Array | undefined;
28
34
  links: Float32Array | undefined;
29
35
  linkColors: Float32Array | undefined;
@@ -58,9 +64,19 @@ export declare class GraphData {
58
64
  */
59
65
  updatePointSize(): void;
60
66
  /**
61
- * Updates the point shapes based on the input data or default shape (Circle).
67
+ * Updates the point shapes based on the input data or default shape.
68
+ * Default behavior: Circle (0).
69
+ * Images are rendered above shapes.
62
70
  */
63
71
  updatePointShape(): void;
72
+ /**
73
+ * Updates the point image indices based on the input data or default value (-1 for no image).
74
+ */
75
+ updatePointImageIndices(): void;
76
+ /**
77
+ * Updates the point image sizes based on the input data or default to point sizes.
78
+ */
79
+ updatePointImageSizes(): void;
64
80
  updateLinks(): void;
65
81
  /**
66
82
  * Updates the link colors based on the input data or default config value.
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Creates a texture atlas from an array of ImageData objects.
3
+ *
4
+ * A texture atlas is a single large texture that contains multiple smaller images.
5
+ * This allows efficient rendering by reducing the number of texture bindings needed.
6
+ *
7
+ * The atlas uses a grid layout where each image gets a square region sized to
8
+ * accommodate the largest image dimension. Images are placed left-to-right, top-to-bottom.
9
+ *
10
+ * @param imageDataArray - Array of ImageData objects to pack into the atlas
11
+ * @param webglMaxTextureSize - WebGL maximum texture size limit (default: 16384)
12
+ * @returns Atlas data object containing:
13
+ * - atlasData: RGBA pixel data as Uint8Array
14
+ * - atlasSize: Total atlas texture size in pixels
15
+ * - atlasCoords: UV coordinates for each image as Float32Array
16
+ * - atlasCoordsSize: Grid size (number of rows/columns)
17
+ * Returns null if creation fails or no valid images provided
18
+ */
19
+ export declare function createAtlasDataFromImageData(imageDataArray: ImageData[], webglMaxTextureSize?: number): {
20
+ atlasData: Uint8Array;
21
+ atlasSize: number;
22
+ atlasCoords: Float32Array;
23
+ atlasCoordsSize: number;
24
+ } | null;
@@ -9,14 +9,22 @@ export declare class Points extends CoreModule {
9
9
  greyoutStatusFbo: regl.Framebuffer2D | undefined;
10
10
  scaleX: ((x: number) => number) | undefined;
11
11
  scaleY: ((y: number) => number) | undefined;
12
- dontRescale: boolean | undefined;
12
+ shouldSkipRescale: boolean | undefined;
13
+ imageAtlasTexture: regl.Texture2D | undefined;
14
+ imageCount: number;
13
15
  private colorBuffer;
14
16
  private sizeFbo;
15
17
  private sizeBuffer;
16
18
  private shapeBuffer;
19
+ private imageIndicesBuffer;
20
+ private imageSizesBuffer;
21
+ private imageAtlasCoordsTexture;
22
+ private imageAtlasCoordsTextureSize;
17
23
  private trackedIndicesFbo;
18
24
  private trackedPositionsFbo;
19
25
  private sampledPointsFbo;
26
+ private trackedPositions;
27
+ private isPositionsUpToDate;
20
28
  private drawCommand;
21
29
  private drawHighlightedCommand;
22
30
  private updatePositionCommand;
@@ -45,6 +53,9 @@ export declare class Points extends CoreModule {
45
53
  updateGreyoutStatus(): void;
46
54
  updateSize(): void;
47
55
  updateShape(): void;
56
+ updateImageIndices(): void;
57
+ updateImageSizes(): void;
58
+ createAtlas(): void;
48
59
  updateSampledPointsGrid(): void;
49
60
  trackPoints(): void;
50
61
  draw(): void;
@@ -55,7 +66,15 @@ export declare class Points extends CoreModule {
55
66
  updatePolygonPath(polygonPath: [number, number][]): void;
56
67
  findHoveredPoint(): void;
57
68
  trackPointsByIndices(indices?: number[] | undefined): void;
58
- getTrackedPositionsMap(): Map<number, [number, number]>;
69
+ /**
70
+ * Get current X and Y coordinates of the tracked points.
71
+ *
72
+ * When the simulation is disabled or stopped, this method returns a cached
73
+ * result to avoid expensive GPU-to-CPU memory transfers (`readPixels`).
74
+ *
75
+ * @returns A ReadonlyMap where keys are point indices and values are [x, y] coordinates.
76
+ */
77
+ getTrackedPositionsMap(): ReadonlyMap<number, [number, number]>;
59
78
  getSampledPointPositionsMap(): Map<number, [number, number]>;
60
79
  getSampledPoints(): {
61
80
  indices: number[];
@@ -27,10 +27,11 @@ export declare class Store {
27
27
  adjustedSpaceSize: number;
28
28
  isSpaceKeyPressed: boolean;
29
29
  div: HTMLDivElement | undefined;
30
+ webglMaxTextureSize: number;
30
31
  hoveredPointRingColor: number[];
31
32
  focusedPointRingColor: number[];
32
33
  greyoutPointColor: number[];
33
- darkenGreyout: boolean;
34
+ isDarkenGreyout: boolean;
34
35
  private alphaTarget;
35
36
  private scalePointX;
36
37
  private scalePointY;
@@ -45,6 +46,10 @@ export declare class Store {
45
46
  * it reduces the space size without changing the config parameter.
46
47
  */
47
48
  adjustSpaceSize(configSpaceSize: number, webglMaxTextureSize: number): void;
49
+ /**
50
+ * Sets the WebGL texture size limit for use in atlas creation and other texture operations.
51
+ */
52
+ setWebGLMaxTextureSize(webglMaxTextureSize: number): void;
48
53
  updateScreenSize(width: number, height: number): void;
49
54
  scaleX(x: number): number;
50
55
  scaleY(y: number): number;
@@ -9,4 +9,8 @@ export declare const createStory: (storyFunction: () => {
9
9
  graph: Graph;
10
10
  div: HTMLDivElement;
11
11
  destroy?: () => void;
12
- }) => Story;
12
+ } | Promise<{
13
+ graph: Graph;
14
+ div: HTMLDivElement;
15
+ destroy?: () => void;
16
+ }>) => Story;
@@ -0,0 +1,5 @@
1
+ import { Graph } from '../../..';
2
+ export declare const imageExample: () => Promise<{
3
+ div: HTMLDivElement;
4
+ graph: Graph;
5
+ }>;
@@ -3,4 +3,5 @@ import { Story } from './create-story';
3
3
  import { CosmosStoryProps } from './create-cosmos';
4
4
  declare const meta: Meta<CosmosStoryProps>;
5
5
  export declare const AllShapes: Story;
6
+ export declare const ImageExample: Story;
6
7
  export default meta;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cosmos.gl/graph",
3
- "version": "2.3.1-beta.1",
3
+ "version": "2.4.0",
4
4
  "description": "GPU-based force graph layout and rendering",
5
5
  "jsdelivr": "dist/index.min.js",
6
6
  "main": "dist/index.js",
@@ -54,11 +54,11 @@
54
54
  "@types/d3-transition": "^3.0.1",
55
55
  "@types/d3-zoom": "^3.0.1",
56
56
  "@types/dompurify": "^3.0.5",
57
- "@typescript-eslint/eslint-plugin": "^5.30.5",
58
- "@typescript-eslint/parser": "^5.30.5",
57
+ "@typescript-eslint/eslint-plugin": "^6.21.0",
58
+ "@typescript-eslint/parser": "^6.21.0",
59
59
  "@zerollup/ts-transform-paths": "^1.7.18",
60
60
  "d3-scale-chromatic": "^3.1.0",
61
- "eslint": "^8.19.0",
61
+ "eslint": "^8.57.1",
62
62
  "eslint-config-standard": "^17.0.0",
63
63
  "eslint-plugin-import": "^2.26.0",
64
64
  "eslint-plugin-node": "^11.1.0",
package/src/config.ts CHANGED
@@ -1,3 +1,4 @@
1
+ /* eslint-disable @typescript-eslint/naming-convention */
1
2
  import { D3ZoomEvent } from 'd3-zoom'
2
3
  import { D3DragEvent } from 'd3-drag'
3
4
  import {
@@ -1,5 +1,10 @@
1
1
  declare module '*.frag';
2
2
  declare module '*.vert';
3
+ declare module '*.png' {
4
+ const content: string
5
+ // eslint-disable-next-line import/no-default-export
6
+ export default content
7
+ }
3
8
  declare module '*?raw' {
4
9
  const content: string
5
10
  // eslint-disable-next-line import/no-default-export
package/src/index.ts CHANGED
@@ -64,18 +64,20 @@ export class Graph {
64
64
  private _isFirstRenderAfterInit = true
65
65
  private _fitViewOnInitTimeoutID: number | undefined
66
66
 
67
- private _needsPointPositionsUpdate = false
68
- private _needsPointColorUpdate = false
69
- private _needsPointSizeUpdate = false
70
- private _needsPointShapeUpdate = false
71
- private _needsLinksUpdate = false
72
- private _needsLinkColorUpdate = false
73
- private _needsLinkWidthUpdate = false
74
- private _needsLinkArrowUpdate = false
75
- private _needsPointClusterUpdate = false
76
- private _needsForceManyBodyUpdate = false
77
- private _needsForceLinkUpdate = false
78
- private _needsForceCenterUpdate = false
67
+ private isPointPositionsUpdateNeeded = false
68
+ private isPointColorUpdateNeeded = false
69
+ private isPointSizeUpdateNeeded = false
70
+ private isPointShapeUpdateNeeded = false
71
+ private isPointImageIndicesUpdateNeeded = false
72
+ private isLinksUpdateNeeded = false
73
+ private isLinkColorUpdateNeeded = false
74
+ private isLinkWidthUpdateNeeded = false
75
+ private isLinkArrowUpdateNeeded = false
76
+ private isPointClusterUpdateNeeded = false
77
+ private isForceManyBodyUpdateNeeded = false
78
+ private isForceLinkUpdateNeeded = false
79
+ private isForceCenterUpdateNeeded = false
80
+ private isPointImageSizesUpdateNeeded = false
79
81
 
80
82
  private _isDestroyed = false
81
83
 
@@ -114,6 +116,7 @@ export class Graph {
114
116
  this.reglInstance = reglInstance
115
117
 
116
118
  this.store.adjustSpaceSize(this.config.spaceSize, this.reglInstance.limits.maxTextureSize)
119
+ this.store.setWebGLMaxTextureSize(this.reglInstance.limits.maxTextureSize)
117
120
  this.store.updateScreenSize(w, h)
118
121
 
119
122
  this.canvasD3Selection = select<HTMLCanvasElement, undefined>(this.canvas)
@@ -297,18 +300,19 @@ export class Graph {
297
300
  public setPointPositions (pointPositions: Float32Array, dontRescale?: boolean | undefined): void {
298
301
  if (this._isDestroyed || !this.points) return
299
302
  this.graph.inputPointPositions = pointPositions
300
- this.points.dontRescale = dontRescale
301
- this._needsPointPositionsUpdate = true
303
+ this.points.shouldSkipRescale = dontRescale
304
+ this.isPointPositionsUpdateNeeded = true
302
305
  // Links related texture depends on point positions, so we need to update it
303
- this._needsLinksUpdate = true
306
+ this.isLinksUpdateNeeded = true
304
307
  // Point related textures depend on point positions length, so we need to update them
305
- this._needsPointColorUpdate = true
306
- this._needsPointSizeUpdate = true
307
- this._needsPointShapeUpdate = true
308
- this._needsPointClusterUpdate = true
309
- this._needsForceManyBodyUpdate = true
310
- this._needsForceLinkUpdate = true
311
- this._needsForceCenterUpdate = true
308
+ this.isPointColorUpdateNeeded = true
309
+ this.isPointSizeUpdateNeeded = true
310
+ this.isPointShapeUpdateNeeded = true
311
+ this.isPointImageIndicesUpdateNeeded = true
312
+ this.isPointClusterUpdateNeeded = true
313
+ this.isForceManyBodyUpdateNeeded = true
314
+ this.isForceLinkUpdateNeeded = true
315
+ this.isForceCenterUpdateNeeded = true
312
316
  }
313
317
 
314
318
  /**
@@ -321,7 +325,7 @@ export class Graph {
321
325
  public setPointColors (pointColors: Float32Array): void {
322
326
  if (this._isDestroyed) return
323
327
  this.graph.inputPointColors = pointColors
324
- this._needsPointColorUpdate = true
328
+ this.isPointColorUpdateNeeded = true
325
329
  }
326
330
 
327
331
  /**
@@ -345,7 +349,7 @@ export class Graph {
345
349
  public setPointSizes (pointSizes: Float32Array): void {
346
350
  if (this._isDestroyed) return
347
351
  this.graph.inputPointSizes = pointSizes
348
- this._needsPointSizeUpdate = true
352
+ this.isPointSizeUpdateNeeded = true
349
353
  }
350
354
 
351
355
  /**
@@ -353,13 +357,55 @@ export class Graph {
353
357
  *
354
358
  * @param {Float32Array} pointShapes - A Float32Array representing the shapes of points in the format [shape1, shape2, ..., shapen],
355
359
  * where `n` is the index of the point and each shape value corresponds to a PointShape enum:
356
- * 0 = Circle, 1 = Square, 2 = Triangle, 3 = Diamond, 4 = Pentagon, 5 = Hexagon, 6 = Star, 7 = Cross.
360
+ * 0 = Circle, 1 = Square, 2 = Triangle, 3 = Diamond, 4 = Pentagon, 5 = Hexagon, 6 = Star, 7 = Cross, 8 = None.
357
361
  * Example: `new Float32Array([0, 1, 2])` sets the first point to Circle, the second point to Square, and the third point to Triangle.
362
+ * Images are rendered above shapes.
358
363
  */
359
364
  public setPointShapes (pointShapes: Float32Array): void {
360
365
  if (this._isDestroyed) return
361
366
  this.graph.inputPointShapes = pointShapes
362
- this._needsPointShapeUpdate = true
367
+ this.isPointShapeUpdateNeeded = true
368
+ }
369
+
370
+ /**
371
+ * Sets the images for the graph points using ImageData objects.
372
+ * Images are rendered above shapes.
373
+ * To use images, provide image indices via setPointImageIndices().
374
+ *
375
+ * @param {ImageData[]} imageDataArray - Array of ImageData objects to use as point images.
376
+ * Example: `setImageData([imageData1, imageData2, imageData3])`
377
+ */
378
+ public setImageData (imageDataArray: ImageData[]): void {
379
+ if (this._isDestroyed || !this.points) return
380
+ this.graph.inputImageData = imageDataArray
381
+ this.points.createAtlas()
382
+ }
383
+
384
+ /**
385
+ * Sets which image each point should use from the images array.
386
+ * Images are rendered above shapes.
387
+ *
388
+ * @param {Float32Array} imageIndices - A Float32Array representing which image each point uses in the format [index1, index2, ..., indexn],
389
+ * where `n` is the index of the point and each value is an index into the images array provided to `setImageData`.
390
+ * Example: `new Float32Array([0, 1, 0])` sets the first point to use image 0, second point to use image 1, third point to use image 0.
391
+ */
392
+ public setPointImageIndices (imageIndices: Float32Array): void {
393
+ if (this._isDestroyed) return
394
+ this.graph.inputPointImageIndices = imageIndices
395
+ this.isPointImageIndicesUpdateNeeded = true
396
+ }
397
+
398
+ /**
399
+ * Sets the sizes for the point images.
400
+ *
401
+ * @param {Float32Array} imageSizes - A Float32Array representing the sizes of point images in the format [size1, size2, ..., sizen],
402
+ * where `n` is the index of the point.
403
+ * Example: `new Float32Array([10, 20, 30])` sets the first image to size 10, the second image to size 20, and the third image to size 30.
404
+ */
405
+ public setPointImageSizes (imageSizes: Float32Array): void {
406
+ if (this._isDestroyed) return
407
+ this.graph.inputPointImageSizes = imageSizes
408
+ this.isPointImageSizesUpdateNeeded = true
363
409
  }
364
410
 
365
411
  /**
@@ -384,12 +430,12 @@ export class Graph {
384
430
  public setLinks (links: Float32Array): void {
385
431
  if (this._isDestroyed) return
386
432
  this.graph.inputLinks = links
387
- this._needsLinksUpdate = true
433
+ this.isLinksUpdateNeeded = true
388
434
  // Links related texture depends on links length, so we need to update it
389
- this._needsLinkColorUpdate = true
390
- this._needsLinkWidthUpdate = true
391
- this._needsLinkArrowUpdate = true
392
- this._needsForceLinkUpdate = true
435
+ this.isLinkColorUpdateNeeded = true
436
+ this.isLinkWidthUpdateNeeded = true
437
+ this.isLinkArrowUpdateNeeded = true
438
+ this.isForceLinkUpdateNeeded = true
393
439
  }
394
440
 
395
441
  /**
@@ -402,7 +448,7 @@ export class Graph {
402
448
  public setLinkColors (linkColors: Float32Array): void {
403
449
  if (this._isDestroyed) return
404
450
  this.graph.inputLinkColors = linkColors
405
- this._needsLinkColorUpdate = true
451
+ this.isLinkColorUpdateNeeded = true
406
452
  }
407
453
 
408
454
  /**
@@ -426,7 +472,7 @@ export class Graph {
426
472
  public setLinkWidths (linkWidths: Float32Array): void {
427
473
  if (this._isDestroyed) return
428
474
  this.graph.inputLinkWidths = linkWidths
429
- this._needsLinkWidthUpdate = true
475
+ this.isLinkWidthUpdateNeeded = true
430
476
  }
431
477
 
432
478
  /**
@@ -450,7 +496,7 @@ export class Graph {
450
496
  public setLinkArrows (linkArrows: boolean[]): void {
451
497
  if (this._isDestroyed) return
452
498
  this.graph.linkArrowsBoolean = linkArrows
453
- this._needsLinkArrowUpdate = true
499
+ this.isLinkArrowUpdateNeeded = true
454
500
  }
455
501
 
456
502
  /**
@@ -463,7 +509,7 @@ export class Graph {
463
509
  public setLinkStrength (linkStrength: Float32Array): void {
464
510
  if (this._isDestroyed) return
465
511
  this.graph.inputLinkStrength = linkStrength
466
- this._needsForceLinkUpdate = true
512
+ this.isForceLinkUpdateNeeded = true
467
513
  }
468
514
 
469
515
  /**
@@ -480,7 +526,7 @@ export class Graph {
480
526
  public setPointClusters (pointClusters: (number | undefined)[]): void {
481
527
  if (this._isDestroyed) return
482
528
  this.graph.inputPointClusters = pointClusters
483
- this._needsPointClusterUpdate = true
529
+ this.isPointClusterUpdateNeeded = true
484
530
  }
485
531
 
486
532
  /**
@@ -496,7 +542,7 @@ export class Graph {
496
542
  public setClusterPositions (clusterPositions: (number | undefined)[]): void {
497
543
  if (this._isDestroyed) return
498
544
  this.graph.inputClusterPositions = clusterPositions
499
- this._needsPointClusterUpdate = true
545
+ this.isPointClusterUpdateNeeded = true
500
546
  }
501
547
 
502
548
  /**
@@ -512,7 +558,7 @@ export class Graph {
512
558
  public setPointClusterStrength (clusterStrength: Float32Array): void {
513
559
  if (this._isDestroyed) return
514
560
  this.graph.inputClusterStrength = clusterStrength
515
- this._needsPointClusterUpdate = true
561
+ this.isPointClusterUpdateNeeded = true
516
562
  }
517
563
 
518
564
  /**
@@ -936,9 +982,11 @@ export class Graph {
936
982
 
937
983
  /**
938
984
  * Get current X and Y coordinates of the tracked points.
939
- * @returns A Map object where keys are the indices of the points and values are their corresponding X and Y coordinates in the [number, number] format.
985
+ * Do not mutate the returned map - it may affect future calls.
986
+ * @returns A ReadonlyMap where keys are point indices and values are their corresponding X and Y coordinates in the [number, number] format.
987
+ * @see trackPointPositionsByIndices To set which points should be tracked
940
988
  */
941
- public getTrackedPointPositionsMap (): Map<number, [number, number]> {
989
+ public getTrackedPointPositionsMap (): ReadonlyMap<number, [number, number]> {
942
990
  if (this._isDestroyed || !this.points) return new Map()
943
991
  return this.points.getTrackedPositionsMap()
944
992
  }
@@ -1114,36 +1162,40 @@ export class Graph {
1114
1162
  */
1115
1163
  public create (): void {
1116
1164
  if (this._isDestroyed || !this.points || !this.lines) return
1117
- if (this._needsPointPositionsUpdate) this.points.updatePositions()
1118
- if (this._needsPointColorUpdate) this.points.updateColor()
1119
- if (this._needsPointSizeUpdate) this.points.updateSize()
1120
- if (this._needsPointShapeUpdate) this.points.updateShape()
1121
-
1122
- if (this._needsLinksUpdate) this.lines.updatePointsBuffer()
1123
- if (this._needsLinkColorUpdate) this.lines.updateColor()
1124
- if (this._needsLinkWidthUpdate) this.lines.updateWidth()
1125
- if (this._needsLinkArrowUpdate) this.lines.updateArrow()
1126
-
1127
- if (this._needsForceManyBodyUpdate) this.forceManyBody?.create()
1128
- if (this._needsForceLinkUpdate) {
1165
+ if (this.isPointPositionsUpdateNeeded) this.points.updatePositions()
1166
+ if (this.isPointColorUpdateNeeded) this.points.updateColor()
1167
+ if (this.isPointSizeUpdateNeeded) this.points.updateSize()
1168
+ if (this.isPointShapeUpdateNeeded) this.points.updateShape()
1169
+ if (this.isPointImageIndicesUpdateNeeded) this.points.updateImageIndices()
1170
+ if (this.isPointImageSizesUpdateNeeded) this.points.updateImageSizes()
1171
+
1172
+ if (this.isLinksUpdateNeeded) this.lines.updatePointsBuffer()
1173
+ if (this.isLinkColorUpdateNeeded) this.lines.updateColor()
1174
+ if (this.isLinkWidthUpdateNeeded) this.lines.updateWidth()
1175
+ if (this.isLinkArrowUpdateNeeded) this.lines.updateArrow()
1176
+
1177
+ if (this.isForceManyBodyUpdateNeeded) this.forceManyBody?.create()
1178
+ if (this.isForceLinkUpdateNeeded) {
1129
1179
  this.forceLinkIncoming?.create(LinkDirection.INCOMING)
1130
1180
  this.forceLinkOutgoing?.create(LinkDirection.OUTGOING)
1131
1181
  }
1132
- if (this._needsForceCenterUpdate) this.forceCenter?.create()
1133
- if (this._needsPointClusterUpdate) this.clusters?.create()
1134
-
1135
- this._needsPointPositionsUpdate = false
1136
- this._needsPointColorUpdate = false
1137
- this._needsPointSizeUpdate = false
1138
- this._needsPointShapeUpdate = false
1139
- this._needsLinksUpdate = false
1140
- this._needsLinkColorUpdate = false
1141
- this._needsLinkWidthUpdate = false
1142
- this._needsLinkArrowUpdate = false
1143
- this._needsPointClusterUpdate = false
1144
- this._needsForceManyBodyUpdate = false
1145
- this._needsForceLinkUpdate = false
1146
- this._needsForceCenterUpdate = false
1182
+ if (this.isForceCenterUpdateNeeded) this.forceCenter?.create()
1183
+ if (this.isPointClusterUpdateNeeded) this.clusters?.create()
1184
+
1185
+ this.isPointPositionsUpdateNeeded = false
1186
+ this.isPointColorUpdateNeeded = false
1187
+ this.isPointSizeUpdateNeeded = false
1188
+ this.isPointShapeUpdateNeeded = false
1189
+ this.isPointImageIndicesUpdateNeeded = false
1190
+ this.isPointImageSizesUpdateNeeded = false
1191
+ this.isLinksUpdateNeeded = false
1192
+ this.isLinkColorUpdateNeeded = false
1193
+ this.isLinkWidthUpdateNeeded = false
1194
+ this.isLinkArrowUpdateNeeded = false
1195
+ this.isPointClusterUpdateNeeded = false
1196
+ this.isForceManyBodyUpdateNeeded = false
1197
+ this.isForceLinkUpdateNeeded = false
1198
+ this.isForceCenterUpdateNeeded = false
1147
1199
  }
1148
1200
 
1149
1201
  /**
@@ -9,7 +9,8 @@ export enum PointShape {
9
9
  Pentagon = 4,
10
10
  Hexagon = 5,
11
11
  Star = 6,
12
- Cross = 7
12
+ Cross = 7,
13
+ None = 8
13
14
  }
14
15
 
15
16
  export class GraphData {
@@ -17,6 +18,9 @@ export class GraphData {
17
18
  public inputPointColors: Float32Array | undefined
18
19
  public inputPointSizes: Float32Array | undefined
19
20
  public inputPointShapes: Float32Array | undefined
21
+ public inputImageData: ImageData[] | undefined
22
+ public inputPointImageIndices: Float32Array | undefined
23
+ public inputPointImageSizes: Float32Array | undefined
20
24
  public inputLinkColors: Float32Array | undefined
21
25
  public inputLinkWidths: Float32Array | undefined
22
26
  public inputLinkStrength: Float32Array | undefined
@@ -28,6 +32,8 @@ export class GraphData {
28
32
  public pointColors: Float32Array | undefined
29
33
  public pointSizes: Float32Array | undefined
30
34
  public pointShapes: Float32Array | undefined
35
+ public pointImageIndices: Float32Array | undefined
36
+ public pointImageSizes: Float32Array | undefined
31
37
 
32
38
  public inputLinks: Float32Array | undefined
33
39
  public links: Float32Array | undefined
@@ -124,7 +130,9 @@ export class GraphData {
124
130
  }
125
131
 
126
132
  /**
127
- * Updates the point shapes based on the input data or default shape (Circle).
133
+ * Updates the point shapes based on the input data or default shape.
134
+ * Default behavior: Circle (0).
135
+ * Images are rendered above shapes.
128
136
  */
129
137
  public updatePointShape (): void {
130
138
  if (this.pointsNumber === undefined) {
@@ -132,16 +140,68 @@ export class GraphData {
132
140
  return
133
141
  }
134
142
 
135
- // Sets point shapes to default values (Circle) if the input is missing or does not match input points number.
143
+ // Determine default shape: Circle
144
+ const defaultShape = PointShape.Circle
145
+
146
+ // Sets point shapes to default values if the input is missing or does not match input points number.
136
147
  if (this.inputPointShapes === undefined || this.inputPointShapes.length !== this.pointsNumber) {
137
- this.pointShapes = new Float32Array(this.pointsNumber).fill(PointShape.Circle)
148
+ this.pointShapes = new Float32Array(this.pointsNumber).fill(defaultShape)
138
149
  } else {
139
150
  this.pointShapes = new Float32Array(this.inputPointShapes)
140
151
  const pointShapes = this.pointShapes
141
152
  for (let i = 0; i < pointShapes.length; i++) {
142
153
  const shape = pointShapes[i]
143
- if (shape == null || !isNumber(shape) || shape < 0 || shape > 7) {
144
- pointShapes[i] = PointShape.Circle
154
+ if (shape == null || !isNumber(shape) || shape < 0 || shape > 8) {
155
+ pointShapes[i] = defaultShape
156
+ }
157
+ }
158
+ }
159
+ }
160
+
161
+ /**
162
+ * Updates the point image indices based on the input data or default value (-1 for no image).
163
+ */
164
+ public updatePointImageIndices (): void {
165
+ if (this.pointsNumber === undefined) {
166
+ this.pointImageIndices = undefined
167
+ return
168
+ }
169
+
170
+ // Sets point image indices to -1 if input is missing or doesn't match points count
171
+ if (this.inputPointImageIndices === undefined || this.inputPointImageIndices.length !== this.pointsNumber) {
172
+ this.pointImageIndices = new Float32Array(this.pointsNumber).fill(-1)
173
+ } else {
174
+ const pointImageIndices = new Float32Array(this.inputPointImageIndices)
175
+ for (let i = 0; i < pointImageIndices.length; i++) {
176
+ const rawIndex = pointImageIndices[i]
177
+ const imageIndex = (rawIndex === undefined) ? NaN : rawIndex
178
+ if (!Number.isFinite(imageIndex) || imageIndex < 0) {
179
+ pointImageIndices[i] = -1
180
+ } else {
181
+ pointImageIndices[i] = Math.trunc(imageIndex)
182
+ }
183
+ }
184
+ this.pointImageIndices = pointImageIndices
185
+ }
186
+ }
187
+
188
+ /**
189
+ * Updates the point image sizes based on the input data or default to point sizes.
190
+ */
191
+ public updatePointImageSizes (): void {
192
+ if (this.pointsNumber === undefined) {
193
+ this.pointImageSizes = undefined
194
+ return
195
+ }
196
+
197
+ // Sets point image sizes to point sizes if the input is missing or does not match input points number.
198
+ if (this.inputPointImageSizes === undefined || this.inputPointImageSizes.length !== this.pointsNumber) {
199
+ this.pointImageSizes = this.pointSizes ? new Float32Array(this.pointSizes) : new Float32Array(this.pointsNumber).fill(this._config.pointSize)
200
+ } else {
201
+ this.pointImageSizes = new Float32Array(this.inputPointImageSizes)
202
+ for (let i = 0; i < this.pointImageSizes.length; i++) {
203
+ if (!isNumber(this.pointImageSizes[i])) {
204
+ this.pointImageSizes[i] = this.pointSizes?.[i] ?? this._config.pointSize
145
205
  }
146
206
  }
147
207
  }
@@ -261,6 +321,8 @@ export class GraphData {
261
321
  this.updatePointColor()
262
322
  this.updatePointSize()
263
323
  this.updatePointShape()
324
+ this.updatePointImageIndices()
325
+ this.updatePointImageSizes()
264
326
 
265
327
  this.updateLinks()
266
328
  this.updateLinkColor()