@cosmos.gl/graph 2.4.0 → 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 (36) hide show
  1. package/dist/config.d.ts +69 -0
  2. package/dist/index.d.ts +16 -6
  3. package/dist/index.js +4328 -4129
  4. package/dist/index.js.map +1 -1
  5. package/dist/index.min.js +113 -45
  6. package/dist/index.min.js.map +1 -1
  7. package/dist/modules/Lines/index.d.ts +8 -0
  8. package/dist/modules/Store/index.d.ts +14 -2
  9. package/dist/modules/core-module.d.ts +1 -0
  10. package/dist/stories/beginners/link-hovering/data-generator.d.ts +19 -0
  11. package/dist/stories/beginners/link-hovering/index.d.ts +5 -0
  12. package/dist/stories/beginners.stories.d.ts +1 -0
  13. package/dist/variables.d.ts +5 -2
  14. package/package.json +1 -1
  15. package/src/config.ts +86 -2
  16. package/src/index.ts +151 -31
  17. package/src/modules/Lines/draw-curve-line.frag +12 -1
  18. package/src/modules/Lines/draw-curve-line.vert +29 -2
  19. package/src/modules/Lines/hovered-line-index.frag +27 -0
  20. package/src/modules/Lines/hovered-line-index.vert +8 -0
  21. package/src/modules/Lines/index.ts +112 -2
  22. package/src/modules/Store/index.ts +33 -2
  23. package/src/modules/core-module.ts +1 -0
  24. package/src/stories/1. welcome.mdx +2 -1
  25. package/src/stories/2. configuration.mdx +10 -1
  26. package/src/stories/3. api-reference.mdx +13 -4
  27. package/src/stories/beginners/basic-set-up/index.ts +20 -10
  28. package/src/stories/beginners/link-hovering/data-generator.ts +198 -0
  29. package/src/stories/beginners/link-hovering/index.ts +61 -0
  30. package/src/stories/beginners/link-hovering/style.css +73 -0
  31. package/src/stories/beginners/quick-start.ts +2 -1
  32. package/src/stories/beginners/remove-points/index.ts +28 -30
  33. package/src/stories/beginners.stories.ts +17 -0
  34. package/src/stories/clusters/polygon-selection/index.ts +2 -4
  35. package/src/stories/shapes/image-example/index.ts +7 -8
  36. package/src/variables.ts +5 -2
@@ -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
 
@@ -194,14 +194,13 @@ export const imageExample = async (): Promise<{div: HTMLDivElement; graph: Graph
194
194
  renderHoveredPointRing: true,
195
195
 
196
196
  // Add click handler for point and background selection
197
- onClick: (pointIndex: number | undefined): void => {
198
- if (pointIndex !== undefined) {
199
- // Use built-in functionality to select the clicked point and its neighbors
200
- graph.selectPointByIndex(pointIndex, true)
201
- } else {
202
- // Clear selection when clicking on background
203
- graph.unselectPoints()
204
- }
197
+ onPointClick: (pointIndex: number): void => {
198
+ // Use built-in functionality to select the clicked point and its neighbors
199
+ graph.selectPointByIndex(pointIndex, true)
200
+ },
201
+ onBackgroundClick: (): void => {
202
+ // Clear selection when clicking on background
203
+ graph.unselectPoints()
205
204
  },
206
205
  })
207
206
 
package/src/variables.ts CHANGED
@@ -14,18 +14,21 @@ export const defaultConfigValues = {
14
14
  spaceSize: 8192,
15
15
  pointSizeScale: 1,
16
16
  linkWidthScale: 1,
17
- arrowSizeScale: 1,
17
+ linkArrowsSizeScale: 1,
18
18
  renderLinks: true,
19
19
  curvedLinks: false,
20
20
  curvedLinkSegments: 19,
21
21
  curvedLinkWeight: 0.8,
22
22
  curvedLinkControlPointDistance: 0.5,
23
- arrowLinks: false,
23
+ linkArrows: false,
24
24
  linkVisibilityDistanceRange: [50, 150],
25
25
  linkVisibilityMinTransparency: 0.25,
26
26
  hoveredPointCursor: 'auto',
27
+ hoveredLinkCursor: 'auto',
27
28
  renderHoveredPointRing: false,
28
29
  hoveredPointRingColor: 'white',
30
+ hoveredLinkColor: undefined,
31
+ hoveredLinkWidthIncrease: 5,
29
32
  focusedPointRingColor: 'white',
30
33
  focusedPointIndex: undefined,
31
34
  useClassicQuadtree: false,