@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,46 @@
|
|
|
1
|
+
import { LabelRenderer, LabelOptions } from '@interacta/css-labels'
|
|
2
|
+
import { Graph } from '@cosmos.gl/graph'
|
|
3
|
+
|
|
4
|
+
export class CosmosLabels {
|
|
5
|
+
private labelRenderer: LabelRenderer
|
|
6
|
+
private labels: LabelOptions[] = []
|
|
7
|
+
private pointIndexToLabel: Map<number, string>
|
|
8
|
+
|
|
9
|
+
public constructor (div: HTMLDivElement, pointIndexToLabel: Map<number, string>) {
|
|
10
|
+
this.labelRenderer = new LabelRenderer(div, { pointerEvents: 'none' })
|
|
11
|
+
this.pointIndexToLabel = pointIndexToLabel
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
public update (graph: Graph): void {
|
|
15
|
+
// Get coordinates of the tracked nodes
|
|
16
|
+
const trackedNodesPositions = graph.getTrackedPointPositionsMap()
|
|
17
|
+
let index = 0
|
|
18
|
+
trackedNodesPositions.forEach((positions, pointIndex) => {
|
|
19
|
+
// Convert coordinates to the screen space
|
|
20
|
+
const screenPosition = graph.spaceToScreenPosition([
|
|
21
|
+
positions?.[0] ?? 0,
|
|
22
|
+
positions?.[1] ?? 0,
|
|
23
|
+
])
|
|
24
|
+
|
|
25
|
+
// Get the node radius and convert it to the screen space value in pixels
|
|
26
|
+
const radius = graph.spaceToScreenRadius(
|
|
27
|
+
graph.getPointRadiusByIndex(pointIndex) as number
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
// Set label properties
|
|
31
|
+
this.labels[index] = {
|
|
32
|
+
id: `${pointIndex}`,
|
|
33
|
+
text: this.pointIndexToLabel.get(pointIndex) ?? '',
|
|
34
|
+
x: screenPosition[0],
|
|
35
|
+
y: screenPosition[1] - (radius + 2),
|
|
36
|
+
opacity: 1,
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
index += 1
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
// Pass labels configuration to the renderer and draw them
|
|
43
|
+
this.labelRenderer.setLabels(this.labels)
|
|
44
|
+
this.labelRenderer.draw(true)
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
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
|
+
color: white;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.graph {
|
|
14
|
+
width: 100%;
|
|
15
|
+
height: 100vh;
|
|
16
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { Graph, GraphConfigInterface } from '@cosmos.gl/graph'
|
|
2
|
+
|
|
3
|
+
export const quickStart = (): { graph: Graph; div: HTMLDivElement; destroy?: () => void } => {
|
|
4
|
+
const div = document.createElement('div')
|
|
5
|
+
div.style.height = '100vh'
|
|
6
|
+
div.style.width = '100%'
|
|
7
|
+
|
|
8
|
+
const config: GraphConfigInterface = {
|
|
9
|
+
spaceSize: 4096,
|
|
10
|
+
backgroundColor: '#2d313a',
|
|
11
|
+
pointColor: '#F069B4',
|
|
12
|
+
scalePointsOnZoom: true,
|
|
13
|
+
simulationFriction: 0.1, // keeps the graph inert
|
|
14
|
+
simulationGravity: 0, // disables gravity
|
|
15
|
+
simulationRepulsion: 0.5, // increases repulsion between points
|
|
16
|
+
curvedLinks: true, // curved links
|
|
17
|
+
fitViewDelay: 1000, // wait 1 second before fitting the view
|
|
18
|
+
fitViewPadding: 0.3, // centers the graph width padding of ~30% of screen
|
|
19
|
+
rescalePositions: true, // rescale positions
|
|
20
|
+
enableDrag: true, // enable dragging points
|
|
21
|
+
onPointClick: pointIndex => { console.log('Clicked point index: ', pointIndex) },
|
|
22
|
+
onBackgroundClick: () => { console.log('Clicked background') },
|
|
23
|
+
attribution: 'visualized with <a href="https://cosmograph.app/" style="color: var(--cosmosgl-attribution-color);" target="_blank">Cosmograph</a>',
|
|
24
|
+
/* ... */
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const graph = new Graph(div, config)
|
|
28
|
+
|
|
29
|
+
// Points: [x1, y1, x2, y2, x3, y3]
|
|
30
|
+
const pointPositions = new Float32Array([
|
|
31
|
+
0.0, 0.0, // Point 1 at (0,0)
|
|
32
|
+
1.0, 0.0, // Point 2 at (1,0)
|
|
33
|
+
0.5, 1.0, // Point 3 at (0.5,1)
|
|
34
|
+
])
|
|
35
|
+
|
|
36
|
+
graph.setPointPositions(pointPositions)
|
|
37
|
+
|
|
38
|
+
// Links: [sourceIndex1, targetIndex1, sourceIndex2, targetIndex2]
|
|
39
|
+
const links = new Float32Array([
|
|
40
|
+
0, 1, // Link from point 0 to point 1
|
|
41
|
+
1, 2, // Link from point 1 to point 2
|
|
42
|
+
2, 0, // Link from point 2 to point 0
|
|
43
|
+
])
|
|
44
|
+
|
|
45
|
+
graph.setLinks(links)
|
|
46
|
+
|
|
47
|
+
graph.render()
|
|
48
|
+
|
|
49
|
+
const destroy = (): void => {
|
|
50
|
+
graph.destroy()
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return { div, graph, destroy }
|
|
54
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { GraphConfigInterface } from '@cosmos.gl/graph'
|
|
2
|
+
|
|
3
|
+
export const config: GraphConfigInterface = {
|
|
4
|
+
spaceSize: 4096,
|
|
5
|
+
backgroundColor: '#2d313a',
|
|
6
|
+
pointSize: 4,
|
|
7
|
+
pointColor: '#4B5BBF',
|
|
8
|
+
scalePointsOnZoom: true,
|
|
9
|
+
pointGreyoutOpacity: 0.1,
|
|
10
|
+
linkWidth: 0.6,
|
|
11
|
+
linkColor: '#5F74C2',
|
|
12
|
+
linkArrows: false,
|
|
13
|
+
linkGreyoutOpacity: 0,
|
|
14
|
+
hoveredPointCursor: 'pointer',
|
|
15
|
+
renderHoveredPointRing: true,
|
|
16
|
+
fitViewDuration: 1000,
|
|
17
|
+
fitViewPadding: 0.3,
|
|
18
|
+
enableSimulationDuringZoom: true,
|
|
19
|
+
simulationLinkDistance: 1,
|
|
20
|
+
simulationLinkSpring: 2,
|
|
21
|
+
simulationRepulsion: 0.2,
|
|
22
|
+
simulationGravity: 0.1,
|
|
23
|
+
simulationDecay: 100000,
|
|
24
|
+
attribution: 'visualized with <a href="https://cosmograph.app/" style="color: var(--cosmosgl-attribution-color);" target="_blank">Cosmograph</a>',
|
|
25
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
function getRandom (min: number, max: number): number {
|
|
2
|
+
return Math.random() * (max - min) + min
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export function generateData (n = 100, m = 100): { pointPositions: number[]; links: number[] } {
|
|
6
|
+
const pointPositions = new Array(n * m * 2)
|
|
7
|
+
const links: number[] = []
|
|
8
|
+
for (let pointIndex = 0; pointIndex < n * m; pointIndex += 1) {
|
|
9
|
+
const x = 4096 * getRandom(0.495, 0.505)
|
|
10
|
+
const y = 4096 * getRandom(0.495, 0.505)
|
|
11
|
+
pointPositions[pointIndex * 2] = x
|
|
12
|
+
pointPositions[pointIndex * 2 + 1] = y
|
|
13
|
+
const nextPointIndex = pointIndex + 1
|
|
14
|
+
const bottomPointIndex = pointIndex + n
|
|
15
|
+
const pointLine = Math.floor(pointIndex / n)
|
|
16
|
+
const nextPointLine = Math.floor(nextPointIndex / n)
|
|
17
|
+
const bottomPointLine = Math.floor(bottomPointIndex / n)
|
|
18
|
+
if (pointLine === nextPointLine) {
|
|
19
|
+
links.push(pointIndex)
|
|
20
|
+
links.push(nextPointIndex)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (bottomPointLine < m) {
|
|
24
|
+
links.push(pointIndex)
|
|
25
|
+
links.push(bottomPointIndex)
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return { pointPositions, links }
|
|
30
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { Graph } from '@cosmos.gl/graph'
|
|
2
|
+
import { generateData } from './data-gen'
|
|
3
|
+
import { config } from './config'
|
|
4
|
+
import './style.css'
|
|
5
|
+
|
|
6
|
+
export const removePoints = (): { graph: Graph; div: HTMLDivElement; destroy?: () => void } => {
|
|
7
|
+
const { pointPositions, links } = generateData()
|
|
8
|
+
const div = document.createElement('div')
|
|
9
|
+
div.className = 'app'
|
|
10
|
+
|
|
11
|
+
const graphDiv = document.createElement('div')
|
|
12
|
+
graphDiv.className = 'graph'
|
|
13
|
+
div.appendChild(graphDiv)
|
|
14
|
+
|
|
15
|
+
const actionsDiv = document.createElement('div')
|
|
16
|
+
actionsDiv.className = 'actions'
|
|
17
|
+
div.appendChild(actionsDiv)
|
|
18
|
+
|
|
19
|
+
let isPaused = false
|
|
20
|
+
|
|
21
|
+
let graphLinks = links
|
|
22
|
+
|
|
23
|
+
const graph = new Graph(graphDiv, {
|
|
24
|
+
...config,
|
|
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) ||
|
|
32
|
+
(posIndex % 2 === 1 && posIndex !== i * 2 + 1)
|
|
33
|
+
)
|
|
34
|
+
})
|
|
35
|
+
|
|
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)
|
|
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)
|
|
57
|
+
console.log('Clicked node: ', i)
|
|
58
|
+
},
|
|
59
|
+
})
|
|
60
|
+
graph.setPointPositions(new Float32Array(pointPositions))
|
|
61
|
+
graph.setLinks(new Float32Array(graphLinks))
|
|
62
|
+
graph.render()
|
|
63
|
+
graph.zoom(0.9)
|
|
64
|
+
|
|
65
|
+
/* ~ Demo Actions ~ */
|
|
66
|
+
// Start / Pause
|
|
67
|
+
const pauseButton = document.createElement('div')
|
|
68
|
+
pauseButton.className = 'action'
|
|
69
|
+
pauseButton.textContent = 'Pause'
|
|
70
|
+
actionsDiv.appendChild(pauseButton)
|
|
71
|
+
|
|
72
|
+
function pause (): void {
|
|
73
|
+
isPaused = true
|
|
74
|
+
pauseButton.textContent = 'Start'
|
|
75
|
+
graph.pause()
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function start (): void {
|
|
79
|
+
isPaused = false
|
|
80
|
+
pauseButton.textContent = 'Pause'
|
|
81
|
+
graph.start()
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function togglePause (): void {
|
|
85
|
+
if (isPaused) start()
|
|
86
|
+
else pause()
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
pauseButton.addEventListener('click', togglePause)
|
|
90
|
+
|
|
91
|
+
const destroy = (): void => {
|
|
92
|
+
graph.destroy()
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return { div, graph, destroy }
|
|
96
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
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
|
+
|
|
18
|
+
.actions {
|
|
19
|
+
position: absolute;
|
|
20
|
+
top: 10px;
|
|
21
|
+
left: 10px;
|
|
22
|
+
color: #ccc;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.action {
|
|
26
|
+
margin-left: 2px;
|
|
27
|
+
font-size: 10pt;
|
|
28
|
+
text-decoration: underline;
|
|
29
|
+
cursor: pointer;
|
|
30
|
+
user-select: none;
|
|
31
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import type { Meta } from '@storybook/html'
|
|
2
|
+
|
|
3
|
+
import { createStory, Story } from '@/graph/stories/create-story'
|
|
4
|
+
import { CosmosStoryProps } from './create-cosmos'
|
|
5
|
+
import { quickStart } from './beginners/quick-start'
|
|
6
|
+
import { basicSetUp } from './beginners/basic-set-up'
|
|
7
|
+
import { pointLabels } from './beginners/point-labels'
|
|
8
|
+
import { removePoints } from './beginners/remove-points'
|
|
9
|
+
import { linkHovering } from './beginners/link-hovering'
|
|
10
|
+
|
|
11
|
+
import quickStartStoryRaw from './beginners/quick-start?raw'
|
|
12
|
+
import basicSetUpStoryRaw from './beginners/basic-set-up/index?raw'
|
|
13
|
+
import basicSetUpStoryCssRaw from './beginners/basic-set-up/style.css?raw'
|
|
14
|
+
import basicSetUpStoryDataGenRaw from './beginners/basic-set-up/data-gen?raw'
|
|
15
|
+
import pointLabelsStoryRaw from './beginners/point-labels/index?raw'
|
|
16
|
+
import pointLabelsStoryDataRaw from './beginners/point-labels/data.ts?raw'
|
|
17
|
+
import pointLabelsStoryLabelsRaw from './beginners/point-labels/labels.ts?raw'
|
|
18
|
+
import pointLabelsStoryCssRaw from './beginners/point-labels/style.css?raw'
|
|
19
|
+
import removePointsStoryRaw from './beginners/remove-points/index?raw'
|
|
20
|
+
import removePointsStoryCssRaw from './beginners/remove-points/style.css?raw'
|
|
21
|
+
import removePointsStoryConfigRaw from './beginners/remove-points/config.ts?raw'
|
|
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'
|
|
26
|
+
|
|
27
|
+
// More on how to set up stories at: https://storybook.js.org/docs/writing-stories#default-export
|
|
28
|
+
const meta: Meta<CosmosStoryProps> = {
|
|
29
|
+
title: 'Examples/Beginners',
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export const QuickStart: Story = {
|
|
33
|
+
...createStory(quickStart),
|
|
34
|
+
parameters: {
|
|
35
|
+
sourceCode: [
|
|
36
|
+
{ name: 'Story', code: quickStartStoryRaw },
|
|
37
|
+
],
|
|
38
|
+
},
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export const BasicSetUp: Story = {
|
|
42
|
+
...createStory(basicSetUp),
|
|
43
|
+
name: '100x100 grid',
|
|
44
|
+
parameters: {
|
|
45
|
+
sourceCode: [
|
|
46
|
+
{ name: 'Story', code: basicSetUpStoryRaw },
|
|
47
|
+
{ name: 'style.css', code: basicSetUpStoryCssRaw },
|
|
48
|
+
{ name: 'data-gen', code: basicSetUpStoryDataGenRaw },
|
|
49
|
+
],
|
|
50
|
+
},
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export const PointLabels: Story = {
|
|
54
|
+
loaders: [
|
|
55
|
+
async (): Promise<{ data: { performances: [] } }> => {
|
|
56
|
+
try {
|
|
57
|
+
const response = await fetch('https://gist.githubusercontent.com/Stukova/e6c4c7777e0166431a983999213f10c8/raw/performances.json')
|
|
58
|
+
if (!response.ok) {
|
|
59
|
+
throw new Error(`HTTP error! Status: ${response.status}`)
|
|
60
|
+
}
|
|
61
|
+
return {
|
|
62
|
+
data: await response.json(),
|
|
63
|
+
}
|
|
64
|
+
} catch (error) {
|
|
65
|
+
console.error('Failed to fetch data:', error)
|
|
66
|
+
return {
|
|
67
|
+
data: { performances: [] },
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
],
|
|
72
|
+
async beforeEach (d): Promise<() => void> {
|
|
73
|
+
return (): void => {
|
|
74
|
+
d.args.destroy?.()
|
|
75
|
+
d.args.graph?.destroy()
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
render: (args, { loaded: { data } }): HTMLDivElement => {
|
|
79
|
+
const div = document.createElement('div')
|
|
80
|
+
div.style.height = '100vh'
|
|
81
|
+
div.style.width = '100%'
|
|
82
|
+
|
|
83
|
+
try {
|
|
84
|
+
const story = pointLabels(data.performances)
|
|
85
|
+
args.graph = story.graph
|
|
86
|
+
args.destroy = story.destroy
|
|
87
|
+
div.appendChild(story.div)
|
|
88
|
+
} catch (error) {
|
|
89
|
+
console.error('Failed to load PointLabels story:', error)
|
|
90
|
+
div.innerHTML = '<div style="display: flex; align-items: center; justify-content: center; height: 100%; color: #ff0000;">Failed to load story</div>'
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return div
|
|
94
|
+
},
|
|
95
|
+
parameters: {
|
|
96
|
+
sourceCode: [
|
|
97
|
+
{ name: 'Story', code: pointLabelsStoryRaw },
|
|
98
|
+
{ name: 'data.ts', code: pointLabelsStoryDataRaw },
|
|
99
|
+
{ name: 'labels.ts', code: pointLabelsStoryLabelsRaw },
|
|
100
|
+
{ name: 'style.css', code: pointLabelsStoryCssRaw },
|
|
101
|
+
],
|
|
102
|
+
},
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export const RemovePoints: Story = {
|
|
106
|
+
...createStory(removePoints),
|
|
107
|
+
parameters: {
|
|
108
|
+
sourceCode: [
|
|
109
|
+
{ name: 'Story', code: removePointsStoryRaw },
|
|
110
|
+
{ name: 'config.ts', code: removePointsStoryConfigRaw },
|
|
111
|
+
{ name: 'data-gen.ts', code: removePointsStoryDataGenRaw },
|
|
112
|
+
{ name: 'style.css', code: removePointsStoryCssRaw },
|
|
113
|
+
],
|
|
114
|
+
},
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export const LinkHovering: Story = {
|
|
118
|
+
...createStory(linkHovering),
|
|
119
|
+
name: 'Link Hovering',
|
|
120
|
+
parameters: {
|
|
121
|
+
sourceCode: [
|
|
122
|
+
{ name: 'Story', code: linkHoveringStoryRaw },
|
|
123
|
+
{ name: 'data-generator.ts', code: linkHoveringStoryDataGenRaw },
|
|
124
|
+
{ name: 'style.css', code: linkHoveringStoryCssRaw },
|
|
125
|
+
],
|
|
126
|
+
},
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// eslint-disable-next-line import/no-default-export
|
|
130
|
+
export default meta
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { Graph } from '@cosmos.gl/graph'
|
|
2
|
+
import { createCosmos } from '../../create-cosmos'
|
|
3
|
+
import { generateMeshData } from '../../generate-mesh-data'
|
|
4
|
+
import { PolygonSelection } from './polygon'
|
|
5
|
+
|
|
6
|
+
export const polygonSelection = async (): Promise<{div: HTMLDivElement; graph: Graph; destroy: () => void }> => {
|
|
7
|
+
const nClusters = 25
|
|
8
|
+
const { pointPositions, pointColors, pointClusters } = generateMeshData(150, 150, nClusters, 1.0)
|
|
9
|
+
|
|
10
|
+
const { div, graph, destroy: baseDestroy } = await createCosmos({
|
|
11
|
+
pointPositions,
|
|
12
|
+
pointColors,
|
|
13
|
+
pointClusters,
|
|
14
|
+
simulationGravity: 1.5,
|
|
15
|
+
simulationCluster: 0.3,
|
|
16
|
+
simulationRepulsion: 8,
|
|
17
|
+
pointSize: 8,
|
|
18
|
+
backgroundColor: '#1a1a2e',
|
|
19
|
+
pointGreyoutOpacity: 0.2,
|
|
20
|
+
onBackgroundClick: (): void => {
|
|
21
|
+
graph.unselectPoints()
|
|
22
|
+
},
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
graph.setZoomLevel(0.4)
|
|
26
|
+
|
|
27
|
+
const polygonSelection = new PolygonSelection(div, (polygonPoints) => {
|
|
28
|
+
graph.selectPointsInPolygon(polygonPoints)
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
const actionsDiv = document.createElement('div')
|
|
32
|
+
actionsDiv.className = 'actions'
|
|
33
|
+
div.appendChild(actionsDiv)
|
|
34
|
+
|
|
35
|
+
const polygonButton = document.createElement('div')
|
|
36
|
+
polygonButton.className = 'action'
|
|
37
|
+
polygonButton.textContent = 'Enable Polygon Selection'
|
|
38
|
+
polygonButton.addEventListener('click', () => {
|
|
39
|
+
polygonSelection.enablePolygonMode()
|
|
40
|
+
})
|
|
41
|
+
actionsDiv.appendChild(polygonButton)
|
|
42
|
+
|
|
43
|
+
const destroy = (): void => {
|
|
44
|
+
polygonSelection.destroy()
|
|
45
|
+
if (actionsDiv.parentNode) {
|
|
46
|
+
actionsDiv.parentNode.removeChild(actionsDiv)
|
|
47
|
+
}
|
|
48
|
+
baseDestroy?.()
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return { div, graph, destroy }
|
|
52
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import './style.css'
|
|
2
|
+
|
|
3
|
+
export class PolygonSelection {
|
|
4
|
+
private canvas: HTMLCanvasElement
|
|
5
|
+
private ctx: CanvasRenderingContext2D
|
|
6
|
+
private isDrawing = false
|
|
7
|
+
private points: Array<{ x: number; y: number }> = []
|
|
8
|
+
private graphDiv: HTMLElement
|
|
9
|
+
private onPolygonComplete?: (points: [number, number][]) => void
|
|
10
|
+
private boundStartDrawing: (e: MouseEvent) => void
|
|
11
|
+
private boundDraw: (e: MouseEvent) => void
|
|
12
|
+
private boundStopDrawing: () => void
|
|
13
|
+
private resizeObserver: ResizeObserver
|
|
14
|
+
|
|
15
|
+
public constructor (graphDiv: HTMLElement, onPolygonComplete?: (points: [number, number][]) => void) {
|
|
16
|
+
this.graphDiv = graphDiv
|
|
17
|
+
this.onPolygonComplete = onPolygonComplete
|
|
18
|
+
|
|
19
|
+
// Bind event handlers
|
|
20
|
+
this.boundStartDrawing = this.startDrawing.bind(this)
|
|
21
|
+
this.boundDraw = this.draw.bind(this)
|
|
22
|
+
this.boundStopDrawing = this.stopDrawing.bind(this)
|
|
23
|
+
|
|
24
|
+
// Create canvas
|
|
25
|
+
this.canvas = document.createElement('canvas')
|
|
26
|
+
this.canvas.className = 'polygon-canvas'
|
|
27
|
+
|
|
28
|
+
const ctx = this.canvas.getContext('2d')
|
|
29
|
+
if (!ctx) throw new Error('Could not get canvas context')
|
|
30
|
+
this.ctx = ctx
|
|
31
|
+
|
|
32
|
+
this.graphDiv.appendChild(this.canvas)
|
|
33
|
+
|
|
34
|
+
this.resizeObserver = new ResizeObserver(() => {
|
|
35
|
+
this.resizeCanvas()
|
|
36
|
+
})
|
|
37
|
+
this.resizeObserver.observe(this.graphDiv)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
public enablePolygonMode (): void {
|
|
41
|
+
this.canvas.style.pointerEvents = 'auto'
|
|
42
|
+
this.canvas.style.cursor = 'crosshair'
|
|
43
|
+
|
|
44
|
+
// Add event listeners
|
|
45
|
+
this.canvas.addEventListener('mousedown', this.boundStartDrawing)
|
|
46
|
+
this.canvas.addEventListener('mousemove', this.boundDraw)
|
|
47
|
+
this.canvas.addEventListener('mouseup', this.boundStopDrawing)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
public disablePolygonMode (): void {
|
|
51
|
+
this.canvas.style.pointerEvents = 'none'
|
|
52
|
+
this.canvas.style.cursor = 'default'
|
|
53
|
+
|
|
54
|
+
// Remove event listeners
|
|
55
|
+
this.canvas.removeEventListener('mousedown', this.boundStartDrawing)
|
|
56
|
+
this.canvas.removeEventListener('mousemove', this.boundDraw)
|
|
57
|
+
this.canvas.removeEventListener('mouseup', this.boundStopDrawing)
|
|
58
|
+
|
|
59
|
+
// Clear canvas
|
|
60
|
+
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
public destroy (): void {
|
|
64
|
+
this.disablePolygonMode()
|
|
65
|
+
this.resizeObserver.disconnect()
|
|
66
|
+
if (this.canvas.parentNode) {
|
|
67
|
+
this.canvas.parentNode.removeChild(this.canvas)
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
private resizeCanvas (): void {
|
|
72
|
+
const rect = this.graphDiv.getBoundingClientRect()
|
|
73
|
+
|
|
74
|
+
// Apply pixel ratio for crisp rendering
|
|
75
|
+
const pixelRatio = window.devicePixelRatio || 1
|
|
76
|
+
this.canvas.width = rect.width * pixelRatio
|
|
77
|
+
this.canvas.height = rect.height * pixelRatio
|
|
78
|
+
|
|
79
|
+
// Reset transform and scale the context to match the pixel ratio
|
|
80
|
+
this.ctx.resetTransform()
|
|
81
|
+
this.ctx.scale(pixelRatio, pixelRatio)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
private startDrawing (e: MouseEvent): void {
|
|
85
|
+
this.isDrawing = true
|
|
86
|
+
this.points = []
|
|
87
|
+
const rect = this.canvas.getBoundingClientRect()
|
|
88
|
+
this.points.push({ x: e.clientX - rect.left, y: e.clientY - rect.top })
|
|
89
|
+
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
private draw (e: MouseEvent): void {
|
|
93
|
+
if (!this.isDrawing) return
|
|
94
|
+
|
|
95
|
+
const rect = this.canvas.getBoundingClientRect()
|
|
96
|
+
this.points.push({ x: e.clientX - rect.left, y: e.clientY - rect.top })
|
|
97
|
+
|
|
98
|
+
// Clear the entire canvas accounting for pixel ratio
|
|
99
|
+
const pixelRatio = window.devicePixelRatio || 1
|
|
100
|
+
this.ctx.clearRect(0, 0, this.canvas.width / pixelRatio, this.canvas.height / pixelRatio)
|
|
101
|
+
|
|
102
|
+
this.ctx.beginPath()
|
|
103
|
+
if (this.points.length > 0 && this.points[0]) {
|
|
104
|
+
this.ctx.moveTo(this.points[0].x, this.points[0].y)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
for (let i = 1; i < this.points.length; i++) {
|
|
108
|
+
const point = this.points[i]
|
|
109
|
+
if (point) {
|
|
110
|
+
this.ctx.lineTo(point.x, point.y)
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
this.ctx.strokeStyle = '#ffffff'
|
|
115
|
+
this.ctx.lineWidth = 2
|
|
116
|
+
this.ctx.stroke()
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
private stopDrawing (): void {
|
|
120
|
+
if (!this.isDrawing) return
|
|
121
|
+
this.isDrawing = false
|
|
122
|
+
|
|
123
|
+
if (this.points.length > 2) {
|
|
124
|
+
this.ctx.closePath()
|
|
125
|
+
this.ctx.stroke()
|
|
126
|
+
|
|
127
|
+
const polygonPoints: [number, number][] = this.points.map(p => [p.x, p.y])
|
|
128
|
+
const firstPolygonPoint = polygonPoints[0]
|
|
129
|
+
const lastPolygonPoint = polygonPoints[polygonPoints.length - 1]
|
|
130
|
+
if (firstPolygonPoint && lastPolygonPoint && (firstPolygonPoint[0] !== lastPolygonPoint[0] || firstPolygonPoint[1] !== lastPolygonPoint[1])) {
|
|
131
|
+
polygonPoints.push(firstPolygonPoint)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (this.onPolygonComplete) {
|
|
135
|
+
this.onPolygonComplete(polygonPoints)
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const pixelRatio = window.devicePixelRatio || 1
|
|
140
|
+
this.ctx.clearRect(0, 0, this.canvas.width / pixelRatio, this.canvas.height / pixelRatio)
|
|
141
|
+
this.disablePolygonMode()
|
|
142
|
+
}
|
|
143
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
|
|
2
|
+
import { Graph } from '@cosmos.gl/graph'
|
|
3
|
+
import { createCosmos } from '../create-cosmos'
|
|
4
|
+
import { generateMeshData } from '../generate-mesh-data'
|
|
5
|
+
|
|
6
|
+
export const radial = async (): Promise<{ graph: Graph; div: HTMLDivElement; destroy?: () => void }> => {
|
|
7
|
+
const {
|
|
8
|
+
pointPositions, pointColors, pointSizes,
|
|
9
|
+
links, linkColors, linkWidths,
|
|
10
|
+
pointClusters, clusterPositions, clusterStrength,
|
|
11
|
+
} = generateMeshData(100, 100, 100, 1.0)
|
|
12
|
+
|
|
13
|
+
return createCosmos({
|
|
14
|
+
pointPositions,
|
|
15
|
+
pointColors,
|
|
16
|
+
pointSizes,
|
|
17
|
+
pointClusters,
|
|
18
|
+
links,
|
|
19
|
+
linkColors,
|
|
20
|
+
linkWidths,
|
|
21
|
+
clusterPositions,
|
|
22
|
+
clusterStrength,
|
|
23
|
+
})
|
|
24
|
+
}
|