@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.
- package/.eslintrc +61 -0
- package/CHARTER.md +69 -0
- package/GOVERNANCE.md +21 -0
- package/dist/config.d.ts +69 -0
- package/dist/index.d.ts +62 -21
- package/dist/index.js +5672 -5188
- package/dist/index.js.map +1 -1
- package/dist/index.min.js +272 -86
- package/dist/index.min.js.map +1 -1
- package/dist/modules/GraphData/index.d.ts +18 -2
- package/dist/modules/Lines/index.d.ts +8 -0
- package/dist/modules/Points/atlas-utils.d.ts +24 -0
- package/dist/modules/Points/index.d.ts +21 -2
- package/dist/modules/Store/index.d.ts +20 -3
- package/dist/modules/core-module.d.ts +1 -0
- package/dist/stories/beginners/link-hovering/data-generator.d.ts +19 -0
- package/dist/stories/beginners/link-hovering/index.d.ts +5 -0
- package/dist/stories/beginners.stories.d.ts +1 -0
- package/dist/stories/create-story.d.ts +5 -1
- package/dist/stories/shapes/image-example/index.d.ts +5 -0
- package/dist/stories/shapes.stories.d.ts +1 -0
- package/dist/variables.d.ts +5 -2
- package/package.json +4 -4
- package/src/config.ts +87 -2
- package/src/declaration.d.ts +5 -0
- package/src/index.ts +270 -98
- package/src/modules/GraphData/index.ts +68 -6
- package/src/modules/Lines/draw-curve-line.frag +12 -1
- package/src/modules/Lines/draw-curve-line.vert +29 -2
- package/src/modules/Lines/hovered-line-index.frag +27 -0
- package/src/modules/Lines/hovered-line-index.vert +8 -0
- package/src/modules/Lines/index.ts +112 -2
- package/src/modules/Points/atlas-utils.ts +137 -0
- package/src/modules/Points/draw-highlighted.vert +3 -3
- package/src/modules/Points/draw-points.frag +106 -14
- package/src/modules/Points/draw-points.vert +51 -25
- package/src/modules/Points/find-points-on-area-selection.frag +6 -5
- package/src/modules/Points/index.ts +121 -13
- package/src/modules/Store/index.ts +44 -5
- package/src/modules/core-module.ts +1 -0
- package/src/stories/1. welcome.mdx +2 -1
- package/src/stories/2. configuration.mdx +10 -1
- package/src/stories/3. api-reference.mdx +61 -5
- package/src/stories/beginners/basic-set-up/index.ts +20 -10
- package/src/stories/beginners/link-hovering/data-generator.ts +198 -0
- package/src/stories/beginners/link-hovering/index.ts +61 -0
- package/src/stories/beginners/link-hovering/style.css +73 -0
- package/src/stories/beginners/quick-start.ts +2 -1
- package/src/stories/beginners/remove-points/index.ts +28 -30
- package/src/stories/beginners.stories.ts +17 -0
- package/src/stories/clusters/polygon-selection/index.ts +2 -4
- package/src/stories/create-story.ts +32 -5
- package/src/stories/shapes/image-example/icons/box.png +0 -0
- package/src/stories/shapes/image-example/icons/lego.png +0 -0
- package/src/stories/shapes/image-example/icons/s.png +0 -0
- package/src/stories/shapes/image-example/icons/swift.png +0 -0
- package/src/stories/shapes/image-example/icons/toolbox.png +0 -0
- package/src/stories/shapes/image-example/index.ts +238 -0
- package/src/stories/shapes.stories.ts +12 -0
- 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
|
-
|
|
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
|
-
|
|
|
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-
|
|
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="
|
|
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
|
-
* **`
|
|
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="
|
|
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
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
|
74
|
+
function unpause (): void {
|
|
75
75
|
isPaused = false
|
|
76
76
|
pauseButton.textContent = 'Pause'
|
|
77
|
-
graph
|
|
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)
|
|
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
|
-
|
|
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
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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
|
-
|
|
50
|
-
.map((p) => {
|
|
51
|
-
if (p > i) return p - 1
|
|
52
|
-
else return p
|
|
53
|
-
})
|
|
34
|
+
})
|
|
54
35
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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
|
-
|
|
21
|
-
|
|
22
|
-
graph.unselectPoints()
|
|
23
|
-
}
|
|
20
|
+
onBackgroundClick: (): void => {
|
|
21
|
+
graph.unselectPoints()
|
|
24
22
|
},
|
|
25
23
|
})
|
|
26
24
|
|