@cosmos.gl/graph 2.3.1 → 2.5.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 (60) hide show
  1. package/.eslintrc +61 -0
  2. package/CHARTER.md +69 -0
  3. package/GOVERNANCE.md +21 -0
  4. package/dist/config.d.ts +69 -0
  5. package/dist/index.d.ts +62 -21
  6. package/dist/index.js +5672 -5188
  7. package/dist/index.js.map +1 -1
  8. package/dist/index.min.js +272 -86
  9. package/dist/index.min.js.map +1 -1
  10. package/dist/modules/GraphData/index.d.ts +18 -2
  11. package/dist/modules/Lines/index.d.ts +8 -0
  12. package/dist/modules/Points/atlas-utils.d.ts +24 -0
  13. package/dist/modules/Points/index.d.ts +21 -2
  14. package/dist/modules/Store/index.d.ts +20 -3
  15. package/dist/modules/core-module.d.ts +1 -0
  16. package/dist/stories/beginners/link-hovering/data-generator.d.ts +19 -0
  17. package/dist/stories/beginners/link-hovering/index.d.ts +5 -0
  18. package/dist/stories/beginners.stories.d.ts +1 -0
  19. package/dist/stories/create-story.d.ts +5 -1
  20. package/dist/stories/shapes/image-example/index.d.ts +5 -0
  21. package/dist/stories/shapes.stories.d.ts +1 -0
  22. package/dist/variables.d.ts +5 -2
  23. package/package.json +4 -4
  24. package/src/config.ts +87 -2
  25. package/src/declaration.d.ts +5 -0
  26. package/src/index.ts +270 -98
  27. package/src/modules/GraphData/index.ts +68 -6
  28. package/src/modules/Lines/draw-curve-line.frag +12 -1
  29. package/src/modules/Lines/draw-curve-line.vert +29 -2
  30. package/src/modules/Lines/hovered-line-index.frag +27 -0
  31. package/src/modules/Lines/hovered-line-index.vert +8 -0
  32. package/src/modules/Lines/index.ts +112 -2
  33. package/src/modules/Points/atlas-utils.ts +137 -0
  34. package/src/modules/Points/draw-highlighted.vert +3 -3
  35. package/src/modules/Points/draw-points.frag +106 -14
  36. package/src/modules/Points/draw-points.vert +51 -25
  37. package/src/modules/Points/find-points-on-area-selection.frag +6 -5
  38. package/src/modules/Points/index.ts +121 -13
  39. package/src/modules/Store/index.ts +44 -5
  40. package/src/modules/core-module.ts +1 -0
  41. package/src/stories/1. welcome.mdx +2 -1
  42. package/src/stories/2. configuration.mdx +10 -1
  43. package/src/stories/3. api-reference.mdx +61 -5
  44. package/src/stories/beginners/basic-set-up/index.ts +20 -10
  45. package/src/stories/beginners/link-hovering/data-generator.ts +198 -0
  46. package/src/stories/beginners/link-hovering/index.ts +61 -0
  47. package/src/stories/beginners/link-hovering/style.css +73 -0
  48. package/src/stories/beginners/quick-start.ts +2 -1
  49. package/src/stories/beginners/remove-points/index.ts +28 -30
  50. package/src/stories/beginners.stories.ts +17 -0
  51. package/src/stories/clusters/polygon-selection/index.ts +2 -4
  52. package/src/stories/create-story.ts +32 -5
  53. package/src/stories/shapes/image-example/icons/box.png +0 -0
  54. package/src/stories/shapes/image-example/icons/lego.png +0 -0
  55. package/src/stories/shapes/image-example/icons/s.png +0 -0
  56. package/src/stories/shapes/image-example/icons/swift.png +0 -0
  57. package/src/stories/shapes/image-example/icons/toolbox.png +0 -0
  58. package/src/stories/shapes/image-example/index.ts +238 -0
  59. package/src/stories/shapes.stories.ts +12 -0
  60. package/src/variables.ts +5 -2
@@ -35,7 +35,8 @@ const config = {
35
35
  fitViewPadding: 0.3, // centers the graph width padding of ~30% of screen
36
36
  rescalePositions: true, // rescale positions
37
37
  enableDrag: true, // enable dragging points
38
- onClick: pointIndex => { console.log('Clicked point index: ', pointIndex) },
38
+ onPointClick: pointIndex => { console.log('Clicked point index: ', pointIndex) },
39
+ onBackgroundClick: () => { console.log('Clicked background') },
39
40
  /* ... */
40
41
  }
41
42
 
@@ -16,6 +16,7 @@ import { Meta } from "@storybook/blocks";
16
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` |
17
17
  | pointSizeScale | Scale factor for the point size | `1` |
18
18
  | hoveredPointCursor | Cursor style to use when hovering over a point | `auto` |
19
+ | hoveredLinkCursor | Cursor style to use when hovering over a link | `auto` |
19
20
  | renderHoveredPointRing | Turns ring rendering around a point on hover on / off | `false` |
20
21
  | hoveredPointRingColor | Hovered point ring color hex value or an array of RGBA values | `white` |
21
22
  | focusedPointRingColor | Focused point ring color hex value or an array of RGBA values | `white` |
@@ -26,6 +27,8 @@ import { Meta } from "@storybook/blocks";
26
27
  | linkGreyoutOpacity | Greyed out link opacity value when the selection is active | `0.1` |
27
28
  | 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` |
28
29
  | linkWidthScale | Scale factor for the link width | `1` |
30
+ | hoveredLinkColor | The color to use for links when they are hovered. This can be either a hex color string (e.g., '#ff3333') or an array of RGBA values in the format `[red, green, blue, alpha]` where each value is a number between 0 and 255 | `undefined` |
31
+ | hoveredLinkWidthIncrease | Number of pixels to add to the link width when hovered | `5` |
29
32
  | scaleLinksOnZoom | Increase/decrease link width when zooming | `false` |
30
33
  | curvedLinks | If set to true, links are rendered as curved lines. Otherwise as straight lines | `false` |
31
34
  | curvedLinkSegments | Number of segments in a curved line | `19` |
@@ -86,11 +89,17 @@ cosmos.gl layout algorithm was inspired by the [d3-force](https://github.com/d3/
86
89
  | onSimulationTick | Called on every simulation tick, with the current alpha value and hover information |
87
90
  | onSimulationEnd | Called when simulation stops |
88
91
  | onSimulationPause | Called when simulation pauses |
89
- | onSimulationRestart | Called when simulation restarts |
92
+ | onSimulationUnpause | Called when simulation unpauses |
93
+ | onSimulationRestart | **[DEPRECATED]** Called when simulation restarts. Use `onSimulationUnpause` instead |
90
94
  | onClick | Called on canvas click with point index and position |
95
+ | onPointClick | Called when a point is clicked |
96
+ | onLinkClick | Called when a link is clicked |
97
+ | onBackgroundClick | Called when the background (empty space) is clicked |
91
98
  | onMouseMove | Called on mouse movement with hover info |
92
99
  | onPointMouseOver | Called when pointer enters a point |
93
100
  | onPointMouseOut | Called when pointer leaves a point |
101
+ | onLinkMouseOver | Called when pointer enters a link |
102
+ | onLinkMouseOut | Called when pointer leaves a link |
94
103
  | onZoomStart | Called when zoom/pan starts |
95
104
  | onZoom | Called during zoom/pan |
96
105
  | onZoomEnd | Called when zoom/pan ends |
@@ -8,11 +8,14 @@ This method sets the [cosmos.gl configuration](../?path=/docs/configuration--doc
8
8
 
9
9
  * **`config`** (Object): The configuration object adhering to cosmos.gl configuration properties.
10
10
 
11
- ### <a name="set_point_positions" href="#set_point_positions">#</a> graph.<b>setPointPositions</b>(<i>pointPositions</i>)
11
+ ### <a name="set_point_positions" href="#set_point_positions">#</a> graph.<b>setPointPositions</b>(<i>pointPositions</i>, [<i>dontRescale</i>])
12
12
 
13
13
  This method sets the positions of points in a cosmos.gl graph using the provided coordinates array.
14
14
 
15
15
  * **`pointPositions`** (Float32Array): A Float32Array representing the x and y coordinates of points in the format `[x1, y1, x2, y2, ..., xN, yN]`. Each pair represents the coordinates of a single point.
16
+ * **`dontRescale`** (Boolean, optional): For this call only, controls whether to rescale the points.
17
+ - `true`: Don't rescale the points.
18
+ - `false` or `undefined` (default): Use the behavior defined by `config.rescalePositions`.
16
19
 
17
20
  **Example:**
18
21
  ```javascript
@@ -91,8 +94,11 @@ This method sets the shapes for the graph points.
91
94
  | 5 | Hexagon |
92
95
  | 6 | Star |
93
96
  | 7 | Cross |
97
+ | 8 | None |
94
98
 
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).
99
+ 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-8) will default to Circle (0).
100
+
101
+ Images are rendered above shapes.
96
102
 
97
103
  **Example:**
98
104
  ```javascript
@@ -108,6 +114,50 @@ graph.setPointShapes(new Float32Array([
108
114
  graph.render();
109
115
  ```
110
116
 
117
+ ### <a name="set_image_data" href="#set_image_data">#</a> graph.<b>setImageData</b>(<i>imageDataArray</i>)
118
+
119
+ This method sets the images for the graph points using [ImageData](https://developer.mozilla.org/en-US/docs/Web/API/ImageData) objects. Images are rendered above shapes. To use images, provide image indices via `setPointImageIndices()`.
120
+
121
+ Images are rendered above shapes.
122
+
123
+ * **`imageDataArray`** (ImageData[]): Array of ImageData objects to use as point images.
124
+
125
+ **Example:**
126
+ ```javascript
127
+ // Create ImageData objects from canvas or other sources
128
+ const imageData1 = canvas1.getContext('2d').getImageData(0, 0, 32, 32);
129
+ const imageData2 = canvas2.getContext('2d').getImageData(0, 0, 32, 32);
130
+
131
+ // Set the images for the graph
132
+ graph.setImageData([imageData1, imageData2]);
133
+
134
+ // Set which image each point should use (0 = imageData1, 1 = imageData2)
135
+ graph.setPointImageIndices(new Float32Array([0, 1, 0, 1]));
136
+
137
+ // To show shapes with images
138
+ graph.setPointShapes(new Float32Array([0, 1, 2, 3])); // Circle, Square, Triangle, Diamond
139
+
140
+ graph.render();
141
+ ```
142
+
143
+ This example sets up two images and assigns them to four points: the first and third points use imageData1, while the second and fourth points use imageData2. The shapes are also set to show both shapes and images together.
144
+
145
+ ### <a name="set_point_image_indices" href="#set_point_image_indices">#</a> graph.<b>setPointImageIndices</b>(<i>imageIndices</i>)
146
+
147
+ This method sets which image each point should use from the images array provided to `setImageData()`. Images are rendered above shapes.
148
+
149
+ * **`imageIndices`** (Float32Array): A Float32Array representing which image each point uses in the format `[index1, index2, ..., indexN]`, where each value is an index into the images array provided to `setImageData()`.
150
+ * **Valid indices**: Use 0 for the first image, 1 for the second image, etc. Invalid or negative indices will default to -1 (no image).
151
+ * **Default behavior**: When no image indices are provided, all points default to -1 (no image).
152
+
153
+ ### <a name="set_point_image_sizes" href="#set_point_image_sizes">#</a> graph.<b>setPointImageSizes</b>(<i>imageSizes</i>)
154
+
155
+ This method sets the sizes for the point images in the graph.
156
+
157
+ * **`imageSizes`** (Float32Array): A Float32Array representing the sizes of point images in the format `[size1, size2, ..., sizeN]`, where each `size` value corresponds to the size of the image for the point at the same index in the graph's data.
158
+
159
+ Each size value in the array specifies the size of a point image using the same order as the points in the graph data. The sizes are applied to images that have been set using `setImageData()` and referenced via `setPointImageIndices()`.
160
+
111
161
  ### <a name="set_links" href="#set_links">#</a> graph.<b>setLinks</b>(<i>links</i>)
112
162
 
113
163
  This method sets the links (connections) between points in a cosmos.gl graph.
@@ -199,7 +249,7 @@ graph.render();
199
249
 
200
250
  In this example, the `linkArrows` array contains three boolean values. The first value `true` sets an arrow on the first link, the second value `false` leaves the second link without an arrow, and the third value `true` sets an arrow on the third link.
201
251
 
202
- ### <a name="set_link_strengths" href="#set_link_strengths">#</a> graph.<b>setLinkStrength</b>(<i>linkStrength</i>)
252
+ ### <a name="set_link_strength" href="#set_link_strength">#</a> graph.<b>setLinkStrength</b>(<i>linkStrength</i>)
203
253
 
204
254
  This method sets the strength of the graph links.
205
255
 
@@ -251,7 +301,7 @@ This method sets the force strength coefficients for clustering points in the gr
251
301
 
252
302
  The `render` method renders the graph and, optionally, controls the initial energy of the simulation.
253
303
 
254
- * **`runSimulation`** (number, optional): The higher the value, the more initial energy the simulation will get. Zero value stops the simulation.
304
+ * **`simulationAlpha`** (number, optional): The higher the value, the more initial energy the simulation will get. Zero value stops the simulation.
255
305
 
256
306
  ### <a name="zoom_to_point_by_index" href="#zoom_to_point_by_index">#</a> graph.<b>zoomToPointByIndex</b>(<i>index</i>, [<i>duration</i>], [<i>scale</i>], [<i>canZoomOut</i>])
257
307
 
@@ -461,7 +511,13 @@ Starts the simulation with an optional <i>alpha</i> parameter, which controls th
461
511
 
462
512
  Pauses the current simulation in the graph.
463
513
 
464
- ### <a name="restart" href="#restart">#</a> graph.<b>restart</b>()
514
+ ### <a name="unpause" href="#unpause">#</a> graph.<b>unpause</b>()
515
+
516
+ Unpauses (resumes) the current simulation in the graph.
517
+
518
+ ### <a name="restart" href="#restart">#</a> graph.<b>restart</b>() <b style={{ color: 'orange' }}>[DEPRECATED]</b>
519
+
520
+ **⚠️ Deprecated:** Use `unpause()` instead. This method will be removed in a future version.
465
521
 
466
522
  Restarts the current simulation in the graph.
467
523
 
@@ -38,15 +38,15 @@ export const basicSetUp = (): { graph: Graph; div: HTMLDivElement} => {
38
38
  simulationRepulsion: 0.2,
39
39
  simulationGravity: 0.1,
40
40
  simulationDecay: 100000,
41
- onClick: (index: number | undefined): void => {
42
- if (index !== undefined) {
43
- graph.selectPointByIndex(index)
44
- graph.zoomToPointByIndex(index)
45
- } else {
46
- graph.unselectPoints()
47
- }
41
+ onPointClick: (index: number): void => {
42
+ graph.selectPointByIndex(index)
43
+ graph.zoomToPointByIndex(index)
48
44
  console.log('Clicked point index: ', index)
49
45
  },
46
+ onBackgroundClick: (): void => {
47
+ graph.unselectPoints()
48
+ console.log('Clicked background')
49
+ },
50
50
  attribution: 'visualized with <a href="https://cosmograph.app/" style="color: var(--cosmosgl-attribution-color);" target="_blank">Cosmograph</a>',
51
51
  })
52
52
 
@@ -71,18 +71,28 @@ export const basicSetUp = (): { graph: Graph; div: HTMLDivElement} => {
71
71
  graph.pause()
72
72
  }
73
73
 
74
- function start (): void {
74
+ function unpause (): void {
75
75
  isPaused = false
76
76
  pauseButton.textContent = 'Pause'
77
- graph.start()
77
+ // if the graph is at 100% progress, start the graph
78
+ if (graph.progress === 1) {
79
+ graph.start()
80
+ } else {
81
+ graph.unpause()
82
+ }
78
83
  }
79
84
 
80
85
  function togglePause (): void {
81
- if (isPaused) start()
86
+ if (isPaused) unpause()
82
87
  else pause()
83
88
  }
84
89
 
85
90
  pauseButton.addEventListener('click', togglePause)
91
+ graph.setConfig({
92
+ onSimulationEnd: (): void => {
93
+ pause()
94
+ },
95
+ })
86
96
 
87
97
  // Zoom and Select
88
98
  function getRandomPointIndex (): number {
@@ -0,0 +1,198 @@
1
+ interface Point {
2
+ id: number;
3
+ }
4
+
5
+ interface Link {
6
+ source: number;
7
+ target: number;
8
+ }
9
+
10
+ interface NetworkData {
11
+ pointPositions: Float32Array;
12
+ pointColors: Float32Array;
13
+ pointSizes: Float32Array;
14
+ links: Float32Array;
15
+ linkColors: Float32Array;
16
+ linkWidths: Float32Array;
17
+ points: Point[];
18
+ connections: Link[];
19
+ }
20
+
21
+ function hslToRgb (hue: number, saturation: number, lightness: number): [number, number, number] {
22
+ const c = (1 - Math.abs(2 * lightness - 1)) * saturation
23
+ const x = c * (1 - Math.abs(((hue / 60) % 2) - 1))
24
+ const m = lightness - c / 2
25
+
26
+ let r, g, b
27
+ if (hue >= 0 && hue < 60) {
28
+ r = c; g = x; b = 0
29
+ } else if (hue >= 60 && hue < 120) {
30
+ r = x; g = c; b = 0
31
+ } else if (hue >= 120 && hue < 180) {
32
+ r = 0; g = c; b = x
33
+ } else if (hue >= 180 && hue < 240) {
34
+ r = 0; g = x; b = c
35
+ } else if (hue >= 240 && hue < 300) {
36
+ r = x; g = 0; b = c
37
+ } else {
38
+ r = c; g = 0; b = x
39
+ }
40
+
41
+ return [r + m, g + m, b + m]
42
+ }
43
+
44
+ function generatePoints (count: number): Point[] {
45
+ const points: Point[] = []
46
+ for (let i = 0; i < count; i++) {
47
+ points.push({ id: i })
48
+ }
49
+ return points
50
+ }
51
+
52
+ function generateConnections (points: Point[]): Link[] {
53
+ const connections: Link[] = []
54
+ const pointCount = points.length
55
+
56
+ // Sequential connections
57
+ for (let i = 0; i < pointCount; i++) {
58
+ const nextId1 = (i + 1) % pointCount
59
+ const nextId2 = (i + 2) % pointCount
60
+ connections.push({ source: i, target: nextId1 })
61
+ if (i % 2 === 0) {
62
+ connections.push({ source: i, target: nextId2 })
63
+ }
64
+ }
65
+
66
+ // Hub connections
67
+ const hubPoints = [0, 10, 20, 30, 40, 50, 60, 70]
68
+ hubPoints.forEach(hub => {
69
+ for (let i = 1; i <= 5; i++) {
70
+ const targetId = (hub + i * 3) % pointCount
71
+ if (targetId !== hub) {
72
+ connections.push({ source: hub, target: targetId })
73
+ }
74
+ }
75
+ })
76
+
77
+ // Cross connections
78
+ for (let i = 0; i < pointCount / 2; i++) {
79
+ const oppositeId = i + Math.floor(pointCount / 2)
80
+ if (i % 3 === 0) {
81
+ connections.push({ source: i, target: oppositeId })
82
+ }
83
+ }
84
+
85
+ // Random connections
86
+ for (let i = 0; i < 30; i++) {
87
+ const source = Math.floor(Math.random() * pointCount)
88
+ const target = Math.floor(Math.random() * pointCount)
89
+ if (source !== target) {
90
+ const exists = connections.some(conn =>
91
+ (conn.source === source && conn.target === target) ||
92
+ (conn.source === target && conn.target === source)
93
+ )
94
+ if (!exists) {
95
+ connections.push({ source, target })
96
+ }
97
+ }
98
+ }
99
+
100
+ return connections
101
+ }
102
+
103
+ function generatePointPositions (points: Point[]): Float32Array {
104
+ const radius = 100
105
+ const positions = new Float32Array(points.length * 2)
106
+
107
+ points.forEach((point, i) => {
108
+ const angle = (i / points.length) * Math.PI * 2
109
+ const pointRadius = radius + (Math.random() - 0.5) * 20
110
+
111
+ positions[i * 2] = Math.cos(angle) * pointRadius
112
+ positions[i * 2 + 1] = Math.sin(angle) * pointRadius
113
+ })
114
+
115
+ return positions
116
+ }
117
+
118
+ function generatePointColors (points: Point[]): Float32Array {
119
+ const colors = new Float32Array(points.length * 4)
120
+
121
+ points.forEach((point, i) => {
122
+ const hue = (point.id / points.length) * 360
123
+ const [r, g, b] = hslToRgb(hue, 0.8, 0.6)
124
+
125
+ colors[i * 4] = r
126
+ colors[i * 4 + 1] = g
127
+ colors[i * 4 + 2] = b
128
+ colors[i * 4 + 3] = 1.0
129
+ })
130
+
131
+ return colors
132
+ }
133
+
134
+ function generatePointSizes (points: Point[]): Float32Array {
135
+ const sizes = new Float32Array(points.length)
136
+ sizes.fill(10)
137
+ return sizes
138
+ }
139
+
140
+ function generateLinkData (connections: Link[], points: Point[]): {
141
+ links: Float32Array;
142
+ linkColors: Float32Array;
143
+ linkWidths: Float32Array;
144
+ } {
145
+ const links = new Float32Array(connections.length * 2)
146
+ const linkColors = new Float32Array(connections.length * 4)
147
+ const linkWidths = new Float32Array(connections.length)
148
+
149
+ connections.forEach((connection, i) => {
150
+ links[i * 2] = connection.source
151
+ links[i * 2 + 1] = connection.target
152
+
153
+ // Color links based on average hue of connected points
154
+ const sourceHue = (connection.source / points.length) * 360
155
+ const targetHue = (connection.target / points.length) * 360
156
+
157
+ let avgHue
158
+ const hueDiff = Math.abs(targetHue - sourceHue)
159
+ if (hueDiff > 180) {
160
+ avgHue = ((sourceHue + targetHue + 360) / 2) % 360
161
+ } else {
162
+ avgHue = (sourceHue + targetHue) / 2
163
+ }
164
+
165
+ const [r, g, b] = hslToRgb(avgHue, 0.7, 0.5)
166
+
167
+ linkColors[i * 4] = r
168
+ linkColors[i * 4 + 1] = g
169
+ linkColors[i * 4 + 2] = b
170
+ linkColors[i * 4 + 3] = 0.9
171
+
172
+ linkWidths[i] = 2
173
+ })
174
+
175
+ return { links, linkColors, linkWidths }
176
+ }
177
+
178
+ export function generateData (pointCount: number = 500): NetworkData {
179
+ const points = generatePoints(pointCount)
180
+ const connections = generateConnections(points)
181
+
182
+ const pointPositions = generatePointPositions(points)
183
+ const pointColors = generatePointColors(points)
184
+ const pointSizes = generatePointSizes(points)
185
+
186
+ const { links, linkColors, linkWidths } = generateLinkData(connections, points)
187
+
188
+ return {
189
+ pointPositions,
190
+ pointColors,
191
+ pointSizes,
192
+ links,
193
+ linkColors,
194
+ linkWidths,
195
+ points,
196
+ connections,
197
+ }
198
+ }
@@ -0,0 +1,61 @@
1
+ import { Graph, GraphConfigInterface } from '@cosmos.gl/graph'
2
+ import { generateData } from './data-generator'
3
+ import './style.css'
4
+
5
+ export const linkHovering = (): { div: HTMLDivElement; graph: Graph } => {
6
+ const data = generateData()
7
+ const infoPanel = document.createElement('div')
8
+
9
+ // Create div container
10
+ const div = document.createElement('div')
11
+ div.style.height = '100vh'
12
+ div.style.width = '100%'
13
+ div.style.position = 'relative'
14
+
15
+ // Configure graph
16
+ const config: GraphConfigInterface = {
17
+ backgroundColor: '#2d313a',
18
+ scalePointsOnZoom: true,
19
+ linkArrows: false,
20
+ curvedLinks: true,
21
+ enableSimulation: false,
22
+ hoveredLinkWidthIncrease: 4,
23
+ attribution: 'visualized with <a href="https://cosmograph.app/" style="color: var(--cosmosgl-attribution-color);" target="_blank">Cosmograph</a>',
24
+
25
+ onLinkMouseOver: (linkIndex: number) => {
26
+ infoPanel.style.display = 'block'
27
+ infoPanel.textContent = `Link ${linkIndex}`
28
+ },
29
+
30
+ onLinkMouseOut: () => {
31
+ infoPanel.style.display = 'none'
32
+ },
33
+ }
34
+
35
+ // Create graph instance
36
+ const graph = new Graph(div, config)
37
+
38
+ // Set data
39
+ graph.setPointPositions(data.pointPositions)
40
+ graph.setPointColors(data.pointColors)
41
+ graph.setPointSizes(data.pointSizes)
42
+ graph.setLinks(data.links)
43
+ graph.setLinkColors(data.linkColors)
44
+ graph.setLinkWidths(data.linkWidths)
45
+
46
+ // Render graph
47
+ graph.zoom(0.9)
48
+ graph.render()
49
+
50
+ infoPanel.style.cssText = `
51
+ position: absolute;
52
+ top: 20px;
53
+ left: 20px;
54
+ color: white;
55
+ font-size: 14px;
56
+ display: none;
57
+ `
58
+ div.appendChild(infoPanel)
59
+
60
+ return { div, graph }
61
+ }
@@ -0,0 +1,73 @@
1
+ /* Enhanced styling for the link hovering demo */
2
+
3
+ .company-network {
4
+ background: radial-gradient(circle at 50% 50%, #1a1a2e 0%, #0a0a0a 100%);
5
+ position: relative;
6
+ overflow: hidden;
7
+ }
8
+
9
+ .info-panel {
10
+ backdrop-filter: blur(12px);
11
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
12
+ }
13
+
14
+ .info-panel:hover {
15
+ transform: translateY(-2px);
16
+ box-shadow: 0 20px 48px rgba(0, 0, 0, 0.6), 0 8px 24px rgba(0, 0, 0, 0.4);
17
+ }
18
+
19
+ .relationship-type-indicator {
20
+ display: inline-block;
21
+ width: 12px;
22
+ height: 12px;
23
+ border-radius: 50%;
24
+ margin-right: 8px;
25
+ }
26
+
27
+ .relationship-type-indicator.management {
28
+ background: linear-gradient(45deg, #FFAD6B, #FF8A65);
29
+ box-shadow: 0 2px 8px rgba(255, 173, 107, 0.4);
30
+ }
31
+
32
+ .relationship-type-indicator.collaboration {
33
+ background: linear-gradient(45deg, #59C0FF, #42A5F5);
34
+ box-shadow: 0 2px 8px rgba(89, 192, 255, 0.4);
35
+ }
36
+
37
+ .relationship-type-indicator.mentorship {
38
+ background: linear-gradient(45deg, #6BD17B, #66BB6A);
39
+ box-shadow: 0 2px 8px rgba(107, 209, 123, 0.4);
40
+ }
41
+
42
+ .relationship-type-indicator.friendship {
43
+ background: linear-gradient(45deg, #FF61AD, #EC407A);
44
+ box-shadow: 0 2px 8px rgba(255, 97, 173, 0.4);
45
+ }
46
+
47
+ .person-card {
48
+ background: rgba(255, 255, 255, 0.03);
49
+ border-radius: 8px;
50
+ padding: 12px;
51
+ margin: 6px 0;
52
+ border-left: 3px solid transparent;
53
+ border: 1px solid rgba(255, 255, 255, 0.06);
54
+ transition: all 0.2s ease;
55
+ }
56
+
57
+ .person-card.highlighted {
58
+ border-left-color: #59C0FF;
59
+ background: rgba(89, 192, 255, 0.08);
60
+ box-shadow: 0 4px 16px rgba(89, 192, 255, 0.15);
61
+ }
62
+
63
+ /* Modern color scheme variables */
64
+ :root {
65
+ --cosmos-orange: #FFAD6B;
66
+ --cosmos-blue: #59C0FF;
67
+ --cosmos-green: #6BD17B;
68
+ --cosmos-pink: #FF61AD;
69
+ --cosmos-gray: #C7CDD1;
70
+ --cosmos-dark-bg: rgba(16, 20, 40, 0.95);
71
+ --cosmos-card-bg: rgba(255, 255, 255, 0.03);
72
+ --cosmos-border: rgba(255, 255, 255, 0.08);
73
+ }
@@ -18,7 +18,8 @@ export const quickStart = (): { graph: Graph; div: HTMLDivElement} => {
18
18
  fitViewPadding: 0.3, // centers the graph width padding of ~30% of screen
19
19
  rescalePositions: true, // rescale positions
20
20
  enableDrag: true, // enable dragging points
21
- onClick: pointIndex => { console.log('Clicked point index: ', pointIndex) },
21
+ onPointClick: pointIndex => { console.log('Clicked point index: ', pointIndex) },
22
+ onBackgroundClick: () => { console.log('Clicked background') },
22
23
  attribution: 'visualized with <a href="https://cosmograph.app/" style="color: var(--cosmosgl-attribution-color);" target="_blank">Cosmograph</a>',
23
24
  /* ... */
24
25
  }
@@ -22,40 +22,38 @@ export const removePoints = (): { graph: Graph; div: HTMLDivElement} => {
22
22
 
23
23
  const graph = new Graph(graphDiv, {
24
24
  ...config,
25
- onClick: (i): void => {
26
- if (i !== undefined) {
27
- // Filter out the clicked point from positions array
28
- const currentPositions = graph.getPointPositions()
29
- const newPointPositions = currentPositions
30
- .filter((pos, posIndex) => {
31
- return (
32
- (posIndex % 2 === 0 && posIndex !== i * 2) ||
25
+ onPointClick: (i): void => {
26
+ // Filter out the clicked point from positions array
27
+ const currentPositions = graph.getPointPositions()
28
+ const newPointPositions = currentPositions
29
+ .filter((pos, posIndex) => {
30
+ return (
31
+ (posIndex % 2 === 0 && posIndex !== i * 2) ||
33
32
  (posIndex % 2 === 1 && posIndex !== i * 2 + 1)
34
- )
35
- })
36
-
37
- // Convert links array to source-target pairs for easier filtering
38
- const pairedNumbers = []
39
- for (let j = 0; j < graphLinks.length; j += 2) {
40
- const pair = [graphLinks[j], graphLinks[j + 1]]
41
- pairedNumbers.push(pair)
42
- }
43
-
44
- // Remove links connected to deleted point and adjust indices of remaining links
45
- graphLinks = (pairedNumbers
46
- .filter(
47
- ([sourceIndex, targetIndex]) => sourceIndex !== i && targetIndex !== i
48
33
  )
49
- .flat() as number[])
50
- .map((p) => {
51
- if (p > i) return p - 1
52
- else return p
53
- })
34
+ })
54
35
 
55
- graph.setPointPositions(new Float32Array(newPointPositions))
56
- graph.setLinks(new Float32Array(graphLinks))
57
- graph.render(isPaused ? 0 : undefined)
36
+ // Convert links array to source-target pairs for easier filtering
37
+ const pairedNumbers = []
38
+ for (let j = 0; j < graphLinks.length; j += 2) {
39
+ const pair = [graphLinks[j], graphLinks[j + 1]]
40
+ pairedNumbers.push(pair)
58
41
  }
42
+
43
+ // Remove links connected to deleted point and adjust indices of remaining links
44
+ graphLinks = (pairedNumbers
45
+ .filter(
46
+ ([sourceIndex, targetIndex]) => sourceIndex !== i && targetIndex !== i
47
+ )
48
+ .flat() as number[])
49
+ .map((p) => {
50
+ if (p > i) return p - 1
51
+ else return p
52
+ })
53
+
54
+ graph.setPointPositions(new Float32Array(newPointPositions))
55
+ graph.setLinks(new Float32Array(graphLinks))
56
+ graph.render(isPaused ? 0 : undefined)
59
57
  console.log('Clicked node: ', i)
60
58
  },
61
59
  })
@@ -6,6 +6,7 @@ import { quickStart } from './beginners/quick-start'
6
6
  import { basicSetUp } from './beginners/basic-set-up'
7
7
  import { pointLabels } from './beginners/point-labels'
8
8
  import { removePoints } from './beginners/remove-points'
9
+ import { linkHovering } from './beginners/link-hovering'
9
10
 
10
11
  import quickStartStoryRaw from './beginners/quick-start?raw'
11
12
  import basicSetUpStoryRaw from './beginners/basic-set-up/index?raw'
@@ -19,6 +20,9 @@ import removePointsStoryRaw from './beginners/remove-points/index?raw'
19
20
  import removePointsStoryCssRaw from './beginners/remove-points/style.css?raw'
20
21
  import removePointsStoryConfigRaw from './beginners/remove-points/config.ts?raw'
21
22
  import removePointsStoryDataGenRaw from './beginners/remove-points/data-gen.ts?raw'
23
+ import linkHoveringStoryRaw from './beginners/link-hovering/index?raw'
24
+ import linkHoveringStoryDataGenRaw from './beginners/link-hovering/data-generator.ts?raw'
25
+ import linkHoveringStoryCssRaw from './beginners/link-hovering/style.css?raw'
22
26
 
23
27
  // More on how to set up stories at: https://storybook.js.org/docs/writing-stories#default-export
24
28
  const meta: Meta<CosmosStoryProps> = {
@@ -96,5 +100,18 @@ export const RemovePoints: Story = {
96
100
  ],
97
101
  },
98
102
  }
103
+
104
+ export const LinkHovering: Story = {
105
+ ...createStory(linkHovering),
106
+ name: 'Link Hovering',
107
+ parameters: {
108
+ sourceCode: [
109
+ { name: 'Story', code: linkHoveringStoryRaw },
110
+ { name: 'data-generator.ts', code: linkHoveringStoryDataGenRaw },
111
+ { name: 'style.css', code: linkHoveringStoryCssRaw },
112
+ ],
113
+ },
114
+ }
115
+
99
116
  // eslint-disable-next-line import/no-default-export
100
117
  export default meta
@@ -17,10 +17,8 @@ export const polygonSelection = (): {div: HTMLDivElement; graph: Graph; destroy:
17
17
  pointSize: 8,
18
18
  backgroundColor: '#1a1a2e',
19
19
  pointGreyoutOpacity: 0.2,
20
- onClick: (index: number | undefined): void => {
21
- if (index === undefined) {
22
- graph.unselectPoints()
23
- }
20
+ onBackgroundClick: (): void => {
21
+ graph.unselectPoints()
24
22
  },
25
23
  })
26
24