@cosmos.gl/graph 2.5.0 → 2.6.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.
@@ -24,6 +24,7 @@ export declare class GraphData {
24
24
  inputPointClusters: (number | undefined)[] | undefined;
25
25
  inputClusterPositions: (number | undefined)[] | undefined;
26
26
  inputClusterStrength: Float32Array | undefined;
27
+ inputPinnedPoints: number[] | undefined;
27
28
  pointPositions: Float32Array | undefined;
28
29
  pointColors: Float32Array | undefined;
29
30
  pointSizes: Float32Array | undefined;
@@ -39,6 +39,8 @@ export declare class Points extends CoreModule {
39
39
  private trackedIndices;
40
40
  private selectedTexture;
41
41
  private greyoutStatusTexture;
42
+ private pinnedStatusTexture;
43
+ private pinnedStatusFbo;
42
44
  private sizeTexture;
43
45
  private trackedIndicesTexture;
44
46
  private polygonPathTexture;
@@ -51,6 +53,7 @@ export declare class Points extends CoreModule {
51
53
  initPrograms(): void;
52
54
  updateColor(): void;
53
55
  updateGreyoutStatus(): void;
56
+ updatePinnedStatus(): void;
54
57
  updateSize(): void;
55
58
  updateShape(): void;
56
59
  updateImageIndices(): void;
@@ -0,0 +1,5 @@
1
+ export declare function generateData(numNodes?: number): {
2
+ pointPositions: Float32Array;
3
+ links: Float32Array;
4
+ pointColors: Float32Array;
5
+ };
@@ -0,0 +1,5 @@
1
+ import { Graph } from '../../..';
2
+ export declare const pinnedPoints: () => {
3
+ graph: Graph;
4
+ div: HTMLDivElement;
5
+ };
@@ -7,4 +7,5 @@ export declare const BasicSetUp: Story;
7
7
  export declare const PointLabels: Story;
8
8
  export declare const RemovePoints: Story;
9
9
  export declare const LinkHovering: Story;
10
+ export declare const PinnedPoints: Story;
10
11
  export default meta;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cosmos.gl/graph",
3
- "version": "2.5.0",
3
+ "version": "2.6.0",
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
@@ -44,6 +44,9 @@ export interface GraphConfigInterface {
44
44
  * in the format `[red, green, blue, alpha]` where each value is a number between 0 and 255.
45
45
  * Default value: '#b3b3b3'
46
46
  */
47
+ pointDefaultColor?: string | [number, number, number, number];
48
+
49
+ /** @deprecated Use `pointDefaultColor` instead */
47
50
  pointColor?: string | [number, number, number, number];
48
51
 
49
52
  /**
@@ -51,7 +54,7 @@ export interface GraphConfigInterface {
51
54
  * This can be either a hex color string (e.g., '#b3b3b3') or an array of RGBA values
52
55
  * in the format `[red, green, blue, alpha]` where each value is a number between 0 and 255.
53
56
  *
54
- * If not provided, the color will be the same as the `pointColor`,
57
+ * If not provided, the color will be the same as the point's original color,
55
58
  * but darkened or lightened depending on the background color.
56
59
  *
57
60
  * If `pointGreyoutOpacity` is also defined, it will override the alpha/opacity component
@@ -615,6 +618,11 @@ export class GraphConfig implements GraphConfigInterface {
615
618
  public backgroundColor = defaultBackgroundColor
616
619
  public spaceSize = defaultConfigValues.spaceSize
617
620
  public pointColor = defaultPointColor
621
+ // TODO: When pointColor is removed, change this to:
622
+ // public pointDefaultColor = defaultPointColor
623
+ // Currently undefined to allow fallback to deprecated pointColor via nullish coalescing
624
+ // in GraphData.updatePointColor() (see: this._config.pointDefaultColor ?? this._config.pointColor)
625
+ public pointDefaultColor = undefined
618
626
  public pointGreyoutOpacity = defaultGreyoutPointOpacity
619
627
  public pointGreyoutColor = defaultGreyoutPointColor
620
628
  public pointSize = defaultPointSize
package/src/index.ts CHANGED
@@ -247,7 +247,8 @@ export class Graph {
247
247
  if (this._isDestroyed || !this.reglInstance || !this.points || !this.lines || !this.clusters) return
248
248
  const prevConfig = { ...this.config }
249
249
  this.config.init(config)
250
- if (prevConfig.pointColor !== this.config.pointColor) {
250
+ if ((prevConfig.pointDefaultColor !== this.config.pointDefaultColor) ||
251
+ (prevConfig.pointColor !== this.config.pointColor)) {
251
252
  this.graph.updatePointColor()
252
253
  this.points.updateColor()
253
254
  }
@@ -340,6 +341,7 @@ export class Graph {
340
341
  this.isPointSizeUpdateNeeded = true
341
342
  this.isPointShapeUpdateNeeded = true
342
343
  this.isPointImageIndicesUpdateNeeded = true
344
+ this.isPointImageSizesUpdateNeeded = true
343
345
  this.isPointClusterUpdateNeeded = true
344
346
  this.isForceManyBodyUpdateNeeded = true
345
347
  this.isForceLinkUpdateNeeded = true
@@ -592,6 +594,29 @@ export class Graph {
592
594
  this.isPointClusterUpdateNeeded = true
593
595
  }
594
596
 
597
+ /**
598
+ * Sets which points are pinned (fixed) in position.
599
+ *
600
+ * Pinned points:
601
+ * - Do not move due to physics forces (gravity, repulsion, link forces, etc.)
602
+ * - Still participate in force calculations (other nodes are attracted to/repelled by them)
603
+ * - Can still be dragged by the user if `enableDrag` is true
604
+ *
605
+ * @param {number[] | null} pinnedIndices - Array of point indices to pin. Set to `[]` or `null` to unpin all points.
606
+ * @example
607
+ * // Pin points 0 and 5
608
+ * graph.setPinnedPoints([0, 5])
609
+ *
610
+ * // Unpin all points
611
+ * graph.setPinnedPoints([])
612
+ * graph.setPinnedPoints(null)
613
+ */
614
+ public setPinnedPoints (pinnedIndices: number[] | null): void {
615
+ if (this._isDestroyed || !this.points) return
616
+ this.graph.inputPinnedPoints = pinnedIndices && pinnedIndices.length > 0 ? pinnedIndices : undefined
617
+ this.points.updatePinnedStatus()
618
+ }
619
+
595
620
  /**
596
621
  * Renders the graph.
597
622
  *
@@ -1361,6 +1386,8 @@ export class Graph {
1361
1386
  // To prevent the dragged point from suddenly jumping, run the drag function twice
1362
1387
  this.points?.drag()
1363
1388
  this.points?.drag()
1389
+ // Update tracked positions after drag, even when simulation is disabled
1390
+ this.points?.trackPoints()
1364
1391
  }
1365
1392
  this.fpsMonitor?.end(now)
1366
1393
 
@@ -27,6 +27,7 @@ export class GraphData {
27
27
  public inputPointClusters: (number | undefined)[] | undefined
28
28
  public inputClusterPositions: (number | undefined)[] | undefined
29
29
  public inputClusterStrength: Float32Array | undefined
30
+ public inputPinnedPoints: number[] | undefined
30
31
 
31
32
  public pointPositions: Float32Array | undefined
32
33
  public pointColors: Float32Array | undefined
@@ -87,7 +88,7 @@ export class GraphData {
87
88
  }
88
89
 
89
90
  // Sets point colors to default values from config if the input is missing or does not match input points number.
90
- const defaultRgba = getRgbaColor(this._config.pointColor)
91
+ const defaultRgba = getRgbaColor(this._config.pointDefaultColor ?? this._config.pointColor)
91
92
  if (this.inputPointColors === undefined || this.inputPointColors.length / 4 !== this.pointsNumber) {
92
93
  this.pointColors = new Float32Array(this.pointsNumber * 4)
93
94
  for (let i = 0; i < this.pointColors.length / 4; i++) {
@@ -61,6 +61,8 @@ export class Points extends CoreModule {
61
61
  private trackedIndices: number[] | undefined
62
62
  private selectedTexture: regl.Texture2D | undefined
63
63
  private greyoutStatusTexture: regl.Texture2D | undefined
64
+ private pinnedStatusTexture: regl.Texture2D | undefined
65
+ private pinnedStatusFbo: regl.Framebuffer2D | undefined
64
66
  private sizeTexture: regl.Texture2D | undefined
65
67
  private trackedIndicesTexture: regl.Texture2D | undefined
66
68
  private polygonPathTexture: regl.Texture2D | undefined
@@ -172,6 +174,7 @@ export class Points extends CoreModule {
172
174
  this.sampledPointIndices(createIndexesForBuffer(store.pointsTextureSize))
173
175
 
174
176
  this.updateGreyoutStatus()
177
+ this.updatePinnedStatus()
175
178
  this.updateSampledPointsGrid()
176
179
 
177
180
  this.trackPointsByIndices()
@@ -193,6 +196,7 @@ export class Points extends CoreModule {
193
196
  velocity: () => this.velocityFbo,
194
197
  friction: () => config.simulationFriction,
195
198
  spaceSize: () => store.adjustedSpaceSize,
199
+ pinnedStatusTexture: () => this.pinnedStatusFbo,
196
200
  },
197
201
  })
198
202
  }
@@ -520,6 +524,36 @@ export class Points extends CoreModule {
520
524
  })
521
525
  }
522
526
 
527
+ public updatePinnedStatus (): void {
528
+ const { reglInstance, store: { pointsTextureSize }, data } = this
529
+ if (!pointsTextureSize) return
530
+
531
+ // Pinned status: 0 - not pinned, 1 - pinned
532
+ const initialState = new Float32Array(pointsTextureSize * pointsTextureSize * 4).fill(0)
533
+
534
+ if (data.inputPinnedPoints && data.pointsNumber !== undefined) {
535
+ for (const pinnedIndex of data.inputPinnedPoints) {
536
+ if (pinnedIndex >= 0 && pinnedIndex < data.pointsNumber) {
537
+ initialState[pinnedIndex * 4] = 1
538
+ }
539
+ }
540
+ }
541
+
542
+ if (!this.pinnedStatusTexture) this.pinnedStatusTexture = reglInstance.texture()
543
+ this.pinnedStatusTexture({
544
+ data: initialState,
545
+ width: pointsTextureSize,
546
+ height: pointsTextureSize,
547
+ type: 'float',
548
+ })
549
+ if (!this.pinnedStatusFbo) this.pinnedStatusFbo = reglInstance.framebuffer()
550
+ this.pinnedStatusFbo({
551
+ color: this.pinnedStatusTexture,
552
+ depth: false,
553
+ stencil: false,
554
+ })
555
+ }
556
+
523
557
  public updateSize (): void {
524
558
  const { reglInstance, store: { pointsTextureSize }, data } = this
525
559
  if (!pointsTextureSize || data.pointsNumber === undefined || data.pointSizes === undefined) return
@@ -4,6 +4,7 @@ precision highp float;
4
4
 
5
5
  uniform sampler2D positionsTexture;
6
6
  uniform sampler2D velocity;
7
+ uniform sampler2D pinnedStatusTexture;
7
8
  uniform float friction;
8
9
  uniform float spaceSize;
9
10
 
@@ -13,6 +14,17 @@ void main() {
13
14
  vec4 pointPosition = texture2D(positionsTexture, textureCoords);
14
15
  vec4 pointVelocity = texture2D(velocity, textureCoords);
15
16
 
17
+ // Check if point is pinned
18
+ // pinnedStatusTexture has the same size and layout as positionsTexture
19
+ // Each pixel corresponds to a point: red channel > 0.5 means the point is pinned
20
+ vec4 pinnedStatus = texture2D(pinnedStatusTexture, textureCoords);
21
+
22
+ // If pinned, don't update position
23
+ if (pinnedStatus.r > 0.5) {
24
+ gl_FragColor = pointPosition;
25
+ return;
26
+ }
27
+
16
28
  // Friction
17
29
  pointVelocity.rg *= friction;
18
30
 
@@ -2,13 +2,19 @@ import { Meta } from "@storybook/blocks";
2
2
 
3
3
  <Meta title="Welcome to cosmos.gl" />
4
4
 
5
- <p style={{ fontSize: '2rem', lineHeight: '1.25em' }}>Welcome to cosmos.gl — a high-performance WebGL library for visualizing network graphs and machine learning embeddings.</p>
5
+ <div style={{ fontSize: '1.0rem', float: 'right' }}>
6
+ <a href="https://github.com/cosmosgl/graph" target="_blank" rel="noopener noreferrer" style={{ display: 'inline-flex', alignItems: 'center', gap: '0.5rem', color: '#fff' }}>
7
+ <svg height="30" viewBox="0 0 16 16" width="30" aria-hidden="true">
8
+ <path fill="currentColor" d="M8 0c4.42 0 8 3.58 8 8a8.013 8.013 0 0 1-5.45 7.59c-.4.08-.55-.17-.55-.38 0-.27.01-1.13.01-2.2 0-.75-.25-1.23-.54-1.48 1.78-.2 3.65-.88 3.65-3.95 0-.88-.31-1.59-.82-2.15.08-.2.36-1.02-.08-2.12 0 0-.67-.22-2.2.82-.64-.18-1.32-.27-2-.27-.68 0-1.36.09-2 .27-1.53-1.03-2.2-.82-2.2-.82-.44 1.1-.16 1.92-.08 2.12-.51.56-.82 1.28-.82 2.15 0 3.06 1.86 3.75 3.64 3.95-.23.2-.44.55-.51 1.07-.46.21-1.61.55-2.33-.66-.15-.24-.6-.83-1.23-.82-.67.01-.27.38.01.53.34.19.73.9.82 1.13.16.45.68 1.31 2.69.94 0 .67.01 1.3.01 1.49 0 .21-.15.45-.55.38A7.995 7.995 0 0 1 0 8c0-4.42 3.58-8 8-8Z"></path>
9
+ </svg>
10
+ </a>
11
+ </div>
12
+
13
+ <p style={{ fontSize: '1.75rem', lineHeight: '1.25em', marginTop: 0 }}>Welcome to <b>cosmos.gl</b> – a high-performance WebGL library for visualizing network graphs and machine learning embeddings.</p>
6
14
 
7
15
  <video style={{ width: '100%' }} src="https://user-images.githubusercontent.com/755708/173392407-9b05cbb6-d39e-4c2c-ab41-50900cfda823.mp4" loop autoPlay muted playsInline>
8
16
  </video>
9
17
 
10
- <p style={{ fontSize: '1.0rem' }}>Here you can find documentaion and examples of how to use cosmos.gl</p>
11
-
12
18
  ---
13
19
 
14
20
  ### Quick Start
@@ -9,9 +9,10 @@ import { Meta } from "@storybook/blocks";
9
9
  | enableSimulation | If set to `false`, the simulation will not run. This property will be applied only on component initialization and it can't be changed using the `setConfig` method | `true` |
10
10
  | backgroundColor | Canvas background color | `#222222` |
11
11
  | spaceSize | Simulation space size (max 8192) | `8192` |
12
- | pointColor | The default color to use for points when no point 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., '#b3b3b3') or an array of RGBA values in the format `[red, green, blue, alpha]` where each value is a number between 0 and 255 | `#b3b3b3` |
12
+ | pointDefaultColor | The default color to use for points when no point 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., '#b3b3b3') or an array of RGBA values in the format `[red, green, blue, alpha]` where each value is a number between 0 and 255 | `#b3b3b3` |
13
+ | pointColor | **[DEPRECATED]** Use `pointDefaultColor` instead. The default color to use for points when no point colors are provided... | `#b3b3b3` |
13
14
  | pointGreyoutOpacity | Greyed out point opacity value when the selection is active | `undefined` |
14
- | pointGreyoutColor | Greyed out point color value when the selection is active | `undefined` |
15
+ | pointGreyoutColor | The color to use for points when they are greyed out (when selection is active). This can be either a hex color string (e.g., '#b3b3b3') or an array of RGBA values in the format `[red, green, blue, alpha]` where each value is a number between 0 and 255. If not provided, the color will be the same as the point's original color, but darkened or lightened depending on the background color. If `pointGreyoutOpacity` is also defined, it will override the alpha/opacity component of this color. | `undefined` |
15
16
  | 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
17
  | 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` |
17
18
  | pointSizeScale | Scale factor for the point size | `1` |
@@ -23,7 +23,7 @@ export const basicSetUp = (): { graph: Graph; div: HTMLDivElement} => {
23
23
  spaceSize: 4096,
24
24
  backgroundColor: '#2d313a',
25
25
  pointSize: 4,
26
- pointColor: '#4B5BBF',
26
+ pointDefaultColor: '#4B5BBF',
27
27
  linkWidth: 0.6,
28
28
  scalePointsOnZoom: true,
29
29
  linkColor: '#5F74C2',
@@ -0,0 +1,153 @@
1
+ // Note: This is vibe coding only - quick prototype code for demonstration purposes
2
+
3
+ function getRandom (min: number, max: number): number {
4
+ return Math.random() * (max - min) + min
5
+ }
6
+
7
+ function hslToRgb (hue: number, saturation: number, lightness: number): [number, number, number] {
8
+ const c = (1 - Math.abs(2 * lightness - 1)) * saturation
9
+ const x = c * (1 - Math.abs(((hue / 60) % 2) - 1))
10
+ const m = lightness - c / 2
11
+
12
+ let r, g, b
13
+ if (hue >= 0 && hue < 60) {
14
+ r = c; g = x; b = 0
15
+ } else if (hue >= 60 && hue < 120) {
16
+ r = x; g = c; b = 0
17
+ } else if (hue >= 120 && hue < 180) {
18
+ r = 0; g = c; b = x
19
+ } else if (hue >= 180 && hue < 240) {
20
+ r = 0; g = x; b = c
21
+ } else if (hue >= 240 && hue < 300) {
22
+ r = x; g = 0; b = c
23
+ } else {
24
+ r = c; g = 0; b = x
25
+ }
26
+
27
+ return [r + m, g + m, b + m]
28
+ }
29
+
30
+ export function generateData (numNodes = 60): { pointPositions: Float32Array; links: Float32Array; pointColors: Float32Array } {
31
+ const pointPositions = new Float32Array(numNodes * 2)
32
+ const pointColors = new Float32Array(numNodes * 4)
33
+ const linksArray: number[] = []
34
+
35
+ const centerX = 2048
36
+ const centerY = 2048
37
+ const circleRadius = 900
38
+
39
+ // First, place 6 nodes in a perfect circle with equal spacing
40
+ const numCircleNodes = 6
41
+ for (let i = 0; i < numCircleNodes; i++) {
42
+ const angle = (i / numCircleNodes) * Math.PI * 2
43
+ const x = centerX + Math.cos(angle) * circleRadius
44
+ const y = centerY + Math.sin(angle) * circleRadius
45
+
46
+ pointPositions[i * 2] = x
47
+ pointPositions[i * 2 + 1] = y
48
+
49
+ // Color based on position - rainbow gradient
50
+ const hue = (i / numNodes) * 360
51
+ const [r, g, b] = hslToRgb(hue, 0.7, 0.6)
52
+
53
+ pointColors[i * 4] = r
54
+ pointColors[i * 4 + 1] = g
55
+ pointColors[i * 4 + 2] = b
56
+ pointColors[i * 4 + 3] = 1.0
57
+ }
58
+
59
+ // Create remaining nodes in clusters around the space
60
+ const numClusters = 4
61
+ const remainingNodes = numNodes - numCircleNodes
62
+ const nodesPerCluster = Math.floor(remainingNodes / numClusters)
63
+
64
+ for (let cluster = 0; cluster < numClusters; cluster++) {
65
+ const clusterAngle = (cluster / numClusters) * Math.PI * 2
66
+ const clusterRadius = 1200
67
+ const clusterX = centerX + Math.cos(clusterAngle) * clusterRadius
68
+ const clusterY = centerY + Math.sin(clusterAngle) * clusterRadius
69
+
70
+ const startIndex = numCircleNodes + cluster * nodesPerCluster
71
+ const endIndex = cluster === numClusters - 1 ? numNodes : startIndex + nodesPerCluster
72
+
73
+ for (let i = startIndex; i < endIndex; i++) {
74
+ // Position nodes in a small cluster
75
+ const angle = (i - startIndex) / (endIndex - startIndex) * Math.PI * 2
76
+ const radius = 300 + getRandom(-50, 50)
77
+ const x = clusterX + Math.cos(angle) * radius * getRandom(0.7, 1.3)
78
+ const y = clusterY + Math.sin(angle) * radius * getRandom(0.7, 1.3)
79
+
80
+ pointPositions[i * 2] = x
81
+ pointPositions[i * 2 + 1] = y
82
+
83
+ // Color based on position - rainbow gradient
84
+ const hue = (i / numNodes) * 360
85
+ const [r, g, b] = hslToRgb(hue, 0.7, 0.6)
86
+
87
+ pointColors[i * 4] = r
88
+ pointColors[i * 4 + 1] = g
89
+ pointColors[i * 4 + 2] = b
90
+ pointColors[i * 4 + 3] = 1.0
91
+ }
92
+ }
93
+
94
+ // Create links: connect the 6 circle nodes to form a ring
95
+ for (let i = 0; i < numCircleNodes; i++) {
96
+ const nextIndex = (i + 1) % numCircleNodes
97
+ linksArray.push(i)
98
+ linksArray.push(nextIndex)
99
+ }
100
+
101
+ // Connect circle nodes to nearby cluster nodes - more connections
102
+ for (let i = 0; i < numCircleNodes; i++) {
103
+ const circleAngle = (i / numCircleNodes) * Math.PI * 2
104
+ // Find nearest cluster and connect to many nodes in it
105
+ const nearestCluster = Math.floor((circleAngle / (Math.PI * 2)) * numClusters) % numClusters
106
+ const clusterStart = numCircleNodes + nearestCluster * nodesPerCluster
107
+ const clusterEnd = nearestCluster === numClusters - 1 ? numNodes : clusterStart + nodesPerCluster
108
+ // Connect to many nodes in the nearest cluster
109
+ for (let j = clusterStart; j < Math.min(clusterStart + Math.floor(nodesPerCluster * 0.6), clusterEnd); j++) {
110
+ linksArray.push(i)
111
+ linksArray.push(j)
112
+ }
113
+ // Also connect to some nodes in adjacent clusters
114
+ const nextCluster = (nearestCluster + 1) % numClusters
115
+ const nextClusterStart = numCircleNodes + nextCluster * nodesPerCluster
116
+ const nextClusterEnd = nextCluster === numClusters - 1 ? numNodes : nextClusterStart + nodesPerCluster
117
+ for (let j = nextClusterStart; j < Math.min(nextClusterStart + Math.floor(nodesPerCluster * 0.3), nextClusterEnd); j++) {
118
+ linksArray.push(i)
119
+ linksArray.push(j)
120
+ }
121
+ }
122
+
123
+ // Connect nodes within clusters and some cross-cluster links
124
+ for (let i = numCircleNodes; i < numNodes; i++) {
125
+ const cluster = Math.floor((i - numCircleNodes) / nodesPerCluster)
126
+ const clusterStart = numCircleNodes + cluster * nodesPerCluster
127
+ const clusterEnd = cluster === numClusters - 1 ? numNodes : clusterStart + nodesPerCluster
128
+
129
+ // Connect to nearby nodes in the same cluster
130
+ for (let j = clusterStart; j < clusterEnd; j++) {
131
+ if (i !== j && Math.abs(i - j) <= 3) {
132
+ linksArray.push(i)
133
+ linksArray.push(j)
134
+ }
135
+ }
136
+
137
+ // Connect to nodes in adjacent clusters (sparse connections)
138
+ if (i % 3 === 0) {
139
+ const nextCluster = (cluster + 1) % numClusters
140
+ const nextClusterStart = numCircleNodes + nextCluster * nodesPerCluster
141
+ const nextClusterEnd = nextCluster === numClusters - 1 ? numNodes : nextClusterStart + nodesPerCluster
142
+ const targetIndex = nextClusterStart + Math.floor(((i - clusterStart) % nodesPerCluster) * (nextClusterEnd - nextClusterStart) / nodesPerCluster)
143
+ if (targetIndex < numNodes && targetIndex >= numCircleNodes) {
144
+ linksArray.push(i)
145
+ linksArray.push(targetIndex)
146
+ }
147
+ }
148
+ }
149
+
150
+ const links = new Float32Array(linksArray)
151
+
152
+ return { pointPositions, links, pointColors }
153
+ }
@@ -0,0 +1,61 @@
1
+ import { Graph } from '@cosmos.gl/graph'
2
+ import { generateData } from './data-gen'
3
+
4
+ export const pinnedPoints = (): { graph: Graph; div: HTMLDivElement} => {
5
+ const div = document.createElement('div')
6
+ div.style.height = '100vh'
7
+ div.style.width = '100%'
8
+ div.style.position = 'relative'
9
+
10
+ const infoPanel = document.createElement('div')
11
+ infoPanel.textContent = 'White points are pinned. Try to move them.'
12
+ Object.assign(infoPanel.style, {
13
+ position: 'absolute',
14
+ top: '20px',
15
+ left: '20px',
16
+ color: 'white',
17
+ fontSize: '14px',
18
+ })
19
+ div.appendChild(infoPanel)
20
+
21
+ const graph = new Graph(div, {
22
+ spaceSize: 4096,
23
+ backgroundColor: '#2d313a',
24
+ curvedLinks: true,
25
+ enableDrag: true,
26
+ simulationLinkSpring: 3.1,
27
+ simulationRepulsion: 150,
28
+ simulationGravity: 0.05,
29
+ simulationDecay: 10000000,
30
+ attribution: 'visualized with <a href="https://cosmograph.app/" style="color: var(--cosmosgl-attribution-color);" target="_blank">Cosmograph</a>',
31
+ })
32
+
33
+ const { pointPositions, links, pointColors } = generateData(100)
34
+
35
+ const pinnedIndices = [0, 1, 2, 3, 4, 5]
36
+ const numPoints = pointPositions.length / 2
37
+
38
+ const colors = new Float32Array(pointColors)
39
+ for (const pinnedIndex of pinnedIndices) {
40
+ colors[pinnedIndex * 4] = 1.0
41
+ colors[pinnedIndex * 4 + 1] = 1.0
42
+ colors[pinnedIndex * 4 + 2] = 1.0
43
+ colors[pinnedIndex * 4 + 3] = 1.0
44
+ }
45
+
46
+ const pointSizes = new Float32Array(numPoints).fill(12)
47
+ for (const pinnedIndex of pinnedIndices) {
48
+ pointSizes[pinnedIndex] = 30
49
+ }
50
+
51
+ graph.setPointPositions(pointPositions)
52
+ graph.setPointColors(colors)
53
+ graph.setPointSizes(pointSizes)
54
+ graph.setLinks(links)
55
+ graph.setPinnedPoints(pinnedIndices)
56
+
57
+ graph.zoom(0.8)
58
+ graph.render()
59
+
60
+ return { div, graph }
61
+ }
@@ -8,7 +8,7 @@ export const quickStart = (): { graph: Graph; div: HTMLDivElement} => {
8
8
  const config: GraphConfigInterface = {
9
9
  spaceSize: 4096,
10
10
  backgroundColor: '#2d313a',
11
- pointColor: '#F069B4',
11
+ pointDefaultColor: '#F069B4',
12
12
  scalePointsOnZoom: true,
13
13
  simulationFriction: 0.1, // keeps the graph inert
14
14
  simulationGravity: 0, // disables gravity
@@ -4,7 +4,7 @@ export const config: GraphConfigInterface = {
4
4
  spaceSize: 4096,
5
5
  backgroundColor: '#2d313a',
6
6
  pointSize: 4,
7
- pointColor: '#4B5BBF',
7
+ pointDefaultColor: '#4B5BBF',
8
8
  scalePointsOnZoom: true,
9
9
  pointGreyoutOpacity: 0.1,
10
10
  linkWidth: 0.6,
@@ -7,6 +7,7 @@ import { basicSetUp } from './beginners/basic-set-up'
7
7
  import { pointLabels } from './beginners/point-labels'
8
8
  import { removePoints } from './beginners/remove-points'
9
9
  import { linkHovering } from './beginners/link-hovering'
10
+ import { pinnedPoints } from './beginners/pinned-points'
10
11
 
11
12
  import quickStartStoryRaw from './beginners/quick-start?raw'
12
13
  import basicSetUpStoryRaw from './beginners/basic-set-up/index?raw'
@@ -23,6 +24,8 @@ import removePointsStoryDataGenRaw from './beginners/remove-points/data-gen.ts?r
23
24
  import linkHoveringStoryRaw from './beginners/link-hovering/index?raw'
24
25
  import linkHoveringStoryDataGenRaw from './beginners/link-hovering/data-generator.ts?raw'
25
26
  import linkHoveringStoryCssRaw from './beginners/link-hovering/style.css?raw'
27
+ import pinnedPointsStoryRaw from './beginners/pinned-points/index?raw'
28
+ import pinnedPointsStoryDataGenRaw from './beginners/pinned-points/data-gen.ts?raw'
26
29
 
27
30
  // More on how to set up stories at: https://storybook.js.org/docs/writing-stories#default-export
28
31
  const meta: Meta<CosmosStoryProps> = {
@@ -113,5 +116,16 @@ export const LinkHovering: Story = {
113
116
  },
114
117
  }
115
118
 
119
+ export const PinnedPoints: Story = {
120
+ ...createStory(pinnedPoints),
121
+ name: 'Pinned Points',
122
+ parameters: {
123
+ sourceCode: [
124
+ { name: 'Story', code: pinnedPointsStoryRaw },
125
+ { name: 'data-gen.ts', code: pinnedPointsStoryDataGenRaw },
126
+ ],
127
+ },
128
+ }
129
+
116
130
  // eslint-disable-next-line import/no-default-export
117
131
  export default meta
@@ -23,7 +23,7 @@ export const createCosmos = (props: CosmosStoryProps): { div: HTMLDivElement; gr
23
23
  const config: GraphConfigInterface = {
24
24
  backgroundColor: '#2d313a',
25
25
  pointSize: 3,
26
- pointColor: '#4B5BBF',
26
+ pointDefaultColor: '#4B5BBF',
27
27
  pointGreyoutOpacity: 0.1,
28
28
  scalePointsOnZoom: true,
29
29
  linkWidth: 0.8,
@@ -31,7 +31,7 @@ export const moscowMetroStations = (): {graph: Graph; div: HTMLDivElement} => {
31
31
  backgroundColor: '#2d313a',
32
32
  scalePointsOnZoom: false,
33
33
  rescalePositions,
34
- pointColor: '#FEE08B',
34
+ pointDefaultColor: '#FEE08B',
35
35
  enableSimulation: false,
36
36
  enableDrag: false,
37
37
  fitViewOnInit: true,