@cosmos.gl/graph 2.3.0 → 2.3.1-beta.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.
@@ -1,8 +1,19 @@
1
1
  import { GraphConfig } from '../../config';
2
+ export declare enum PointShape {
3
+ Circle = 0,
4
+ Square = 1,
5
+ Triangle = 2,
6
+ Diamond = 3,
7
+ Pentagon = 4,
8
+ Hexagon = 5,
9
+ Star = 6,
10
+ Cross = 7
11
+ }
2
12
  export declare class GraphData {
3
13
  inputPointPositions: Float32Array | undefined;
4
14
  inputPointColors: Float32Array | undefined;
5
15
  inputPointSizes: Float32Array | undefined;
16
+ inputPointShapes: Float32Array | undefined;
6
17
  inputLinkColors: Float32Array | undefined;
7
18
  inputLinkWidths: Float32Array | undefined;
8
19
  inputLinkStrength: Float32Array | undefined;
@@ -12,6 +23,7 @@ export declare class GraphData {
12
23
  pointPositions: Float32Array | undefined;
13
24
  pointColors: Float32Array | undefined;
14
25
  pointSizes: Float32Array | undefined;
26
+ pointShapes: Float32Array | undefined;
15
27
  inputLinks: Float32Array | undefined;
16
28
  links: Float32Array | undefined;
17
29
  linkColors: Float32Array | undefined;
@@ -45,6 +57,10 @@ export declare class GraphData {
45
57
  * Updates the point sizes based on the input data or default config value.
46
58
  */
47
59
  updatePointSize(): void;
60
+ /**
61
+ * Updates the point shapes based on the input data or default shape (Circle).
62
+ */
63
+ updatePointShape(): void;
48
64
  updateLinks(): void;
49
65
  /**
50
66
  * Updates the link colors based on the input data or default config value.
@@ -13,6 +13,7 @@ export declare class Points extends CoreModule {
13
13
  private colorBuffer;
14
14
  private sizeFbo;
15
15
  private sizeBuffer;
16
+ private shapeBuffer;
16
17
  private trackedIndicesFbo;
17
18
  private trackedPositionsFbo;
18
19
  private sampledPointsFbo;
@@ -43,6 +44,7 @@ export declare class Points extends CoreModule {
43
44
  updateColor(): void;
44
45
  updateGreyoutStatus(): void;
45
46
  updateSize(): void;
47
+ updateShape(): void;
46
48
  updateSampledPointsGrid(): void;
47
49
  trackPoints(): void;
48
50
  draw(): void;
@@ -0,0 +1,5 @@
1
+ import { Graph } from '../../..';
2
+ export declare const allShapes: () => {
3
+ div: HTMLDivElement;
4
+ graph: Graph;
5
+ };
@@ -0,0 +1,6 @@
1
+ import { Meta } from '@storybook/html';
2
+ import { Story } from './create-story';
3
+ import { CosmosStoryProps } from './create-cosmos';
4
+ declare const meta: Meta<CosmosStoryProps>;
5
+ export declare const AllShapes: Story;
6
+ export default meta;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cosmos.gl/graph",
3
- "version": "2.3.0",
3
+ "version": "2.3.1-beta.1",
4
4
  "description": "GPU-based force graph layout and rendering",
5
5
  "jsdelivr": "dist/index.min.js",
6
6
  "main": "dist/index.js",
package/src/config.ts CHANGED
@@ -492,6 +492,12 @@ export interface GraphConfigInterface {
492
492
  * Default: `undefined`
493
493
  */
494
494
  fitViewByPointsInRect?: [[number, number], [number, number]] | [number, number][];
495
+ /**
496
+ * When `fitViewOnInit` is set to `true`, fits the view to show only the specified points by their indices.
497
+ * Takes precedence over `fitViewByPointsInRect` when both are provided.
498
+ * Default: `undefined`
499
+ */
500
+ fitViewByPointIndices?: number[];
495
501
  /**
496
502
  * Providing a `randomSeed` value allows you to control
497
503
  * the randomness of the layout across different simulation runs.
@@ -605,6 +611,7 @@ export class GraphConfig implements GraphConfigInterface {
605
611
  public fitViewPadding = defaultConfigValues.fitViewPadding
606
612
  public fitViewDuration = defaultConfigValues.fitViewDuration
607
613
  public fitViewByPointsInRect = undefined
614
+ public fitViewByPointIndices = undefined
608
615
 
609
616
  public randomSeed = undefined
610
617
  public pointSamplingDistance = defaultConfigValues.pointSamplingDistance
package/src/index.ts CHANGED
@@ -67,6 +67,7 @@ export class Graph {
67
67
  private _needsPointPositionsUpdate = false
68
68
  private _needsPointColorUpdate = false
69
69
  private _needsPointSizeUpdate = false
70
+ private _needsPointShapeUpdate = false
70
71
  private _needsLinksUpdate = false
71
72
  private _needsLinkColorUpdate = false
72
73
  private _needsLinkWidthUpdate = false
@@ -303,6 +304,7 @@ export class Graph {
303
304
  // Point related textures depend on point positions length, so we need to update them
304
305
  this._needsPointColorUpdate = true
305
306
  this._needsPointSizeUpdate = true
307
+ this._needsPointShapeUpdate = true
306
308
  this._needsPointClusterUpdate = true
307
309
  this._needsForceManyBodyUpdate = true
308
310
  this._needsForceLinkUpdate = true
@@ -346,6 +348,20 @@ export class Graph {
346
348
  this._needsPointSizeUpdate = true
347
349
  }
348
350
 
351
+ /**
352
+ * Sets the shapes for the graph points.
353
+ *
354
+ * @param {Float32Array} pointShapes - A Float32Array representing the shapes of points in the format [shape1, shape2, ..., shapen],
355
+ * 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.
357
+ * Example: `new Float32Array([0, 1, 2])` sets the first point to Circle, the second point to Square, and the third point to Triangle.
358
+ */
359
+ public setPointShapes (pointShapes: Float32Array): void {
360
+ if (this._isDestroyed) return
361
+ this.graph.inputPointShapes = pointShapes
362
+ this._needsPointShapeUpdate = true
363
+ }
364
+
349
365
  /**
350
366
  * Gets the current sizes of the graph points.
351
367
  *
@@ -509,7 +525,7 @@ export class Graph {
509
525
  public render (simulationAlpha?: number): void {
510
526
  if (this._isDestroyed || !this.reglInstance) return
511
527
  this.graph.update()
512
- const { fitViewOnInit, fitViewDelay, fitViewPadding, fitViewDuration, fitViewByPointsInRect, initialZoomLevel } = this.config
528
+ const { fitViewOnInit, fitViewDelay, fitViewPadding, fitViewDuration, fitViewByPointsInRect, fitViewByPointIndices, initialZoomLevel } = this.config
513
529
  if (!this.graph.pointsNumber && !this.graph.linksNumber) {
514
530
  this.stopFrames()
515
531
  select(this.canvas).style('cursor', null)
@@ -524,7 +540,8 @@ export class Graph {
524
540
  // If `initialZoomLevel` is set, we don't need to fit the view
525
541
  if (this._isFirstRenderAfterInit && fitViewOnInit && initialZoomLevel === undefined) {
526
542
  this._fitViewOnInitTimeoutID = window.setTimeout(() => {
527
- if (fitViewByPointsInRect) this.setZoomTransformByPointPositions(fitViewByPointsInRect, fitViewDuration, undefined, fitViewPadding)
543
+ if (fitViewByPointIndices) this.fitViewByPointIndices(fitViewByPointIndices, fitViewDuration, fitViewPadding)
544
+ else if (fitViewByPointsInRect) this.setZoomTransformByPointPositions(fitViewByPointsInRect, fitViewDuration, undefined, fitViewPadding)
528
545
  else this.fitView(fitViewDuration, fitViewPadding)
529
546
  }, fitViewDelay)
530
547
  }
@@ -1100,6 +1117,7 @@ export class Graph {
1100
1117
  if (this._needsPointPositionsUpdate) this.points.updatePositions()
1101
1118
  if (this._needsPointColorUpdate) this.points.updateColor()
1102
1119
  if (this._needsPointSizeUpdate) this.points.updateSize()
1120
+ if (this._needsPointShapeUpdate) this.points.updateShape()
1103
1121
 
1104
1122
  if (this._needsLinksUpdate) this.lines.updatePointsBuffer()
1105
1123
  if (this._needsLinkColorUpdate) this.lines.updateColor()
@@ -1117,6 +1135,7 @@ export class Graph {
1117
1135
  this._needsPointPositionsUpdate = false
1118
1136
  this._needsPointColorUpdate = false
1119
1137
  this._needsPointSizeUpdate = false
1138
+ this._needsPointShapeUpdate = false
1120
1139
  this._needsLinksUpdate = false
1121
1140
  this._needsLinkColorUpdate = false
1122
1141
  this._needsLinkWidthUpdate = false
@@ -1412,5 +1431,6 @@ export class Graph {
1412
1431
  }
1413
1432
 
1414
1433
  export type { GraphConfigInterface } from './config'
1434
+ export { PointShape } from './modules/GraphData'
1415
1435
 
1416
1436
  export * from './helper'
@@ -1,9 +1,22 @@
1
1
  import { getRgbaColor, isNumber } from '@/graph/helper'
2
2
  import { GraphConfig } from '@/graph/config'
3
+
4
+ export enum PointShape {
5
+ Circle = 0,
6
+ Square = 1,
7
+ Triangle = 2,
8
+ Diamond = 3,
9
+ Pentagon = 4,
10
+ Hexagon = 5,
11
+ Star = 6,
12
+ Cross = 7
13
+ }
14
+
3
15
  export class GraphData {
4
16
  public inputPointPositions: Float32Array | undefined
5
17
  public inputPointColors: Float32Array | undefined
6
18
  public inputPointSizes: Float32Array | undefined
19
+ public inputPointShapes: Float32Array | undefined
7
20
  public inputLinkColors: Float32Array | undefined
8
21
  public inputLinkWidths: Float32Array | undefined
9
22
  public inputLinkStrength: Float32Array | undefined
@@ -14,6 +27,7 @@ export class GraphData {
14
27
  public pointPositions: Float32Array | undefined
15
28
  public pointColors: Float32Array | undefined
16
29
  public pointSizes: Float32Array | undefined
30
+ public pointShapes: Float32Array | undefined
17
31
 
18
32
  public inputLinks: Float32Array | undefined
19
33
  public links: Float32Array | undefined
@@ -109,6 +123,30 @@ export class GraphData {
109
123
  }
110
124
  }
111
125
 
126
+ /**
127
+ * Updates the point shapes based on the input data or default shape (Circle).
128
+ */
129
+ public updatePointShape (): void {
130
+ if (this.pointsNumber === undefined) {
131
+ this.pointShapes = undefined
132
+ return
133
+ }
134
+
135
+ // Sets point shapes to default values (Circle) if the input is missing or does not match input points number.
136
+ if (this.inputPointShapes === undefined || this.inputPointShapes.length !== this.pointsNumber) {
137
+ this.pointShapes = new Float32Array(this.pointsNumber).fill(PointShape.Circle)
138
+ } else {
139
+ this.pointShapes = new Float32Array(this.inputPointShapes)
140
+ const pointShapes = this.pointShapes
141
+ for (let i = 0; i < pointShapes.length; i++) {
142
+ const shape = pointShapes[i]
143
+ if (shape == null || !isNumber(shape) || shape < 0 || shape > 7) {
144
+ pointShapes[i] = PointShape.Circle
145
+ }
146
+ }
147
+ }
148
+ }
149
+
112
150
  public updateLinks (): void {
113
151
  this.links = this.inputLinks
114
152
  }
@@ -222,6 +260,7 @@ export class GraphData {
222
260
  this.updatePoints()
223
261
  this.updatePointColor()
224
262
  this.updatePointSize()
263
+ this.updatePointShape()
225
264
 
226
265
  this.updateLinks()
227
266
  this.updateLinkColor()
@@ -63,7 +63,11 @@ float calculateArrowWidth(float arrowWidth) {
63
63
  if (scaleLinksOnZoom) {
64
64
  return arrowWidth;
65
65
  } else {
66
- return arrowWidth / transformationMatrix[0][0];
66
+ // Apply the same scaling logic as calculateLinkWidth to maintain proportionality
67
+ arrowWidth = arrowWidth / transformationMatrix[0][0];
68
+ // Apply the same non-linear scaling to avoid extreme widths
69
+ arrowWidth *= min(5.0, max(1.0, transformationMatrix[0][0] * 0.01));
70
+ return arrowWidth;
67
71
  }
68
72
  }
69
73
 
@@ -100,7 +104,8 @@ void main() {
100
104
  float arrowWidth = linkWidth * k;
101
105
  arrowWidth *= arrowSizeScale;
102
106
 
103
- float arrowWidthDifference = arrowWidth - linkWidth;
107
+ // Ensure arrow width difference is non-negative to prevent unwanted changes to link width
108
+ float arrowWidthDifference = max(0.0, arrowWidth - linkWidth);
104
109
 
105
110
  // Calculate arrow width in pixels
106
111
  float arrowWidthPx = calculateArrowWidth(arrowWidth);
@@ -1,4 +1,4 @@
1
- // Fragment shader for rendering points with a smooth circular edge
1
+ // Fragment shader for rendering points with various shapes and smooth edges
2
2
 
3
3
  #ifdef GL_ES
4
4
  precision highp float;
@@ -7,20 +7,145 @@ precision highp float;
7
7
  // The color and alpha values from the vertex shader
8
8
  varying vec3 rgbColor;
9
9
  varying float alpha;
10
+ varying float pointShape;
10
11
 
11
- // Smoothing control the smoothness of the points edge
12
+ // Smoothing controls the smoothness of the point's edge
12
13
  const float smoothing = 0.9;
13
14
 
15
+ // Shape constants
16
+ const float CIRCLE = 0.0;
17
+ const float SQUARE = 1.0;
18
+ const float TRIANGLE = 2.0;
19
+ const float DIAMOND = 3.0;
20
+ const float PENTAGON = 4.0;
21
+ const float HEXAGON = 5.0;
22
+ const float STAR = 6.0;
23
+ const float CROSS = 7.0;
24
+
25
+ // Distance functions for different shapes
26
+ float circleDistance(vec2 p) {
27
+ return dot(p, p);
28
+ }
29
+
30
+ float squareDistance(vec2 p) {
31
+ vec2 d = abs(p) - vec2(0.8);
32
+ return length(max(d, 0.0)) + min(max(d.x, d.y), 0.0);
33
+ }
34
+
35
+ float triangleDistance(vec2 p) {
36
+ const float k = sqrt(3.0); // ≈1.732; slope of 60° lines for an equilateral triangle
37
+ p.x = abs(p.x) - 0.9; // fold the X axis and shift: brings left and right halves together
38
+ p.y = p.y + 0.55; // move the whole shape up slightly so it is centred vertically
39
+
40
+ // reflect points that fall outside the main triangle back inside, to reuse the same maths
41
+ if (p.x + k * p.y > 0.0)
42
+ p = vec2(p.x - k * p.y, -k * p.x - p.y) / 2.0;
43
+
44
+ p.x -= clamp(p.x, -1.0, 0.0); // clip any remainder on the left side
45
+
46
+ // Return signed distance: negative = inside; positive = outside
47
+ return -length(p) * sign(p.y);
48
+ }
49
+
50
+ float diamondDistance(vec2 p) {
51
+ // aspect > 1 → taller diamond
52
+ const float aspect = 1.2;
53
+ return abs(p.x) + abs(p.y) / aspect - 0.8;
54
+ }
55
+
56
+ float pentagonDistance(vec2 p) {
57
+ // Regular pentagon signed-distance (Inigo Quilez)
58
+ const vec3 k = vec3(0.809016994, 0.587785252, 0.726542528);
59
+ p.x = abs(p.x);
60
+
61
+ // Reflect across the two tilted edges ─ only if point is outside
62
+ p -= 2.0 * min(dot(vec2(-k.x, k.y), p), 0.0) * vec2(-k.x, k.y);
63
+ p -= 2.0 * min(dot(vec2( k.x, k.y), p), 0.0) * vec2( k.x, k.y);
64
+
65
+ // Clip against the top horizontal edge (keeps top point sharp)
66
+ p -= vec2(clamp(p.x, -k.z * k.x, k.z * k.x), k.z);
67
+
68
+ // Return signed distance (negative → inside, positive → outside)
69
+ return length(p) * sign(p.y);
70
+ }
71
+
72
+ float hexagonDistance(vec2 p) {
73
+ const vec3 k = vec3(-0.866025404, 0.5, 0.577350269);
74
+ p = abs(p);
75
+ p -= 2.0 * min(dot(k.xy, p), 0.0) * k.xy;
76
+ p -= vec2(clamp(p.x, -k.z * 0.8, k.z * 0.8), 0.8);
77
+ return length(p) * sign(p.y);
78
+ }
79
+
80
+ float starDistance(vec2 p) {
81
+ // 5-point star signed-distance function (adapted from Inigo Quilez)
82
+ // r – outer radius, rf – inner/outer radius ratio
83
+ const float r = 0.9;
84
+ const float rf = 0.45;
85
+
86
+ // Pre-computed rotation vectors for the star arms (36° increments)
87
+ const vec2 k1 = vec2(0.809016994, -0.587785252);
88
+ const vec2 k2 = vec2(-k1.x, k1.y);
89
+
90
+ // Fold the plane into a single arm sector
91
+ p.x = abs(p.x);
92
+ p -= 2.0 * max(dot(k1, p), 0.0) * k1;
93
+ p -= 2.0 * max(dot(k2, p), 0.0) * k2;
94
+ p.x = abs(p.x);
95
+
96
+ // Translate so the top tip of the star lies on the X-axis
97
+ p.y -= r;
98
+
99
+ // Vector describing the edge between an outer tip and its adjacent inner point
100
+ vec2 ba = rf * vec2(-k1.y, k1.x) - vec2(0.0, 1.0);
101
+ // Project the point onto that edge and clamp the projection to the segment
102
+ float h = clamp(dot(p, ba) / dot(ba, ba), 0.0, r);
103
+
104
+ // Return signed distance (negative => inside, positive => outside)
105
+ return length(p - ba * h) * sign(p.y * ba.x - p.x * ba.y);
106
+ }
107
+
108
+ float crossDistance(vec2 p) {
109
+ // Signed distance function for a cross (union of two rectangles)
110
+ // Adapted from Inigo Quilez (https://iquilezles.org/)
111
+ // Each arm has half-sizes 0.3 (thickness) and 0.8 (length)
112
+ p = abs(p);
113
+ if (p.y > p.x) p = p.yx; // exploit symmetry
114
+
115
+ vec2 q = p - vec2(0.8, 0.3); // subtract half-sizes (length, thickness)
116
+
117
+ // Standard rectangle SDF, then take union of the two arms
118
+ return length(max(q, 0.0)) + min(max(q.x, q.y), 0.0);
119
+ }
120
+
121
+ float getShapeDistance(vec2 p, float shape) {
122
+ if (shape == SQUARE) return squareDistance(p);
123
+ else if (shape == TRIANGLE) return triangleDistance(p);
124
+ else if (shape == DIAMOND) return diamondDistance(p);
125
+ else if (shape == PENTAGON) return pentagonDistance(p);
126
+ else if (shape == HEXAGON) return hexagonDistance(p);
127
+ else if (shape == STAR) return starDistance(p);
128
+ else if (shape == CROSS) return crossDistance(p);
129
+ else return circleDistance(p); // Default to circle
130
+ }
131
+
14
132
  void main() {
15
133
  // Discard the fragment if the point is fully transparent
16
134
  if (alpha == 0.0) { discard; }
17
135
 
18
136
  // Calculate coordinates within the point
19
137
  vec2 pointCoord = 2.0 * gl_PointCoord - 1.0;
20
- // Calculate squared distance from the center
21
- float pointCenterDistance = dot(pointCoord, pointCoord);
22
- // Calculate opacity based on distance and smoothing
23
- float opacity = alpha * (1.0 - smoothstep(smoothing, 1.0, pointCenterDistance));
138
+
139
+ float opacity;
140
+ if (pointShape == CIRCLE) {
141
+ // For circles, use the original distance calculation
142
+ float pointCenterDistance = dot(pointCoord, pointCoord);
143
+ opacity = alpha * (1.0 - smoothstep(smoothing, 1.0, pointCenterDistance));
144
+ } else {
145
+ // For other shapes, use the shape distance function
146
+ float shapeDistance = getShapeDistance(pointCoord, pointShape);
147
+ opacity = alpha * (1.0 - smoothstep(-0.01, 0.01, shapeDistance));
148
+ }
24
149
 
25
150
  gl_FragColor = vec4(rgbColor, opacity);
26
151
  }
@@ -5,6 +5,7 @@ precision highp float;
5
5
  attribute vec2 pointIndices;
6
6
  attribute float size;
7
7
  attribute vec4 color;
8
+ attribute float shape;
8
9
 
9
10
  uniform sampler2D positionsTexture;
10
11
  uniform sampler2D pointGreyoutStatus;
@@ -21,10 +22,13 @@ uniform vec4 backgroundColor;
21
22
  uniform bool scalePointsOnZoom;
22
23
  uniform float maxPointSize;
23
24
  uniform bool darkenGreyout;
25
+ uniform bool skipSelected;
26
+ uniform bool skipUnselected;
24
27
 
25
28
  varying vec2 textureCoords;
26
29
  varying vec3 rgbColor;
27
30
  varying float alpha;
31
+ varying float pointShape;
28
32
 
29
33
  float calculatePointSize(float size) {
30
34
  float pSize;
@@ -39,6 +43,23 @@ float calculatePointSize(float size) {
39
43
 
40
44
  void main() {
41
45
  textureCoords = pointIndices;
46
+
47
+ // Check greyout status for selective rendering
48
+ vec4 greyoutStatus = texture2D(pointGreyoutStatus, (textureCoords + 0.5) / pointsTextureSize);
49
+ bool isSelected = greyoutStatus.r == 0.0;
50
+
51
+ // Discard point based on rendering mode
52
+ if (skipSelected && isSelected) {
53
+ gl_Position = vec4(2.0, 2.0, 2.0, 1.0); // Move off-screen
54
+ gl_PointSize = 0.0;
55
+ return;
56
+ }
57
+ if (skipUnselected && !isSelected) {
58
+ gl_Position = vec4(2.0, 2.0, 2.0, 1.0); // Move off-screen
59
+ gl_PointSize = 0.0;
60
+ return;
61
+ }
62
+
42
63
  // Position
43
64
  vec4 pointPosition = texture2D(positionsTexture, (textureCoords + 0.5) / pointsTextureSize);
44
65
  vec2 point = pointPosition.rg;
@@ -53,9 +74,9 @@ void main() {
53
74
 
54
75
  rgbColor = color.rgb;
55
76
  alpha = color.a * pointOpacity;
77
+ pointShape = shape;
56
78
 
57
79
  // Adjust alpha of selected points
58
- vec4 greyoutStatus = texture2D(pointGreyoutStatus, (textureCoords + 0.5) / pointsTextureSize);
59
80
  if (greyoutStatus.r > 0.0) {
60
81
  if (greyoutColor[0] != -1.0) {
61
82
  rgbColor = greyoutColor.rgb;
@@ -34,6 +34,7 @@ export class Points extends CoreModule {
34
34
  private colorBuffer: regl.Buffer | undefined
35
35
  private sizeFbo: regl.Framebuffer2D | undefined
36
36
  private sizeBuffer: regl.Buffer | undefined
37
+ private shapeBuffer: regl.Buffer | undefined
37
38
  private trackedIndicesFbo: regl.Framebuffer2D | undefined
38
39
  private trackedPositionsFbo: regl.Framebuffer2D | undefined
39
40
  private sampledPointsFbo: regl.Framebuffer2D | undefined
@@ -222,6 +223,10 @@ export class Points extends CoreModule {
222
223
  buffer: () => this.colorBuffer,
223
224
  size: 4,
224
225
  },
226
+ shape: {
227
+ buffer: () => this.shapeBuffer,
228
+ size: 1,
229
+ },
225
230
  },
226
231
  uniforms: {
227
232
  positionsTexture: () => this.currentPositionFbo,
@@ -239,6 +244,8 @@ export class Points extends CoreModule {
239
244
  darkenGreyout: () => store.darkenGreyout,
240
245
  scalePointsOnZoom: () => config.scalePointsOnZoom,
241
246
  maxPointSize: () => store.maxPointSize,
247
+ skipSelected: reglInstance.prop<{ skipSelected: boolean }, 'skipSelected'>('skipSelected'),
248
+ skipUnselected: reglInstance.prop<{ skipUnselected: boolean }, 'skipUnselected'>('skipUnselected'),
242
249
  },
243
250
  blend: {
244
251
  enable: true,
@@ -518,6 +525,13 @@ export class Points extends CoreModule {
518
525
  })
519
526
  }
520
527
 
528
+ public updateShape (): void {
529
+ const { reglInstance, data } = this
530
+ if (data.pointsNumber === undefined || data.pointShapes === undefined) return
531
+ if (!this.shapeBuffer) this.shapeBuffer = reglInstance.buffer(0)
532
+ this.shapeBuffer(data.pointShapes)
533
+ }
534
+
521
535
  public updateSampledPointsGrid (): void {
522
536
  const { store: { screenSize }, config: { pointSamplingDistance }, reglInstance } = this
523
537
  let dist = pointSamplingDistance ?? Math.min(...screenSize) / 2
@@ -542,7 +556,18 @@ export class Points extends CoreModule {
542
556
  const { config: { renderHoveredPointRing, pointSize }, store, data } = this
543
557
  if (!this.colorBuffer) this.updateColor()
544
558
  if (!this.sizeBuffer) this.updateSize()
545
- this.drawCommand?.()
559
+ if (!this.shapeBuffer) this.updateShape()
560
+
561
+ // Render in layers: unselected points first (behind), then selected points (in front)
562
+ if (store.selectedIndices && store.selectedIndices.length > 0) {
563
+ // First draw unselected points (they will appear behind)
564
+ this.drawCommand?.({ skipSelected: true, skipUnselected: false })
565
+ // Then draw selected points (they will appear in front)
566
+ this.drawCommand?.({ skipSelected: false, skipUnselected: true })
567
+ } else {
568
+ // If no selection, draw all points
569
+ this.drawCommand?.({ skipSelected: false, skipUnselected: false })
570
+ }
546
571
  if ((renderHoveredPointRing) && store.hoveredPoint) {
547
572
  this.drawHighlightedCommand?.({
548
573
  width: 0.85,
@@ -48,6 +48,7 @@ import { Meta } from "@storybook/blocks";
48
48
  | fitViewPadding | Extra space around points when fitting view | `0.1` |
49
49
  | fitViewDuration | Animation duration for fit view operation in milliseconds | `250` |
50
50
  | fitViewByPointsInRect | When `fitViewOnInit` is set to `true`, fits the view to show the points within a rectangle defined by its two corner coordinates `[[left, bottom], [right, top]]` in the scene space | `undefined` |
51
+ | fitViewByPointIndices | When `fitViewOnInit` is set to `true`, fits the view to show only the specified points by their indices. Takes precedence over `fitViewByPointsInRect` when both are provided. | `undefined` |
51
52
  | randomSeed | Providing a value allows control over layout randomness for consistency across simulations. Applied only on initialization | `undefined` |
52
53
  | pointSamplingDistance | Sampling density for point position methods (used in `getSampledPointPositionsMap`) in pixels | `150` |
53
54
  | attribution | Controls the text shown in the bottom right corner. Provide HTML content as a string for custom attribution. Empty string hides attribution | `''` |
@@ -75,6 +75,39 @@ This method retrieves the current sizes of the graph points that were previously
75
75
 
76
76
  The returned array contains the processed point sizes used for rendering, including any default values that were applied during processing.
77
77
 
78
+ ### <a name="set_point_shapes" href="#set_point_shapes">#</a> graph.<b>setPointShapes</b>(<i>pointShapes</i>)
79
+
80
+ This method sets the shapes for the graph points.
81
+
82
+ * **`pointShapes`** (Float32Array): A Float32Array representing the shapes of points in the format `[shape1, shape2, ..., shapeN]`, where each `shape` value corresponds to the shape of the point at the same index in the graph's data. Each shape value should be one of the available shape constants:
83
+
84
+ | Shape Value | Shape Name |
85
+ |-------------|------------|
86
+ | 0 | Circle |
87
+ | 1 | Square |
88
+ | 2 | Triangle |
89
+ | 3 | Diamond |
90
+ | 4 | Pentagon |
91
+ | 5 | Hexagon |
92
+ | 6 | Star |
93
+ | 7 | Cross |
94
+
95
+ Each shape value in the array specifies the shape of a point using the same order as the points in the graph data. Invalid shape values (outside the range 0-7) will default to Circle (0).
96
+
97
+ **Example:**
98
+ ```javascript
99
+ import { Graph, PointShape } from '@cosmos.gl/graph'
100
+
101
+ // Create a mixed set of shapes using the PointShape enum
102
+ graph.setPointShapes(new Float32Array([
103
+ PointShape.Circle, // Circle for first point
104
+ PointShape.Star, // Star for second point
105
+ PointShape.Triangle, // Triangle for third point
106
+ PointShape.Hexagon // Hexagon for fourth point
107
+ ]));
108
+ graph.render();
109
+ ```
110
+
78
111
  ### <a name="set_links" href="#set_links">#</a> graph.<b>setLinks</b>(<i>links</i>)
79
112
 
80
113
  This method sets the links (connections) between points in a cosmos.gl graph.
@@ -0,0 +1,69 @@
1
+ import { Graph, PointShape } from '@cosmos.gl/graph'
2
+
3
+ export const allShapes = (): {div: HTMLDivElement; graph: Graph } => {
4
+ // Create container div
5
+ const div = document.createElement('div')
6
+ div.style.height = '100vh'
7
+ div.style.width = '100%'
8
+
9
+ // Create 8 points, one for each shape
10
+ const spaceSize = 4096
11
+ const pointCount = 8
12
+ const spacing = spaceSize / pointCount
13
+
14
+ const pointPositions = new Float32Array(pointCount * 2)
15
+ const pointColors = new Float32Array(pointCount * 4)
16
+ const pointShapes = new Float32Array(pointCount)
17
+
18
+ // Define distinct colors for each shape
19
+ const shapeColors: [number, number, number][] = [
20
+ [1.0, 0.42, 0.38], // Coral for Circle
21
+ [0.13, 0.55, 0.45], // Forest Green for Square
22
+ [0.25, 0.32, 0.71], // Royal Blue for Triangle
23
+ [0.96, 0.76, 0.19], // Amber Gold for Diamond
24
+ [0.74, 0.24, 0.45], // Deep Rose for Pentagon
25
+ [0.18, 0.55, 0.56], // Teal for Hexagon
26
+ [0.85, 0.45, 0.28], // Terracotta for Star
27
+ [0.58, 0.44, 0.86], // Periwinkle for Cross
28
+ ]
29
+
30
+ // Position points in a horizontal line
31
+ const startX = spacing / 2
32
+ const centerY = spaceSize / 2
33
+ for (let i = 0; i < pointCount; i++) {
34
+ // Position: horizontal line, centered
35
+ pointPositions[i * 2] = startX + i * spacing
36
+ pointPositions[i * 2 + 1] = centerY
37
+
38
+ // Color: distinct color for each shape
39
+ const color = shapeColors[i] || [1.0, 1.0, 1.0] // fallback to white if undefined
40
+ pointColors[i * 4] = color[0] // R
41
+ pointColors[i * 4 + 1] = color[1] // G
42
+ pointColors[i * 4 + 2] = color[2] // B
43
+ pointColors[i * 4 + 3] = 1.0 // A
44
+
45
+ // Shape: cycle through all available shapes
46
+ pointShapes[i] = i as PointShape
47
+ }
48
+
49
+ // Create graph with minimal configuration
50
+ const graph = new Graph(div, {
51
+ spaceSize,
52
+ pointSize: spacing / 2,
53
+ enableSimulation: false,
54
+ scalePointsOnZoom: true,
55
+ renderHoveredPointRing: true,
56
+ hoveredPointRingColor: '#ffffff',
57
+ rescalePositions: false,
58
+ enableDrag: true,
59
+ })
60
+
61
+ // Set data
62
+ graph.setPointPositions(pointPositions)
63
+ graph.setPointColors(pointColors)
64
+ graph.setPointShapes(pointShapes)
65
+
66
+ graph.render()
67
+
68
+ return { div, graph }
69
+ }