@cosmos.gl/graph 2.6.2-rc.0 → 2.7.0-beta.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 +147 -0
- package/.github/SECURITY.md +13 -0
- package/.github/dco.yml +4 -0
- package/.github/workflows/github_pages.yml +54 -0
- package/.storybook/main.ts +26 -0
- package/.storybook/manager-head.html +1 -0
- package/.storybook/manager.ts +14 -0
- package/.storybook/preview.ts +29 -0
- package/.storybook/style.css +3 -0
- package/CHARTER.md +69 -0
- package/CODE_OF_CONDUCT.md +178 -0
- package/CONTRIBUTING.md +22 -0
- package/GOVERNANCE.md +21 -0
- package/cosmos-2-0-migration-notes.md +98 -0
- package/cosmos_awesome.md +96 -0
- package/dist/config.d.ts +5 -9
- package/dist/graph/utils/error-message.d.ts +1 -1
- package/dist/helper.d.ts +39 -2
- package/dist/index-FUIgayhu.js +19827 -0
- package/dist/index-FUIgayhu.js.map +1 -0
- package/dist/index.d.ts +17 -64
- package/dist/index.js +14 -14654
- package/dist/index.js.map +1 -1
- package/dist/index.min.js +1062 -475
- package/dist/index.min.js.map +1 -1
- package/dist/modules/Clusters/index.d.ts +11 -3
- package/dist/modules/ForceCenter/index.d.ts +10 -3
- package/dist/modules/ForceGravity/index.d.ts +5 -1
- package/dist/modules/ForceLink/index.d.ts +8 -5
- package/dist/modules/ForceManyBody/index.d.ts +16 -7
- package/dist/modules/ForceMouse/index.d.ts +5 -1
- package/dist/modules/GraphData/index.d.ts +0 -1
- package/dist/modules/Lines/index.d.ts +11 -5
- package/dist/modules/Points/index.d.ts +31 -13
- package/dist/modules/Store/index.d.ts +93 -0
- package/dist/modules/core-module.d.ts +3 -3
- package/dist/stories/beginners/basic-set-up/data-gen.d.ts +4 -0
- package/dist/stories/beginners/basic-set-up/index.d.ts +6 -0
- package/dist/stories/beginners/link-hovering/data-generator.d.ts +19 -0
- package/dist/stories/beginners/link-hovering/index.d.ts +6 -0
- package/dist/stories/beginners/point-labels/data.d.ts +13 -0
- package/dist/stories/beginners/point-labels/index.d.ts +10 -0
- package/dist/stories/beginners/point-labels/labels.d.ts +8 -0
- package/dist/stories/beginners/quick-start.d.ts +6 -0
- package/dist/stories/beginners/remove-points/config.d.ts +2 -0
- package/dist/stories/beginners/remove-points/data-gen.d.ts +4 -0
- package/dist/stories/beginners/remove-points/index.d.ts +6 -0
- package/dist/stories/beginners.stories.d.ts +10 -0
- package/dist/stories/clusters/polygon-selection/index.d.ts +6 -0
- package/dist/stories/clusters/polygon-selection/polygon.d.ts +20 -0
- package/dist/stories/clusters/radial.d.ts +6 -0
- package/dist/stories/clusters/with-labels.d.ts +6 -0
- package/dist/stories/clusters/worm.d.ts +6 -0
- package/dist/stories/clusters.stories.d.ts +9 -0
- package/dist/stories/create-cluster-labels.d.ts +4 -0
- package/dist/stories/create-cosmos.d.ts +17 -0
- package/dist/stories/create-story.d.ts +16 -0
- package/dist/stories/experiments/full-mesh.d.ts +6 -0
- package/dist/stories/experiments/mesh-with-holes.d.ts +6 -0
- package/dist/stories/experiments.stories.d.ts +7 -0
- package/dist/stories/generate-mesh-data.d.ts +12 -0
- package/dist/stories/geospatial/moscow-metro-stations/index.d.ts +16 -0
- package/dist/stories/geospatial/moscow-metro-stations/moscow-metro-coords.d.ts +1 -0
- package/dist/stories/geospatial/moscow-metro-stations/point-colors.d.ts +1 -0
- package/dist/stories/geospatial.stories.d.ts +6 -0
- package/dist/stories/shapes/all-shapes/index.d.ts +6 -0
- package/dist/stories/shapes/image-example/index.d.ts +6 -0
- package/dist/stories/shapes.stories.d.ts +7 -0
- package/dist/stories/test-luma-migration.d.ts +6 -0
- package/dist/stories/test.stories.d.ts +6 -0
- package/dist/webgl-device-B9ewDj5L.js +3923 -0
- package/dist/webgl-device-B9ewDj5L.js.map +1 -0
- package/logo.svg +3 -0
- package/package.json +5 -7
- package/rollup.config.js +70 -0
- package/src/config.ts +728 -0
- package/src/declaration.d.ts +12 -0
- package/src/graph/utils/error-message.ts +23 -0
- package/src/helper.ts +113 -0
- package/src/index.ts +1769 -0
- package/src/modules/Clusters/calculate-centermass.frag +12 -0
- package/src/modules/Clusters/calculate-centermass.vert +38 -0
- package/src/modules/Clusters/force-cluster.frag +55 -0
- package/src/modules/Clusters/index.ts +578 -0
- package/src/modules/Drag/index.ts +33 -0
- package/src/modules/FPSMonitor/css.ts +53 -0
- package/src/modules/FPSMonitor/index.ts +28 -0
- package/src/modules/ForceCenter/calculate-centermass.frag +9 -0
- package/src/modules/ForceCenter/calculate-centermass.vert +26 -0
- package/src/modules/ForceCenter/force-center.frag +37 -0
- package/src/modules/ForceCenter/index.ts +284 -0
- package/src/modules/ForceGravity/force-gravity.frag +40 -0
- package/src/modules/ForceGravity/index.ts +107 -0
- package/src/modules/ForceLink/force-spring.ts +89 -0
- package/src/modules/ForceLink/index.ts +293 -0
- package/src/modules/ForceManyBody/calculate-level.frag +9 -0
- package/src/modules/ForceManyBody/calculate-level.vert +37 -0
- package/src/modules/ForceManyBody/force-centermass.frag +61 -0
- package/src/modules/ForceManyBody/force-level.frag +138 -0
- package/src/modules/ForceManyBody/index.ts +525 -0
- package/src/modules/ForceManyBody/quadtree-frag-shader.ts +89 -0
- package/src/modules/ForceManyBodyQuadtree/calculate-level.frag +9 -0
- package/src/modules/ForceManyBodyQuadtree/calculate-level.vert +25 -0
- package/src/modules/ForceManyBodyQuadtree/index.ts +157 -0
- package/src/modules/ForceManyBodyQuadtree/quadtree-frag-shader.ts +93 -0
- package/src/modules/ForceMouse/force-mouse.frag +35 -0
- package/src/modules/ForceMouse/index.ts +102 -0
- package/src/modules/GraphData/index.ts +383 -0
- package/src/modules/Lines/draw-curve-line.frag +59 -0
- package/src/modules/Lines/draw-curve-line.vert +248 -0
- package/src/modules/Lines/geometry.ts +18 -0
- package/src/modules/Lines/hovered-line-index.frag +43 -0
- package/src/modules/Lines/hovered-line-index.vert +13 -0
- package/src/modules/Lines/index.ts +661 -0
- package/src/modules/Points/atlas-utils.ts +137 -0
- package/src/modules/Points/drag-point.frag +34 -0
- package/src/modules/Points/draw-highlighted.frag +44 -0
- package/src/modules/Points/draw-highlighted.vert +145 -0
- package/src/modules/Points/draw-points.frag +259 -0
- package/src/modules/Points/draw-points.vert +203 -0
- package/src/modules/Points/fill-sampled-points.frag +12 -0
- package/src/modules/Points/fill-sampled-points.vert +51 -0
- package/src/modules/Points/find-hovered-point.frag +15 -0
- package/src/modules/Points/find-hovered-point.vert +90 -0
- package/src/modules/Points/find-points-on-area-selection.frag +88 -0
- package/src/modules/Points/find-points-on-polygon-selection.frag +89 -0
- package/src/modules/Points/index.ts +2292 -0
- package/src/modules/Points/track-positions.frag +30 -0
- package/src/modules/Points/update-position.frag +39 -0
- package/src/modules/Shared/buffer.ts +39 -0
- package/src/modules/Shared/clear.frag +10 -0
- package/src/modules/Shared/quad.vert +13 -0
- package/src/modules/Store/index.ts +283 -0
- package/src/modules/Zoom/index.ts +148 -0
- package/src/modules/core-module.ts +28 -0
- package/src/stories/1. welcome.mdx +75 -0
- package/src/stories/2. configuration.mdx +111 -0
- package/src/stories/3. api-reference.mdx +591 -0
- package/src/stories/beginners/basic-set-up/data-gen.ts +33 -0
- package/src/stories/beginners/basic-set-up/index.ts +167 -0
- package/src/stories/beginners/basic-set-up/style.css +35 -0
- package/src/stories/beginners/link-hovering/data-generator.ts +198 -0
- package/src/stories/beginners/link-hovering/index.ts +65 -0
- package/src/stories/beginners/link-hovering/style.css +73 -0
- package/src/stories/beginners/point-labels/data.ts +73 -0
- package/src/stories/beginners/point-labels/index.ts +69 -0
- package/src/stories/beginners/point-labels/labels.ts +46 -0
- package/src/stories/beginners/point-labels/style.css +16 -0
- package/src/stories/beginners/quick-start.ts +54 -0
- package/src/stories/beginners/remove-points/config.ts +25 -0
- package/src/stories/beginners/remove-points/data-gen.ts +30 -0
- package/src/stories/beginners/remove-points/index.ts +96 -0
- package/src/stories/beginners/remove-points/style.css +31 -0
- package/src/stories/beginners.stories.ts +130 -0
- package/src/stories/clusters/polygon-selection/index.ts +52 -0
- package/src/stories/clusters/polygon-selection/polygon.ts +143 -0
- package/src/stories/clusters/polygon-selection/style.css +8 -0
- package/src/stories/clusters/radial.ts +24 -0
- package/src/stories/clusters/with-labels.ts +54 -0
- package/src/stories/clusters/worm.ts +40 -0
- package/src/stories/clusters.stories.ts +77 -0
- package/src/stories/create-cluster-labels.ts +50 -0
- package/src/stories/create-cosmos.ts +72 -0
- package/src/stories/create-story.ts +51 -0
- package/src/stories/experiments/full-mesh.ts +13 -0
- package/src/stories/experiments/mesh-with-holes.ts +13 -0
- package/src/stories/experiments.stories.ts +43 -0
- package/src/stories/generate-mesh-data.ts +125 -0
- package/src/stories/geospatial/moscow-metro-stations/index.ts +66 -0
- package/src/stories/geospatial/moscow-metro-stations/moscow-metro-coords.ts +1 -0
- package/src/stories/geospatial/moscow-metro-stations/point-colors.ts +46 -0
- package/src/stories/geospatial/moscow-metro-stations/style.css +30 -0
- package/src/stories/geospatial.stories.ts +30 -0
- package/src/stories/shapes/all-shapes/index.ts +73 -0
- 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 +246 -0
- package/src/stories/shapes.stories.ts +37 -0
- package/src/stories/test-luma-migration.ts +195 -0
- package/src/stories/test.stories.ts +25 -0
- package/src/variables.ts +68 -0
- package/tsconfig.json +41 -0
- package/vite.config.ts +52 -0
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { Graph } from '@cosmos.gl/graph'
|
|
2
|
+
import { generateData } from './data-gen'
|
|
3
|
+
import './style.css'
|
|
4
|
+
|
|
5
|
+
export const basicSetUp = (): { graph: Graph; div: HTMLDivElement; destroy?: () => void } => {
|
|
6
|
+
const div = document.createElement('div')
|
|
7
|
+
div.className = 'app'
|
|
8
|
+
|
|
9
|
+
const graphDiv = document.createElement('div')
|
|
10
|
+
graphDiv.className = 'graph'
|
|
11
|
+
div.appendChild(graphDiv)
|
|
12
|
+
|
|
13
|
+
const actionsDiv = document.createElement('div')
|
|
14
|
+
actionsDiv.className = 'actions'
|
|
15
|
+
div.appendChild(actionsDiv)
|
|
16
|
+
|
|
17
|
+
const actionsHeader = document.createElement('div')
|
|
18
|
+
actionsHeader.className = 'actions-header'
|
|
19
|
+
actionsHeader.textContent = 'Actions'
|
|
20
|
+
actionsDiv.appendChild(actionsHeader)
|
|
21
|
+
|
|
22
|
+
const graph = new Graph(graphDiv, {
|
|
23
|
+
spaceSize: 4096,
|
|
24
|
+
backgroundColor: '#2d313a',
|
|
25
|
+
pointSize: 4,
|
|
26
|
+
pointColor: '#4B5BBF',
|
|
27
|
+
linkWidth: 0.6,
|
|
28
|
+
scalePointsOnZoom: true,
|
|
29
|
+
linkColor: '#5F74C2',
|
|
30
|
+
linkArrows: false,
|
|
31
|
+
linkGreyoutOpacity: 0,
|
|
32
|
+
curvedLinks: true,
|
|
33
|
+
renderHoveredPointRing: true,
|
|
34
|
+
hoveredPointRingColor: '#4B5BBF',
|
|
35
|
+
enableDrag: true,
|
|
36
|
+
simulationLinkDistance: 1,
|
|
37
|
+
simulationLinkSpring: 2,
|
|
38
|
+
simulationRepulsion: 0.2,
|
|
39
|
+
simulationGravity: 0.1,
|
|
40
|
+
simulationDecay: 100000,
|
|
41
|
+
onPointClick: (index: number): void => {
|
|
42
|
+
graph.selectPointByIndex(index)
|
|
43
|
+
graph.zoomToPointByIndex(index)
|
|
44
|
+
console.log('Clicked point index: ', index)
|
|
45
|
+
},
|
|
46
|
+
onBackgroundClick: (): void => {
|
|
47
|
+
graph.unselectPoints()
|
|
48
|
+
console.log('Clicked background')
|
|
49
|
+
},
|
|
50
|
+
attribution: 'visualized with <a href="https://cosmograph.app/" style="color: var(--cosmosgl-attribution-color);" target="_blank">Cosmograph</a>',
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
const { pointPositions, links } = generateData()
|
|
54
|
+
graph.setPointPositions(pointPositions)
|
|
55
|
+
graph.setLinks(links)
|
|
56
|
+
|
|
57
|
+
graph.zoom(0.9)
|
|
58
|
+
graph.render()
|
|
59
|
+
|
|
60
|
+
/* ~ Demo Actions ~ */
|
|
61
|
+
// Start / Pause
|
|
62
|
+
let isPaused = false
|
|
63
|
+
const pauseButton = document.createElement('div')
|
|
64
|
+
pauseButton.className = 'action'
|
|
65
|
+
pauseButton.textContent = 'Pause'
|
|
66
|
+
actionsDiv.appendChild(pauseButton)
|
|
67
|
+
|
|
68
|
+
function pause (): void {
|
|
69
|
+
isPaused = true
|
|
70
|
+
pauseButton.textContent = 'Start'
|
|
71
|
+
graph.pause()
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function unpause (): void {
|
|
75
|
+
isPaused = false
|
|
76
|
+
pauseButton.textContent = 'Pause'
|
|
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
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function togglePause (): void {
|
|
86
|
+
if (isPaused) unpause()
|
|
87
|
+
else pause()
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
pauseButton.addEventListener('click', togglePause)
|
|
91
|
+
graph.setConfig({
|
|
92
|
+
onSimulationEnd: (): void => {
|
|
93
|
+
pause()
|
|
94
|
+
},
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
// Zoom and Select
|
|
98
|
+
function getRandomPointIndex (): number {
|
|
99
|
+
return Math.floor((Math.random() * pointPositions.length) / 2)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function getRandomInRange ([min, max]: [number, number]): number {
|
|
103
|
+
return Math.random() * (max - min) + min
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function fitView (): void {
|
|
107
|
+
graph.fitView()
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function zoomIn (): void {
|
|
111
|
+
const pointIndex = getRandomPointIndex()
|
|
112
|
+
graph.zoomToPointByIndex(pointIndex)
|
|
113
|
+
graph.selectPointByIndex(pointIndex)
|
|
114
|
+
pause()
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function selectPoint (): void {
|
|
118
|
+
const pointIndex = getRandomPointIndex()
|
|
119
|
+
graph.selectPointByIndex(pointIndex)
|
|
120
|
+
graph.fitView()
|
|
121
|
+
pause()
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function selectPointsInArea (): void {
|
|
125
|
+
const w = div.clientWidth
|
|
126
|
+
const h = div.clientHeight
|
|
127
|
+
const left = getRandomInRange([w / 4, w / 2])
|
|
128
|
+
const right = getRandomInRange([left, (w * 3) / 4])
|
|
129
|
+
const top = getRandomInRange([h / 4, h / 2])
|
|
130
|
+
const bottom = getRandomInRange([top, (h * 3) / 4])
|
|
131
|
+
pause()
|
|
132
|
+
graph.selectPointsInRect([
|
|
133
|
+
[left, top],
|
|
134
|
+
[right, bottom],
|
|
135
|
+
])
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const fitViewButton = document.createElement('div')
|
|
139
|
+
fitViewButton.className = 'action'
|
|
140
|
+
fitViewButton.textContent = 'Fit View'
|
|
141
|
+
fitViewButton.addEventListener('click', fitView)
|
|
142
|
+
actionsDiv.appendChild(fitViewButton)
|
|
143
|
+
|
|
144
|
+
const zoomButton = document.createElement('div')
|
|
145
|
+
zoomButton.className = 'action'
|
|
146
|
+
zoomButton.textContent = 'Zoom to a point'
|
|
147
|
+
zoomButton.addEventListener('click', zoomIn)
|
|
148
|
+
actionsDiv.appendChild(zoomButton)
|
|
149
|
+
|
|
150
|
+
const selectPointButton = document.createElement('div')
|
|
151
|
+
selectPointButton.className = 'action'
|
|
152
|
+
selectPointButton.textContent = 'Select a point'
|
|
153
|
+
selectPointButton.addEventListener('click', selectPoint)
|
|
154
|
+
actionsDiv.appendChild(selectPointButton)
|
|
155
|
+
|
|
156
|
+
const selectPointsInAreaButton = document.createElement('div')
|
|
157
|
+
selectPointsInAreaButton.className = 'action'
|
|
158
|
+
selectPointsInAreaButton.textContent = 'Select points in a rectangular area'
|
|
159
|
+
selectPointsInAreaButton.addEventListener('click', selectPointsInArea)
|
|
160
|
+
actionsDiv.appendChild(selectPointsInAreaButton)
|
|
161
|
+
|
|
162
|
+
const destroy = (): void => {
|
|
163
|
+
graph.destroy()
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return { div, graph, destroy }
|
|
167
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
body {
|
|
2
|
+
font-family: "Nunito Sans", -apple-system, ".SFNSText-Regular", "San Francisco", BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Helvetica, Arial, sans-serif;
|
|
3
|
+
margin: 0px;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
.app {
|
|
7
|
+
position: absolute;
|
|
8
|
+
width: 100%;
|
|
9
|
+
height: 100%;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.graph {
|
|
13
|
+
width: 100%;
|
|
14
|
+
height: 100vh;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.actions {
|
|
18
|
+
position: absolute;
|
|
19
|
+
top: 10px;
|
|
20
|
+
left: 10px;
|
|
21
|
+
color: #ccc;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.actions-header {
|
|
25
|
+
font-weight: bold;
|
|
26
|
+
margin-bottom: 2px;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.action {
|
|
30
|
+
margin-left: 2px;
|
|
31
|
+
font-size: 10pt;
|
|
32
|
+
text-decoration: underline;
|
|
33
|
+
cursor: pointer;
|
|
34
|
+
user-select: none;
|
|
35
|
+
}
|
|
@@ -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,65 @@
|
|
|
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; destroy?: () => void } => {
|
|
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
|
+
const destroy = (): void => {
|
|
61
|
+
graph.destroy()
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return { div, graph, destroy }
|
|
65
|
+
}
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
function getRandom (min: number, max: number): number {
|
|
2
|
+
return Math.random() * (max - min) + min
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export const pointsToShowLabelsFor = [
|
|
6
|
+
'Drury Lane Theatre',
|
|
7
|
+
"King's Theatre",
|
|
8
|
+
"Lincoln's Inn Fields",
|
|
9
|
+
"Goodman's Fields",
|
|
10
|
+
'Haymarket Theatre',
|
|
11
|
+
'Covent Garden',
|
|
12
|
+
'Bartholomew Fair',
|
|
13
|
+
'Southwark Fair',
|
|
14
|
+
'Pantheon, Oxford Street',
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
export const processPerformances = (performances: {
|
|
18
|
+
theaterCode: string;
|
|
19
|
+
performanceTitle: string;
|
|
20
|
+
theaterName: string;
|
|
21
|
+
}[]): { pointPositions: Float32Array; pointColors: Float32Array; pointSizes: Float32Array; pointLabelToIndex: Map<string, number>; pointIndexToLabel: Map<number, string>; links: Float32Array } => {
|
|
22
|
+
const pointLabelToIndex = new Map<string, number>()
|
|
23
|
+
const pointIndexToLabel = new Map<number, string>()
|
|
24
|
+
const pointPositions: number[] = []
|
|
25
|
+
const pointColors: number[] = []
|
|
26
|
+
const pointSizes: number[] = []
|
|
27
|
+
|
|
28
|
+
Array.from(
|
|
29
|
+
new Set([
|
|
30
|
+
...performances.map((p) => `P:${p.performanceTitle}`),
|
|
31
|
+
...performances.map((p) => p.theaterName),
|
|
32
|
+
])
|
|
33
|
+
).forEach((point, index) => {
|
|
34
|
+
pointLabelToIndex.set(point, index)
|
|
35
|
+
pointIndexToLabel.set(index, point)
|
|
36
|
+
pointPositions.push(4096 * getRandom(0.495, 0.505)) // x
|
|
37
|
+
pointPositions.push(4096 * getRandom(0.495, 0.505)) // y
|
|
38
|
+
if (point.indexOf('P:') === 0) {
|
|
39
|
+
// #4B5BBF
|
|
40
|
+
pointColors.push(75 / 256) // r
|
|
41
|
+
pointColors.push(91 / 256) // g
|
|
42
|
+
pointColors.push(191 / 256) // b
|
|
43
|
+
pointColors.push(1) // a
|
|
44
|
+
} else {
|
|
45
|
+
// #ED69B4
|
|
46
|
+
pointColors.push(237 / 256) // r
|
|
47
|
+
pointColors.push(105 / 256) // g
|
|
48
|
+
pointColors.push(180 / 256) // b
|
|
49
|
+
pointColors.push(1) // a
|
|
50
|
+
}
|
|
51
|
+
pointSizes.push(pointsToShowLabelsFor.includes(point) ? 8 : 2)
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
const links = performances
|
|
55
|
+
.map((p) => {
|
|
56
|
+
const theaterIndex = pointLabelToIndex.get(p.theaterName)
|
|
57
|
+
const performanceIndex = pointLabelToIndex.get(`P:${p.performanceTitle}`)
|
|
58
|
+
if (theaterIndex === undefined || performanceIndex === undefined) {
|
|
59
|
+
return []
|
|
60
|
+
}
|
|
61
|
+
return [theaterIndex, performanceIndex]
|
|
62
|
+
})
|
|
63
|
+
.flat()
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
pointPositions: new Float32Array(pointPositions),
|
|
67
|
+
pointColors: new Float32Array(pointColors),
|
|
68
|
+
pointSizes: new Float32Array(pointSizes),
|
|
69
|
+
pointLabelToIndex,
|
|
70
|
+
pointIndexToLabel,
|
|
71
|
+
links: new Float32Array(links),
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { Graph } from '@cosmos.gl/graph'
|
|
2
|
+
import { CosmosLabels } from './labels'
|
|
3
|
+
import { processPerformances, pointsToShowLabelsFor } from './data'
|
|
4
|
+
import './style.css'
|
|
5
|
+
|
|
6
|
+
// Load data from Github Gist and use it as `performances` argument for `PointLabelsStory`:
|
|
7
|
+
// const data = await fetch('https://gist.githubusercontent.com/Stukova/e6c4c7777e0166431a983999213f10c8/raw/performances.json')
|
|
8
|
+
// const performances = await data.json()
|
|
9
|
+
|
|
10
|
+
export const pointLabels = (
|
|
11
|
+
performances: {
|
|
12
|
+
theaterCode: string;
|
|
13
|
+
performanceTitle: string;
|
|
14
|
+
theaterName: string;
|
|
15
|
+
}[]
|
|
16
|
+
): { graph: Graph; div: HTMLDivElement; destroy?: () => void } => {
|
|
17
|
+
const { pointPositions, pointColors, pointSizes, links, pointIndexToLabel, pointLabelToIndex } = processPerformances(performances)
|
|
18
|
+
const div = document.createElement('div')
|
|
19
|
+
div.className = 'app'
|
|
20
|
+
|
|
21
|
+
const labelsDiv = document.createElement('div')
|
|
22
|
+
div.appendChild(labelsDiv)
|
|
23
|
+
|
|
24
|
+
const graphDiv = document.createElement('div')
|
|
25
|
+
graphDiv.className = 'graph'
|
|
26
|
+
div.appendChild(graphDiv)
|
|
27
|
+
|
|
28
|
+
const cosmosLabels = new CosmosLabels(labelsDiv, pointIndexToLabel)
|
|
29
|
+
|
|
30
|
+
const graph = new Graph(graphDiv, {
|
|
31
|
+
spaceSize: 4096,
|
|
32
|
+
backgroundColor: '#2d313a',
|
|
33
|
+
scalePointsOnZoom: true,
|
|
34
|
+
linkWidth: 0.6,
|
|
35
|
+
linkColor: '#5F74C2',
|
|
36
|
+
linkArrows: false,
|
|
37
|
+
fitViewOnInit: false,
|
|
38
|
+
enableDrag: true,
|
|
39
|
+
simulationGravity: 0.1,
|
|
40
|
+
simulationLinkDistance: 1,
|
|
41
|
+
simulationLinkSpring: 0.3,
|
|
42
|
+
simulationRepulsion: 0.4,
|
|
43
|
+
onSimulationTick: () => cosmosLabels.update(graph),
|
|
44
|
+
onZoom: () => cosmosLabels.update(graph),
|
|
45
|
+
attribution: 'visualized with <a href="https://cosmograph.app/" style="color: var(--cosmosgl-attribution-color);" target="_blank">Cosmograph</a>',
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
graph.setPointPositions(new Float32Array(pointPositions))
|
|
49
|
+
graph.setPointColors(new Float32Array(pointColors))
|
|
50
|
+
graph.setPointSizes(new Float32Array(pointSizes))
|
|
51
|
+
graph.setLinks(new Float32Array(links))
|
|
52
|
+
graph.render()
|
|
53
|
+
graph.setZoomLevel(0.6)
|
|
54
|
+
|
|
55
|
+
// _Track the points_ for which you wish to display labels.
|
|
56
|
+
// Their coordinates in the simulation space will be accessible
|
|
57
|
+
// via the `getTrackedPointPositionsMap` method. You can then convert
|
|
58
|
+
// them to the screen space with the `spaceToScreenPosition`
|
|
59
|
+
// method.
|
|
60
|
+
graph.trackPointPositionsByIndices(
|
|
61
|
+
pointsToShowLabelsFor.map((l) => pointLabelToIndex.get(l) as number)
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
const destroy = (): void => {
|
|
65
|
+
graph.destroy()
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return { div, graph, destroy }
|
|
69
|
+
}
|