@cosmos.gl/graph 2.5.0 → 2.6.1

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 (181) hide show
  1. package/dist/config.d.ts +8 -2
  2. package/dist/helper.d.ts +2 -2
  3. package/dist/index.d.ts +21 -2
  4. package/dist/index.js +4950 -14357
  5. package/dist/index.js.map +1 -1
  6. package/dist/index.min.js +1 -1678
  7. package/dist/index.min.js.map +1 -1
  8. package/dist/modules/Clusters/index.d.ts +2 -2
  9. package/dist/modules/Drag/index.d.ts +3 -3
  10. package/dist/modules/ForceCenter/index.d.ts +1 -1
  11. package/dist/modules/ForceGravity/index.d.ts +1 -1
  12. package/dist/modules/ForceLink/index.d.ts +1 -1
  13. package/dist/modules/ForceManyBody/index.d.ts +1 -1
  14. package/dist/modules/ForceManyBodyQuadtree/index.d.ts +1 -1
  15. package/dist/modules/ForceMouse/index.d.ts +1 -1
  16. package/dist/modules/GraphData/index.d.ts +2 -1
  17. package/dist/modules/Lines/index.d.ts +2 -2
  18. package/dist/modules/Points/index.d.ts +5 -2
  19. package/dist/modules/Shared/buffer.d.ts +1 -1
  20. package/dist/modules/Store/index.d.ts +1 -1
  21. package/dist/modules/Zoom/index.d.ts +3 -3
  22. package/dist/modules/core-module.d.ts +5 -5
  23. package/package.json +5 -1
  24. package/.eslintrc +0 -147
  25. package/.github/SECURITY.md +0 -13
  26. package/.github/dco.yml +0 -4
  27. package/.github/workflows/github_pages.yml +0 -54
  28. package/.storybook/main.ts +0 -26
  29. package/.storybook/manager-head.html +0 -1
  30. package/.storybook/manager.ts +0 -14
  31. package/.storybook/preview.ts +0 -29
  32. package/.storybook/style.css +0 -3
  33. package/CHARTER.md +0 -69
  34. package/CODE_OF_CONDUCT.md +0 -178
  35. package/CONTRIBUTING.md +0 -22
  36. package/GOVERNANCE.md +0 -21
  37. package/cosmos-2-0-migration-notes.md +0 -98
  38. package/cosmos_awesome.md +0 -96
  39. package/dist/stories/beginners/basic-set-up/data-gen.d.ts +0 -4
  40. package/dist/stories/beginners/basic-set-up/index.d.ts +0 -5
  41. package/dist/stories/beginners/link-hovering/data-generator.d.ts +0 -19
  42. package/dist/stories/beginners/link-hovering/index.d.ts +0 -5
  43. package/dist/stories/beginners/point-labels/data.d.ts +0 -13
  44. package/dist/stories/beginners/point-labels/index.d.ts +0 -9
  45. package/dist/stories/beginners/point-labels/labels.d.ts +0 -8
  46. package/dist/stories/beginners/quick-start.d.ts +0 -5
  47. package/dist/stories/beginners/remove-points/config.d.ts +0 -2
  48. package/dist/stories/beginners/remove-points/data-gen.d.ts +0 -4
  49. package/dist/stories/beginners/remove-points/index.d.ts +0 -5
  50. package/dist/stories/beginners.stories.d.ts +0 -10
  51. package/dist/stories/clusters/polygon-selection/index.d.ts +0 -6
  52. package/dist/stories/clusters/polygon-selection/polygon.d.ts +0 -20
  53. package/dist/stories/clusters/radial.d.ts +0 -5
  54. package/dist/stories/clusters/with-labels.d.ts +0 -6
  55. package/dist/stories/clusters/worm.d.ts +0 -5
  56. package/dist/stories/clusters.stories.d.ts +0 -9
  57. package/dist/stories/create-cluster-labels.d.ts +0 -4
  58. package/dist/stories/create-cosmos.d.ts +0 -16
  59. package/dist/stories/create-story.d.ts +0 -16
  60. package/dist/stories/experiments/full-mesh.d.ts +0 -5
  61. package/dist/stories/experiments/mesh-with-holes.d.ts +0 -5
  62. package/dist/stories/experiments.stories.d.ts +0 -7
  63. package/dist/stories/generate-mesh-data.d.ts +0 -12
  64. package/dist/stories/geospatial/moscow-metro-stations/index.d.ts +0 -15
  65. package/dist/stories/geospatial/moscow-metro-stations/moscow-metro-coords.d.ts +0 -1
  66. package/dist/stories/geospatial/moscow-metro-stations/point-colors.d.ts +0 -1
  67. package/dist/stories/geospatial.stories.d.ts +0 -6
  68. package/dist/stories/shapes/all-shapes/index.d.ts +0 -5
  69. package/dist/stories/shapes/image-example/index.d.ts +0 -5
  70. package/dist/stories/shapes.stories.d.ts +0 -7
  71. package/logo.svg +0 -3
  72. package/rollup.config.js +0 -70
  73. package/src/config.ts +0 -726
  74. package/src/declaration.d.ts +0 -12
  75. package/src/graph/utils/error-message.ts +0 -23
  76. package/src/helper.ts +0 -74
  77. package/src/index.ts +0 -1608
  78. package/src/modules/Clusters/calculate-centermass.frag +0 -9
  79. package/src/modules/Clusters/calculate-centermass.vert +0 -26
  80. package/src/modules/Clusters/force-cluster.frag +0 -39
  81. package/src/modules/Clusters/index.ts +0 -200
  82. package/src/modules/Drag/index.ts +0 -33
  83. package/src/modules/FPSMonitor/css.ts +0 -53
  84. package/src/modules/FPSMonitor/index.ts +0 -28
  85. package/src/modules/ForceCenter/calculate-centermass.frag +0 -9
  86. package/src/modules/ForceCenter/calculate-centermass.vert +0 -18
  87. package/src/modules/ForceCenter/force-center.frag +0 -27
  88. package/src/modules/ForceCenter/index.ts +0 -104
  89. package/src/modules/ForceGravity/force-gravity.frag +0 -27
  90. package/src/modules/ForceGravity/index.ts +0 -33
  91. package/src/modules/ForceLink/force-spring.ts +0 -73
  92. package/src/modules/ForceLink/index.ts +0 -149
  93. package/src/modules/ForceManyBody/calculate-level.frag +0 -9
  94. package/src/modules/ForceManyBody/calculate-level.vert +0 -25
  95. package/src/modules/ForceManyBody/force-centermass.frag +0 -52
  96. package/src/modules/ForceManyBody/force-level.frag +0 -121
  97. package/src/modules/ForceManyBody/index.ts +0 -223
  98. package/src/modules/ForceManyBody/quadtree-frag-shader.ts +0 -90
  99. package/src/modules/ForceManyBodyQuadtree/calculate-level.frag +0 -9
  100. package/src/modules/ForceManyBodyQuadtree/calculate-level.vert +0 -25
  101. package/src/modules/ForceManyBodyQuadtree/index.ts +0 -157
  102. package/src/modules/ForceManyBodyQuadtree/quadtree-frag-shader.ts +0 -93
  103. package/src/modules/ForceMouse/force-mouse.frag +0 -24
  104. package/src/modules/ForceMouse/index.ts +0 -32
  105. package/src/modules/GraphData/index.ts +0 -383
  106. package/src/modules/Lines/draw-curve-line.frag +0 -46
  107. package/src/modules/Lines/draw-curve-line.vert +0 -194
  108. package/src/modules/Lines/geometry.ts +0 -18
  109. package/src/modules/Lines/hovered-line-index.frag +0 -27
  110. package/src/modules/Lines/hovered-line-index.vert +0 -8
  111. package/src/modules/Lines/index.ts +0 -273
  112. package/src/modules/Points/atlas-utils.ts +0 -137
  113. package/src/modules/Points/drag-point.frag +0 -20
  114. package/src/modules/Points/draw-highlighted.frag +0 -16
  115. package/src/modules/Points/draw-highlighted.vert +0 -86
  116. package/src/modules/Points/draw-points.frag +0 -243
  117. package/src/modules/Points/draw-points.vert +0 -127
  118. package/src/modules/Points/fill-sampled-points.frag +0 -9
  119. package/src/modules/Points/fill-sampled-points.vert +0 -29
  120. package/src/modules/Points/find-hovered-point.frag +0 -9
  121. package/src/modules/Points/find-hovered-point.vert +0 -57
  122. package/src/modules/Points/find-points-on-area-selection.frag +0 -48
  123. package/src/modules/Points/find-points-on-polygon-selection.frag +0 -65
  124. package/src/modules/Points/index.ts +0 -934
  125. package/src/modules/Points/track-positions.frag +0 -18
  126. package/src/modules/Points/update-position.frag +0 -25
  127. package/src/modules/Shared/buffer.ts +0 -37
  128. package/src/modules/Shared/clear.frag +0 -7
  129. package/src/modules/Shared/quad.vert +0 -12
  130. package/src/modules/Store/index.ts +0 -173
  131. package/src/modules/Zoom/index.ts +0 -148
  132. package/src/modules/core-module.ts +0 -28
  133. package/src/stories/1. welcome.mdx +0 -75
  134. package/src/stories/2. configuration.mdx +0 -112
  135. package/src/stories/3. api-reference.mdx +0 -591
  136. package/src/stories/beginners/basic-set-up/data-gen.ts +0 -33
  137. package/src/stories/beginners/basic-set-up/index.ts +0 -163
  138. package/src/stories/beginners/basic-set-up/style.css +0 -35
  139. package/src/stories/beginners/link-hovering/data-generator.ts +0 -198
  140. package/src/stories/beginners/link-hovering/index.ts +0 -61
  141. package/src/stories/beginners/link-hovering/style.css +0 -73
  142. package/src/stories/beginners/point-labels/data.ts +0 -73
  143. package/src/stories/beginners/point-labels/index.ts +0 -65
  144. package/src/stories/beginners/point-labels/labels.ts +0 -46
  145. package/src/stories/beginners/point-labels/style.css +0 -16
  146. package/src/stories/beginners/quick-start.ts +0 -50
  147. package/src/stories/beginners/remove-points/config.ts +0 -25
  148. package/src/stories/beginners/remove-points/data-gen.ts +0 -30
  149. package/src/stories/beginners/remove-points/index.ts +0 -92
  150. package/src/stories/beginners/remove-points/style.css +0 -31
  151. package/src/stories/beginners.stories.ts +0 -117
  152. package/src/stories/clusters/polygon-selection/index.ts +0 -51
  153. package/src/stories/clusters/polygon-selection/polygon.ts +0 -143
  154. package/src/stories/clusters/polygon-selection/style.css +0 -8
  155. package/src/stories/clusters/radial.ts +0 -24
  156. package/src/stories/clusters/with-labels.ts +0 -53
  157. package/src/stories/clusters/worm.ts +0 -40
  158. package/src/stories/clusters.stories.ts +0 -77
  159. package/src/stories/create-cluster-labels.ts +0 -50
  160. package/src/stories/create-cosmos.ts +0 -68
  161. package/src/stories/create-story.ts +0 -51
  162. package/src/stories/experiments/full-mesh.ts +0 -13
  163. package/src/stories/experiments/mesh-with-holes.ts +0 -13
  164. package/src/stories/experiments.stories.ts +0 -43
  165. package/src/stories/generate-mesh-data.ts +0 -125
  166. package/src/stories/geospatial/moscow-metro-stations/index.ts +0 -62
  167. package/src/stories/geospatial/moscow-metro-stations/moscow-metro-coords.ts +0 -1
  168. package/src/stories/geospatial/moscow-metro-stations/point-colors.ts +0 -46
  169. package/src/stories/geospatial/moscow-metro-stations/style.css +0 -30
  170. package/src/stories/geospatial.stories.ts +0 -30
  171. package/src/stories/shapes/all-shapes/index.ts +0 -69
  172. package/src/stories/shapes/image-example/icons/box.png +0 -0
  173. package/src/stories/shapes/image-example/icons/lego.png +0 -0
  174. package/src/stories/shapes/image-example/icons/s.png +0 -0
  175. package/src/stories/shapes/image-example/icons/swift.png +0 -0
  176. package/src/stories/shapes/image-example/icons/toolbox.png +0 -0
  177. package/src/stories/shapes/image-example/index.ts +0 -238
  178. package/src/stories/shapes.stories.ts +0 -37
  179. package/src/variables.ts +0 -68
  180. package/tsconfig.json +0 -41
  181. package/vite.config.ts +0 -54
package/src/index.ts DELETED
@@ -1,1608 +0,0 @@
1
- import { select, Selection } from 'd3-selection'
2
- import 'd3-transition'
3
- import { easeQuadInOut, easeQuadIn, easeQuadOut } from 'd3-ease'
4
- import { D3ZoomEvent } from 'd3-zoom'
5
- import { D3DragEvent } from 'd3-drag'
6
- import regl from 'regl'
7
- import { GraphConfig, GraphConfigInterface } from '@/graph/config'
8
- import { getRgbaColor, readPixels, sanitizeHtml } from '@/graph/helper'
9
- import { ForceCenter } from '@/graph/modules/ForceCenter'
10
- import { ForceGravity } from '@/graph/modules/ForceGravity'
11
- import { ForceLink, LinkDirection } from '@/graph/modules/ForceLink'
12
- import { ForceManyBody } from '@/graph/modules/ForceManyBody'
13
- import { ForceManyBodyQuadtree } from '@/graph/modules/ForceManyBodyQuadtree'
14
- import { ForceMouse } from '@/graph/modules/ForceMouse'
15
- import { Clusters } from '@/graph/modules/Clusters'
16
- import { FPSMonitor } from '@/graph/modules/FPSMonitor'
17
- import { GraphData } from '@/graph/modules/GraphData'
18
- import { Lines } from '@/graph/modules/Lines'
19
- import { Points } from '@/graph/modules/Points'
20
- import { Store, ALPHA_MIN, MAX_POINT_SIZE, MAX_HOVER_DETECTION_DELAY, type Hovered } from '@/graph/modules/Store'
21
- import { Zoom } from '@/graph/modules/Zoom'
22
- import { Drag } from '@/graph/modules/Drag'
23
- import { defaultConfigValues, defaultScaleToZoom, defaultGreyoutPointColor, defaultBackgroundColor } from '@/graph/variables'
24
- import { createWebGLErrorMessage } from './graph/utils/error-message'
25
-
26
- export class Graph {
27
- public config = new GraphConfig()
28
- public graph = new GraphData(this.config)
29
- private canvas: HTMLCanvasElement
30
- private attributionDivElement: HTMLElement | undefined
31
- private canvasD3Selection: Selection<HTMLCanvasElement, undefined, null, undefined> | undefined
32
- private reglInstance: regl.Regl | undefined
33
- private requestAnimationFrameId = 0
34
- private isRightClickMouse = false
35
-
36
- private store = new Store()
37
- private points: Points | undefined
38
- private lines: Lines | undefined
39
- private forceGravity: ForceGravity | undefined
40
- private forceCenter: ForceCenter | undefined
41
- private forceManyBody: ForceManyBody | ForceManyBodyQuadtree | undefined
42
- private forceLinkIncoming: ForceLink | undefined
43
- private forceLinkOutgoing: ForceLink | undefined
44
- private forceMouse: ForceMouse | undefined
45
- private clusters: Clusters | undefined
46
- private zoomInstance = new Zoom(this.store, this.config)
47
- private dragInstance = new Drag(this.store, this.config)
48
-
49
- private fpsMonitor: FPSMonitor | undefined
50
-
51
- private currentEvent: D3ZoomEvent<HTMLCanvasElement, undefined> | D3DragEvent<HTMLCanvasElement, undefined, Hovered> | MouseEvent | undefined
52
- /**
53
- * The value of `_findHoveredItemExecutionCount` is incremented by 1 on each animation frame.
54
- * When the counter reaches MAX_HOVER_DETECTION_DELAY (default 4), it is reset to 0 and the `findHoveredPoint` or `findHoveredLine` method is executed.
55
- */
56
- private _findHoveredItemExecutionCount = 0
57
- /**
58
- * If the mouse is not on the Canvas, the `findHoveredPoint` or `findHoveredLine` method will not be executed.
59
- */
60
- private _isMouseOnCanvas = false
61
- /**
62
- * After setting data and render graph at a first time, the fit logic will run
63
- * */
64
- private _isFirstRenderAfterInit = true
65
- private _fitViewOnInitTimeoutID: number | undefined
66
-
67
- private isPointPositionsUpdateNeeded = false
68
- private isPointColorUpdateNeeded = false
69
- private isPointSizeUpdateNeeded = false
70
- private isPointShapeUpdateNeeded = false
71
- private isPointImageIndicesUpdateNeeded = false
72
- private isLinksUpdateNeeded = false
73
- private isLinkColorUpdateNeeded = false
74
- private isLinkWidthUpdateNeeded = false
75
- private isLinkArrowUpdateNeeded = false
76
- private isPointClusterUpdateNeeded = false
77
- private isForceManyBodyUpdateNeeded = false
78
- private isForceLinkUpdateNeeded = false
79
- private isForceCenterUpdateNeeded = false
80
- private isPointImageSizesUpdateNeeded = false
81
-
82
- private _isDestroyed = false
83
-
84
- public constructor (div: HTMLDivElement, config?: GraphConfigInterface) {
85
- if (config) this.config.init(config)
86
-
87
- this.store.div = div
88
- const canvas = document.createElement('canvas')
89
- canvas.style.width = '100%'
90
- canvas.style.height = '100%'
91
- this.store.div.appendChild(canvas)
92
- this.addAttribution()
93
- const w = canvas.clientWidth
94
- const h = canvas.clientHeight
95
-
96
- canvas.width = w * this.config.pixelRatio
97
- canvas.height = h * this.config.pixelRatio
98
-
99
- this.canvas = canvas
100
-
101
- let reglInstance: regl.Regl | undefined
102
- try {
103
- reglInstance = regl({
104
- canvas: this.canvas,
105
- attributes: {
106
- antialias: false,
107
- preserveDrawingBuffer: true,
108
- },
109
- extensions: ['OES_texture_float', 'ANGLE_instanced_arrays'],
110
- })
111
- } catch (e) {
112
- createWebGLErrorMessage(this.store.div)
113
- this._isDestroyed = true
114
- return
115
- }
116
- this.reglInstance = reglInstance
117
-
118
- this.store.adjustSpaceSize(this.config.spaceSize, this.reglInstance.limits.maxTextureSize)
119
- this.store.setWebGLMaxTextureSize(this.reglInstance.limits.maxTextureSize)
120
- this.store.updateScreenSize(w, h)
121
-
122
- this.canvasD3Selection = select<HTMLCanvasElement, undefined>(this.canvas)
123
- this.canvasD3Selection
124
- .on('mouseenter.cosmos', () => { this._isMouseOnCanvas = true })
125
- .on('mousemove.cosmos', () => { this._isMouseOnCanvas = true })
126
- .on('mouseleave.cosmos', (event) => {
127
- this._isMouseOnCanvas = false
128
- this.currentEvent = event
129
-
130
- // Clear point hover state and trigger callback if needed
131
- if (this.store.hoveredPoint !== undefined && this.config.onPointMouseOut) {
132
- this.config.onPointMouseOut(event)
133
- }
134
-
135
- // Clear link hover state and trigger callback if needed
136
- if (this.store.hoveredLinkIndex !== undefined && this.config.onLinkMouseOut) {
137
- this.config.onLinkMouseOut(event)
138
- }
139
-
140
- // Reset right-click flag
141
- this.isRightClickMouse = false
142
-
143
- // Clear hover states
144
- this.store.hoveredPoint = undefined
145
- this.store.hoveredLinkIndex = undefined
146
-
147
- // Update cursor style after clearing hover states
148
- this.updateCanvasCursor()
149
- })
150
- select(document)
151
- .on('keydown.cosmos', (event) => { if (event.code === 'Space') this.store.isSpaceKeyPressed = true })
152
- .on('keyup.cosmos', (event) => { if (event.code === 'Space') this.store.isSpaceKeyPressed = false })
153
- this.zoomInstance.behavior
154
- .on('start.detect', (e: D3ZoomEvent<HTMLCanvasElement, undefined>) => { this.currentEvent = e })
155
- .on('zoom.detect', (e: D3ZoomEvent<HTMLCanvasElement, undefined>) => {
156
- const userDriven = !!e.sourceEvent
157
- if (userDriven) this.updateMousePosition(e.sourceEvent)
158
- this.currentEvent = e
159
- })
160
- .on('end.detect', (e: D3ZoomEvent<HTMLCanvasElement, undefined>) => { this.currentEvent = e })
161
- this.dragInstance.behavior
162
- .on('start.detect', (e: D3DragEvent<HTMLCanvasElement, undefined, Hovered>) => {
163
- this.currentEvent = e
164
- this.updateCanvasCursor()
165
- })
166
- .on('drag.detect', (e: D3DragEvent<HTMLCanvasElement, undefined, Hovered>) => {
167
- if (this.dragInstance.isActive) {
168
- this.updateMousePosition(e)
169
- }
170
- this.currentEvent = e
171
- })
172
- .on('end.detect', (e: D3DragEvent<HTMLCanvasElement, undefined, Hovered>) => {
173
- this.currentEvent = e
174
- this.updateCanvasCursor()
175
- })
176
- this.canvasD3Selection
177
- .call(this.dragInstance.behavior)
178
- .call(this.zoomInstance.behavior)
179
- .on('click', this.onClick.bind(this))
180
- .on('mousemove', this.onMouseMove.bind(this))
181
- .on('contextmenu', this.onRightClickMouse.bind(this))
182
- if (!this.config.enableZoom || !this.config.enableDrag) this.updateZoomDragBehaviors()
183
- this.setZoomLevel(this.config.initialZoomLevel ?? 1)
184
-
185
- this.store.maxPointSize = (this.reglInstance.limits.pointSizeDims[1] ?? MAX_POINT_SIZE) / this.config.pixelRatio
186
-
187
- this.points = new Points(this.reglInstance, this.config, this.store, this.graph)
188
- this.lines = new Lines(this.reglInstance, this.config, this.store, this.graph, this.points)
189
- if (this.config.enableSimulation) {
190
- this.forceGravity = new ForceGravity(this.reglInstance, this.config, this.store, this.graph, this.points)
191
- this.forceCenter = new ForceCenter(this.reglInstance, this.config, this.store, this.graph, this.points)
192
- this.forceManyBody = this.config.useClassicQuadtree
193
- ? new ForceManyBodyQuadtree(this.reglInstance, this.config, this.store, this.graph, this.points)
194
- : new ForceManyBody(this.reglInstance, this.config, this.store, this.graph, this.points)
195
- this.forceLinkIncoming = new ForceLink(this.reglInstance, this.config, this.store, this.graph, this.points)
196
- this.forceLinkOutgoing = new ForceLink(this.reglInstance, this.config, this.store, this.graph, this.points)
197
- this.forceMouse = new ForceMouse(this.reglInstance, this.config, this.store, this.graph, this.points)
198
- }
199
- this.clusters = new Clusters(this.reglInstance, this.config, this.store, this.graph, this.points)
200
-
201
- this.store.backgroundColor = getRgbaColor(this.config.backgroundColor)
202
- this.store.setHoveredPointRingColor(this.config.hoveredPointRingColor ?? defaultConfigValues.hoveredPointRingColor)
203
- this.store.setFocusedPointRingColor(this.config.focusedPointRingColor ?? defaultConfigValues.focusedPointRingColor)
204
- if (this.config.focusedPointIndex !== undefined) {
205
- this.store.setFocusedPoint(this.config.focusedPointIndex)
206
- }
207
- this.store.setGreyoutPointColor(this.config.pointGreyoutColor ?? defaultGreyoutPointColor)
208
- this.store.setHoveredLinkColor(this.config.hoveredLinkColor ?? defaultConfigValues.hoveredLinkColor)
209
-
210
- this.store.updateLinkHoveringEnabled(this.config)
211
-
212
- if (this.config.showFPSMonitor) this.fpsMonitor = new FPSMonitor(this.canvas)
213
-
214
- if (this.config.randomSeed !== undefined) this.store.addRandomSeed(this.config.randomSeed)
215
- }
216
-
217
- /**
218
- * Returns the current simulation progress
219
- */
220
- public get progress (): number {
221
- if (this._isDestroyed) return 0
222
- return this.store.simulationProgress
223
- }
224
-
225
- /**
226
- * A value that gives information about the running simulation status.
227
- */
228
- public get isSimulationRunning (): boolean {
229
- if (this._isDestroyed) return false
230
- return this.store.isSimulationRunning
231
- }
232
-
233
- /**
234
- * The maximum point size.
235
- * This value is the maximum size of the `gl.POINTS` primitive that WebGL can render on the user's hardware.
236
- */
237
- public get maxPointSize (): number {
238
- if (this._isDestroyed) return 0
239
- return this.store.maxPointSize
240
- }
241
-
242
- /**
243
- * Set or update Cosmos configuration. The changes will be applied in real time.
244
- * @param config Cosmos configuration object.
245
- */
246
- public setConfig (config: Partial<GraphConfigInterface>): void {
247
- if (this._isDestroyed || !this.reglInstance || !this.points || !this.lines || !this.clusters) return
248
- const prevConfig = { ...this.config }
249
- this.config.init(config)
250
- if (prevConfig.pointColor !== this.config.pointColor) {
251
- this.graph.updatePointColor()
252
- this.points.updateColor()
253
- }
254
- if (prevConfig.pointSize !== this.config.pointSize) {
255
- this.graph.updatePointSize()
256
- this.points.updateSize()
257
- }
258
- if (prevConfig.linkColor !== this.config.linkColor) {
259
- this.graph.updateLinkColor()
260
- this.lines.updateColor()
261
- }
262
- if (prevConfig.linkWidth !== this.config.linkWidth) {
263
- this.graph.updateLinkWidth()
264
- this.lines.updateWidth()
265
- }
266
- if (prevConfig.linkArrows !== this.config.linkArrows) {
267
- this.graph.updateArrows()
268
- this.lines.updateArrow()
269
- }
270
- if (prevConfig.curvedLinkSegments !== this.config.curvedLinkSegments ||
271
- prevConfig.curvedLinks !== this.config.curvedLinks) {
272
- this.lines.updateCurveLineGeometry()
273
- }
274
- if (prevConfig.backgroundColor !== this.config.backgroundColor) {
275
- this.store.backgroundColor = getRgbaColor(this.config.backgroundColor ?? defaultBackgroundColor)
276
- }
277
- if (prevConfig.hoveredPointRingColor !== this.config.hoveredPointRingColor) {
278
- this.store.setHoveredPointRingColor(this.config.hoveredPointRingColor ?? defaultConfigValues.hoveredPointRingColor)
279
- }
280
- if (prevConfig.focusedPointRingColor !== this.config.focusedPointRingColor) {
281
- this.store.setFocusedPointRingColor(this.config.focusedPointRingColor ?? defaultConfigValues.focusedPointRingColor)
282
- }
283
- if (prevConfig.pointGreyoutColor !== this.config.pointGreyoutColor) {
284
- this.store.setGreyoutPointColor(this.config.pointGreyoutColor ?? defaultGreyoutPointColor)
285
- }
286
- if (prevConfig.hoveredLinkColor !== this.config.hoveredLinkColor) {
287
- this.store.setHoveredLinkColor(this.config.hoveredLinkColor ?? defaultConfigValues.hoveredLinkColor)
288
- }
289
- if (prevConfig.focusedPointIndex !== this.config.focusedPointIndex) {
290
- this.store.setFocusedPoint(this.config.focusedPointIndex)
291
- }
292
- if (prevConfig.spaceSize !== this.config.spaceSize ||
293
- prevConfig.simulationRepulsionQuadtreeLevels !== this.config.simulationRepulsionQuadtreeLevels) {
294
- this.store.adjustSpaceSize(this.config.spaceSize, this.reglInstance.limits.maxTextureSize)
295
- this.resizeCanvas(true)
296
- this.update(this.store.isSimulationRunning ? this.store.alpha : 0)
297
- }
298
- if (prevConfig.showFPSMonitor !== this.config.showFPSMonitor) {
299
- if (this.config.showFPSMonitor) {
300
- this.fpsMonitor = new FPSMonitor(this.canvas)
301
- } else {
302
- this.fpsMonitor?.destroy()
303
- this.fpsMonitor = undefined
304
- }
305
- }
306
- if (prevConfig.pixelRatio !== this.config.pixelRatio) {
307
- this.store.maxPointSize = (this.reglInstance.limits.pointSizeDims[1] ?? MAX_POINT_SIZE) / this.config.pixelRatio
308
- }
309
-
310
- if (prevConfig.enableZoom !== this.config.enableZoom || prevConfig.enableDrag !== this.config.enableDrag) {
311
- this.updateZoomDragBehaviors()
312
- }
313
-
314
- if (prevConfig.onLinkClick !== this.config.onLinkClick ||
315
- prevConfig.onLinkMouseOver !== this.config.onLinkMouseOver ||
316
- prevConfig.onLinkMouseOut !== this.config.onLinkMouseOut) {
317
- this.store.updateLinkHoveringEnabled(this.config)
318
- }
319
- }
320
-
321
- /**
322
- * Sets the positions for the graph points.
323
- *
324
- * @param {Float32Array} pointPositions - A Float32Array representing the positions of points in the format [x1, y1, x2, y2, ..., xn, yn],
325
- * where `n` is the index of the point.
326
- * Example: `new Float32Array([1, 2, 3, 4, 5, 6])` sets the first point to (1, 2), the second point to (3, 4), and so on.
327
- * @param {boolean | undefined} dontRescale - For this call only, don't rescale the points.
328
- * - `true`: Don't rescale.
329
- * - `false` or `undefined` (default): Use the behavior defined by `config.rescalePositions`.
330
- */
331
- public setPointPositions (pointPositions: Float32Array, dontRescale?: boolean | undefined): void {
332
- if (this._isDestroyed || !this.points) return
333
- this.graph.inputPointPositions = pointPositions
334
- this.points.shouldSkipRescale = dontRescale
335
- this.isPointPositionsUpdateNeeded = true
336
- // Links related texture depends on point positions, so we need to update it
337
- this.isLinksUpdateNeeded = true
338
- // Point related textures depend on point positions length, so we need to update them
339
- this.isPointColorUpdateNeeded = true
340
- this.isPointSizeUpdateNeeded = true
341
- this.isPointShapeUpdateNeeded = true
342
- this.isPointImageIndicesUpdateNeeded = true
343
- this.isPointClusterUpdateNeeded = true
344
- this.isForceManyBodyUpdateNeeded = true
345
- this.isForceLinkUpdateNeeded = true
346
- this.isForceCenterUpdateNeeded = true
347
- }
348
-
349
- /**
350
- * Sets the colors for the graph points.
351
- *
352
- * @param {Float32Array} pointColors - A Float32Array representing the colors of points in the format [r1, g1, b1, a1, r2, g2, b2, a2, ..., rn, gn, bn, an],
353
- * where each color is represented in RGBA format.
354
- * Example: `new Float32Array([255, 0, 0, 1, 0, 255, 0, 1])` sets the first point to red and the second point to green.
355
- */
356
- public setPointColors (pointColors: Float32Array): void {
357
- if (this._isDestroyed) return
358
- this.graph.inputPointColors = pointColors
359
- this.isPointColorUpdateNeeded = true
360
- }
361
-
362
- /**
363
- * Gets the current colors of the graph points.
364
- *
365
- * @returns {Float32Array} A Float32Array representing the colors of points in the format [r1, g1, b1, a1, r2, g2, b2, a2, ..., rn, gn, bn, an],
366
- * where each color is in RGBA format. Returns an empty Float32Array if no point colors are set.
367
- */
368
- public getPointColors (): Float32Array {
369
- if (this._isDestroyed) return new Float32Array()
370
- return this.graph.pointColors ?? new Float32Array()
371
- }
372
-
373
- /**
374
- * Sets the sizes for the graph points.
375
- *
376
- * @param {Float32Array} pointSizes - A Float32Array representing the sizes of points in the format [size1, size2, ..., sizen],
377
- * where `n` is the index of the point.
378
- * Example: `new Float32Array([10, 20, 30])` sets the first point to size 10, the second point to size 20, and the third point to size 30.
379
- */
380
- public setPointSizes (pointSizes: Float32Array): void {
381
- if (this._isDestroyed) return
382
- this.graph.inputPointSizes = pointSizes
383
- this.isPointSizeUpdateNeeded = true
384
- }
385
-
386
- /**
387
- * Sets the shapes for the graph points.
388
- *
389
- * @param {Float32Array} pointShapes - A Float32Array representing the shapes of points in the format [shape1, shape2, ..., shapen],
390
- * where `n` is the index of the point and each shape value corresponds to a PointShape enum:
391
- * 0 = Circle, 1 = Square, 2 = Triangle, 3 = Diamond, 4 = Pentagon, 5 = Hexagon, 6 = Star, 7 = Cross, 8 = None.
392
- * Example: `new Float32Array([0, 1, 2])` sets the first point to Circle, the second point to Square, and the third point to Triangle.
393
- * Images are rendered above shapes.
394
- */
395
- public setPointShapes (pointShapes: Float32Array): void {
396
- if (this._isDestroyed) return
397
- this.graph.inputPointShapes = pointShapes
398
- this.isPointShapeUpdateNeeded = true
399
- }
400
-
401
- /**
402
- * Sets the images for the graph points using ImageData objects.
403
- * Images are rendered above shapes.
404
- * To use images, provide image indices via setPointImageIndices().
405
- *
406
- * @param {ImageData[]} imageDataArray - Array of ImageData objects to use as point images.
407
- * Example: `setImageData([imageData1, imageData2, imageData3])`
408
- */
409
- public setImageData (imageDataArray: ImageData[]): void {
410
- if (this._isDestroyed || !this.points) return
411
- this.graph.inputImageData = imageDataArray
412
- this.points.createAtlas()
413
- }
414
-
415
- /**
416
- * Sets which image each point should use from the images array.
417
- * Images are rendered above shapes.
418
- *
419
- * @param {Float32Array} imageIndices - A Float32Array representing which image each point uses in the format [index1, index2, ..., indexn],
420
- * where `n` is the index of the point and each value is an index into the images array provided to `setImageData`.
421
- * Example: `new Float32Array([0, 1, 0])` sets the first point to use image 0, second point to use image 1, third point to use image 0.
422
- */
423
- public setPointImageIndices (imageIndices: Float32Array): void {
424
- if (this._isDestroyed) return
425
- this.graph.inputPointImageIndices = imageIndices
426
- this.isPointImageIndicesUpdateNeeded = true
427
- }
428
-
429
- /**
430
- * Sets the sizes for the point images.
431
- *
432
- * @param {Float32Array} imageSizes - A Float32Array representing the sizes of point images in the format [size1, size2, ..., sizen],
433
- * where `n` is the index of the point.
434
- * Example: `new Float32Array([10, 20, 30])` sets the first image to size 10, the second image to size 20, and the third image to size 30.
435
- */
436
- public setPointImageSizes (imageSizes: Float32Array): void {
437
- if (this._isDestroyed) return
438
- this.graph.inputPointImageSizes = imageSizes
439
- this.isPointImageSizesUpdateNeeded = true
440
- }
441
-
442
- /**
443
- * Gets the current sizes of the graph points.
444
- *
445
- * @returns {Float32Array} A Float32Array representing the sizes of points in the format [size1, size2, ..., sizen],
446
- * where `n` is the index of the point. Returns an empty Float32Array if no point sizes are set.
447
- */
448
- public getPointSizes (): Float32Array {
449
- if (this._isDestroyed) return new Float32Array()
450
- return this.graph.pointSizes ?? new Float32Array()
451
- }
452
-
453
- /**
454
- * Sets the links for the graph.
455
- *
456
- * @param {Float32Array} links - A Float32Array representing the links between points
457
- * in the format [source1, target1, source2, target2, ..., sourcen, targetn],
458
- * where `source` and `target` are the indices of the points being linked.
459
- * Example: `new Float32Array([0, 1, 1, 2])` creates a link from point 0 to point 1 and another link from point 1 to point 2.
460
- */
461
- public setLinks (links: Float32Array): void {
462
- if (this._isDestroyed) return
463
- this.graph.inputLinks = links
464
- this.isLinksUpdateNeeded = true
465
- // Links related texture depends on links length, so we need to update it
466
- this.isLinkColorUpdateNeeded = true
467
- this.isLinkWidthUpdateNeeded = true
468
- this.isLinkArrowUpdateNeeded = true
469
- this.isForceLinkUpdateNeeded = true
470
- }
471
-
472
- /**
473
- * Sets the colors for the graph links.
474
- *
475
- * @param {Float32Array} linkColors - A Float32Array representing the colors of links in the format [r1, g1, b1, a1, r2, g2, b2, a2, ..., rn, gn, bn, an],
476
- * where each color is in RGBA format.
477
- * Example: `new Float32Array([255, 0, 0, 1, 0, 255, 0, 1])` sets the first link to red and the second link to green.
478
- */
479
- public setLinkColors (linkColors: Float32Array): void {
480
- if (this._isDestroyed) return
481
- this.graph.inputLinkColors = linkColors
482
- this.isLinkColorUpdateNeeded = true
483
- }
484
-
485
- /**
486
- * Gets the current colors of the graph links.
487
- *
488
- * @returns {Float32Array} A Float32Array representing the colors of links in the format [r1, g1, b1, a1, r2, g2, b2, a2, ..., rn, gn, bn, an],
489
- * where each color is in RGBA format. Returns an empty Float32Array if no link colors are set.
490
- */
491
- public getLinkColors (): Float32Array {
492
- if (this._isDestroyed) return new Float32Array()
493
- return this.graph.linkColors ?? new Float32Array()
494
- }
495
-
496
- /**
497
- * Sets the widths for the graph links.
498
- *
499
- * @param {Float32Array} linkWidths - A Float32Array representing the widths of links in the format [width1, width2, ..., widthn],
500
- * where `n` is the index of the link.
501
- * Example: `new Float32Array([1, 2, 3])` sets the first link to width 1, the second link to width 2, and the third link to width 3.
502
- */
503
- public setLinkWidths (linkWidths: Float32Array): void {
504
- if (this._isDestroyed) return
505
- this.graph.inputLinkWidths = linkWidths
506
- this.isLinkWidthUpdateNeeded = true
507
- }
508
-
509
- /**
510
- * Gets the current widths of the graph links.
511
- *
512
- * @returns {Float32Array} A Float32Array representing the widths of links in the format [width1, width2, ..., widthn],
513
- * where `n` is the index of the link. Returns an empty Float32Array if no link widths are set.
514
- */
515
- public getLinkWidths (): Float32Array {
516
- if (this._isDestroyed) return new Float32Array()
517
- return this.graph.linkWidths ?? new Float32Array()
518
- }
519
-
520
- /**
521
- * Sets the arrows for the graph links.
522
- *
523
- * @param {boolean[]} linkArrows - An array of booleans indicating whether each link should have an arrow,
524
- * in the format [arrow1, arrow2, ..., arrown], where `n` is the index of the link.
525
- * Example: `[true, false, true]` sets arrows on the first and third links, but not on the second link.
526
- */
527
- public setLinkArrows (linkArrows: boolean[]): void {
528
- if (this._isDestroyed) return
529
- this.graph.linkArrowsBoolean = linkArrows
530
- this.isLinkArrowUpdateNeeded = true
531
- }
532
-
533
- /**
534
- * Sets the strength for the graph links.
535
- *
536
- * @param {Float32Array} linkStrength - A Float32Array representing the strength of each link in the format [strength1, strength2, ..., strengthn],
537
- * where `n` is the index of the link.
538
- * Example: `new Float32Array([1, 2, 3])` sets the first link to strength 1, the second link to strength 2, and the third link to strength 3.
539
- */
540
- public setLinkStrength (linkStrength: Float32Array): void {
541
- if (this._isDestroyed) return
542
- this.graph.inputLinkStrength = linkStrength
543
- this.isForceLinkUpdateNeeded = true
544
- }
545
-
546
- /**
547
- * Sets the point clusters for the graph.
548
- *
549
- * @param {(number | undefined)[]} pointClusters - Array of cluster indices for each point in the graph.
550
- * - Index: Each index corresponds to a point.
551
- * - Values: Integers starting from 0; `undefined` indicates that a point does not belong to any cluster and will not be affected by cluster forces.
552
- * @example
553
- * `[0, 1, 0, 2, undefined, 1]` maps points to clusters: point 0 and 2 to cluster 0, point 1 to cluster 1, and point 3 to cluster 2.
554
- * Points 4 is unclustered.
555
- * @note Clusters without specified positions via `setClusterPositions` will be positioned at their centermass by default.
556
- */
557
- public setPointClusters (pointClusters: (number | undefined)[]): void {
558
- if (this._isDestroyed) return
559
- this.graph.inputPointClusters = pointClusters
560
- this.isPointClusterUpdateNeeded = true
561
- }
562
-
563
- /**
564
- * Sets the positions of the point clusters for the graph.
565
- *
566
- * @param {(number | undefined)[]} clusterPositions - Array of cluster positions.
567
- * - Every two elements represent the x and y coordinates for a cluster position.
568
- * - `undefined` means the cluster's position is not defined and will use centermass positioning instead.
569
- * @example
570
- * `[10, 20, 30, 40, undefined, undefined]` places the first cluster at (10, 20) and the second at (30, 40);
571
- * the third cluster will be positioned at its centermass automatically.
572
- */
573
- public setClusterPositions (clusterPositions: (number | undefined)[]): void {
574
- if (this._isDestroyed) return
575
- this.graph.inputClusterPositions = clusterPositions
576
- this.isPointClusterUpdateNeeded = true
577
- }
578
-
579
- /**
580
- * Sets the force strength coefficients for clustering points in the graph.
581
- *
582
- * This method allows you to customize the forces acting on individual points during the clustering process.
583
- * The force coefficients determine the strength of the forces applied to each point.
584
- *
585
- * @param {Float32Array} clusterStrength - A Float32Array of force strength coefficients for each point in the format [coeff1, coeff2, ..., coeffn],
586
- * where `n` is the index of the point.
587
- * Example: `new Float32Array([1, 0.4, 0.3])` sets the force coefficient for point 0 to 1, point 1 to 0.4, and point 2 to 0.3.
588
- */
589
- public setPointClusterStrength (clusterStrength: Float32Array): void {
590
- if (this._isDestroyed) return
591
- this.graph.inputClusterStrength = clusterStrength
592
- this.isPointClusterUpdateNeeded = true
593
- }
594
-
595
- /**
596
- * Renders the graph.
597
- *
598
- * @param {number} [simulationAlpha] - Optional value between 0 and 1
599
- * that controls the initial energy of the simulation.The higher the value,
600
- * the more initial energy the simulation will get. Zero value stops the simulation.
601
- */
602
- public render (simulationAlpha?: number): void {
603
- if (this._isDestroyed || !this.reglInstance) return
604
- this.graph.update()
605
- const { fitViewOnInit, fitViewDelay, fitViewPadding, fitViewDuration, fitViewByPointsInRect, fitViewByPointIndices, initialZoomLevel } = this.config
606
- if (!this.graph.pointsNumber && !this.graph.linksNumber) {
607
- this.stopFrames()
608
- select(this.canvas).style('cursor', null)
609
- this.reglInstance.clear({
610
- color: this.store.backgroundColor,
611
- depth: 1,
612
- stencil: 0,
613
- })
614
- return
615
- }
616
-
617
- // If `initialZoomLevel` is set, we don't need to fit the view
618
- if (this._isFirstRenderAfterInit && fitViewOnInit && initialZoomLevel === undefined) {
619
- this._fitViewOnInitTimeoutID = window.setTimeout(() => {
620
- if (fitViewByPointIndices) this.fitViewByPointIndices(fitViewByPointIndices, fitViewDuration, fitViewPadding)
621
- else if (fitViewByPointsInRect) this.setZoomTransformByPointPositions(fitViewByPointsInRect, fitViewDuration, undefined, fitViewPadding)
622
- else this.fitView(fitViewDuration, fitViewPadding)
623
- }, fitViewDelay)
624
- }
625
- this._isFirstRenderAfterInit = false
626
-
627
- this.update(simulationAlpha)
628
- }
629
-
630
- /**
631
- * Center the view on a point and zoom in, by point index.
632
- * @param index The index of the point in the array of points.
633
- * @param duration Duration of the animation transition in milliseconds (`700` by default).
634
- * @param scale Scale value to zoom in or out (`3` by default).
635
- * @param canZoomOut Set to `false` to prevent zooming out from the point (`true` by default).
636
- */
637
- public zoomToPointByIndex (index: number, duration = 700, scale = defaultScaleToZoom, canZoomOut = true): void {
638
- if (this._isDestroyed || !this.reglInstance || !this.points || !this.canvasD3Selection) return
639
- const { store: { screenSize } } = this
640
- const positionPixels = readPixels(this.reglInstance, this.points.currentPositionFbo as regl.Framebuffer2D)
641
- if (index === undefined) return
642
- const posX = positionPixels[index * 4 + 0]
643
- const posY = positionPixels[index * 4 + 1]
644
- if (posX === undefined || posY === undefined) return
645
- const distance = this.zoomInstance.getDistanceToPoint([posX, posY])
646
- const zoomLevel = canZoomOut ? scale : Math.max(this.getZoomLevel(), scale)
647
- if (distance < Math.min(screenSize[0], screenSize[1])) {
648
- this.setZoomTransformByPointPositions([posX, posY], duration, zoomLevel)
649
- } else {
650
- const transform = this.zoomInstance.getTransform([[posX, posY]], zoomLevel)
651
- const middle = this.zoomInstance.getMiddlePointTransform([posX, posY])
652
- this.canvasD3Selection
653
- .transition()
654
- .ease(easeQuadIn)
655
- .duration(duration / 2)
656
- .call(this.zoomInstance.behavior.transform, middle)
657
- .transition()
658
- .ease(easeQuadOut)
659
- .duration(duration / 2)
660
- .call(this.zoomInstance.behavior.transform, transform)
661
- }
662
- }
663
-
664
- /**
665
- * Zoom the view in or out to the specified zoom level.
666
- * @param value Zoom level
667
- * @param duration Duration of the zoom in/out transition.
668
- */
669
-
670
- public zoom (value: number, duration = 0): void {
671
- if (this._isDestroyed) return
672
- this.setZoomLevel(value, duration)
673
- }
674
-
675
- /**
676
- * Zoom the view in or out to the specified zoom level.
677
- * @param value Zoom level
678
- * @param duration Duration of the zoom in/out transition.
679
- */
680
- public setZoomLevel (value: number, duration = 0): void {
681
- if (this._isDestroyed || !this.canvasD3Selection) return
682
- if (duration === 0) {
683
- this.canvasD3Selection
684
- .call(this.zoomInstance.behavior.scaleTo, value)
685
- } else {
686
- this.canvasD3Selection
687
- .transition()
688
- .duration(duration)
689
- .call(this.zoomInstance.behavior.scaleTo, value)
690
- }
691
- }
692
-
693
- /**
694
- * Get zoom level.
695
- * @returns Zoom level value of the view.
696
- */
697
- public getZoomLevel (): number {
698
- if (this._isDestroyed) return 0
699
- return this.zoomInstance.eventTransform.k
700
- }
701
-
702
- /**
703
- * Get current X and Y coordinates of the points.
704
- * @returns Array of point positions.
705
- */
706
- public getPointPositions (): number[] {
707
- if (this._isDestroyed || !this.reglInstance || !this.points) return []
708
- if (this.graph.pointsNumber === undefined) return []
709
- const positions: number[] = []
710
- const pointPositionsPixels = readPixels(this.reglInstance, this.points.currentPositionFbo as regl.Framebuffer2D)
711
- positions.length = this.graph.pointsNumber * 2
712
- for (let i = 0; i < this.graph.pointsNumber; i += 1) {
713
- const posX = pointPositionsPixels[i * 4 + 0]
714
- const posY = pointPositionsPixels[i * 4 + 1]
715
- if (posX !== undefined && posY !== undefined) {
716
- positions[i * 2] = posX
717
- positions[i * 2 + 1] = posY
718
- }
719
- }
720
- return positions
721
- }
722
-
723
- /**
724
- * Get current X and Y coordinates of the clusters.
725
- * @returns Array of point cluster.
726
- */
727
- public getClusterPositions (): number[] {
728
- if (this._isDestroyed || !this.reglInstance || !this.clusters) return []
729
- if (this.graph.pointClusters === undefined || this.clusters.clusterCount === undefined) return []
730
- this.clusters.calculateCentermass()
731
- const positions: number[] = []
732
- const clusterPositionsPixels = readPixels(this.reglInstance, this.clusters.centermassFbo as regl.Framebuffer2D)
733
- positions.length = this.clusters.clusterCount * 2
734
- for (let i = 0; i < positions.length / 2; i += 1) {
735
- const sumX = clusterPositionsPixels[i * 4 + 0]
736
- const sumY = clusterPositionsPixels[i * 4 + 1]
737
- const sumN = clusterPositionsPixels[i * 4 + 2]
738
- if (sumX !== undefined && sumY !== undefined && sumN !== undefined) {
739
- positions[i * 2] = sumX / sumN
740
- positions[i * 2 + 1] = sumY / sumN
741
- }
742
- }
743
- return positions
744
- }
745
-
746
- /**
747
- * Center and zoom in/out the view to fit all points in the scene.
748
- * @param duration Duration of the center and zoom in/out animation in milliseconds (`250` by default).
749
- * @param padding Padding around the viewport in percentage (`0.1` by default).
750
- */
751
- public fitView (duration = 250, padding = 0.1): void {
752
- if (this._isDestroyed) return
753
- this.setZoomTransformByPointPositions(this.getPointPositions(), duration, undefined, padding)
754
- }
755
-
756
- /**
757
- * Center and zoom in/out the view to fit points by their indices in the scene.
758
- * @param duration Duration of the center and zoom in/out animation in milliseconds (`250` by default).
759
- * @param padding Padding around the viewport in percentage
760
- */
761
- public fitViewByPointIndices (indices: number[], duration = 250, padding = 0.1): void {
762
- if (this._isDestroyed) return
763
- const positionsArray = this.getPointPositions()
764
- const positions = new Array(indices.length * 2)
765
- for (const [i, index] of indices.entries()) {
766
- positions[i * 2] = positionsArray[index * 2]
767
- positions[i * 2 + 1] = positionsArray[index * 2 + 1]
768
- }
769
- this.setZoomTransformByPointPositions(positions, duration, undefined, padding)
770
- }
771
-
772
- /**
773
- * Center and zoom in/out the view to fit points by their positions in the scene.
774
- * @param duration Duration of the center and zoom in/out animation in milliseconds (`250` by default).
775
- * @param padding Padding around the viewport in percentage
776
- */
777
- public fitViewByPointPositions (positions: number[], duration = 250, padding = 0.1): void {
778
- if (this._isDestroyed) return
779
- this.setZoomTransformByPointPositions(positions, duration, undefined, padding)
780
- }
781
-
782
- /**
783
- * Get points indices inside a rectangular area.
784
- * @param selection - Array of two corner points `[[left, top], [right, bottom]]`.
785
- * The `left` and `right` coordinates should be from 0 to the width of the canvas.
786
- * The `top` and `bottom` coordinates should be from 0 to the height of the canvas.
787
- * @returns A Float32Array containing the indices of points inside a rectangular area.
788
- */
789
- public getPointsInRect (selection: [[number, number], [number, number]]): Float32Array {
790
- if (this._isDestroyed || !this.reglInstance || !this.points) return new Float32Array()
791
- const h = this.store.screenSize[1]
792
- this.store.selectedArea = [[selection[0][0], (h - selection[1][1])], [selection[1][0], (h - selection[0][1])]]
793
- this.points.findPointsOnAreaSelection()
794
- const pixels = readPixels(this.reglInstance, this.points.selectedFbo as regl.Framebuffer2D)
795
-
796
- return pixels
797
- .map((pixel, i) => {
798
- if (i % 4 === 0 && pixel !== 0) return i / 4
799
- else return -1
800
- })
801
- .filter(d => d !== -1)
802
- }
803
-
804
- /**
805
- * Get points indices inside a rectangular area.
806
- * @param selection - Array of two corner points `[[left, top], [right, bottom]]`.
807
- * The `left` and `right` coordinates should be from 0 to the width of the canvas.
808
- * The `top` and `bottom` coordinates should be from 0 to the height of the canvas.
809
- * @returns A Float32Array containing the indices of points inside a rectangular area.
810
- * @deprecated Use `getPointsInRect` instead. This method will be removed in a future version.
811
- */
812
- public getPointsInRange (selection: [[number, number], [number, number]]): Float32Array {
813
- return this.getPointsInRect(selection)
814
- }
815
-
816
- /**
817
- * Get points indices inside a polygon area.
818
- * @param polygonPath - Array of points `[[x1, y1], [x2, y2], ..., [xn, yn]]` that defines the polygon.
819
- * The coordinates should be from 0 to the width/height of the canvas.
820
- * @returns A Float32Array containing the indices of points inside the polygon area.
821
- */
822
- public getPointsInPolygon (polygonPath: [number, number][]): Float32Array {
823
- if (this._isDestroyed || !this.reglInstance || !this.points) return new Float32Array()
824
- if (polygonPath.length < 3) return new Float32Array() // Need at least 3 points for a polygon
825
-
826
- const h = this.store.screenSize[1]
827
- // Convert coordinates to WebGL coordinate system (flip Y)
828
- const convertedPath = polygonPath.map(([x, y]) => [x, h - y] as [number, number])
829
- this.points.updatePolygonPath(convertedPath)
830
- this.points.findPointsOnPolygonSelection()
831
- const pixels = readPixels(this.reglInstance, this.points.selectedFbo as regl.Framebuffer2D)
832
-
833
- return pixels
834
- .map((pixel, i) => {
835
- if (i % 4 === 0 && pixel !== 0) return i / 4
836
- else return -1
837
- })
838
- .filter(d => d !== -1)
839
- }
840
-
841
- /** Select points inside a rectangular area.
842
- * @param selection - Array of two corner points `[[left, top], [right, bottom]]`.
843
- * The `left` and `right` coordinates should be from 0 to the width of the canvas.
844
- * The `top` and `bottom` coordinates should be from 0 to the height of the canvas. */
845
- public selectPointsInRect (selection: [[number, number], [number, number]] | null): void {
846
- if (this._isDestroyed || !this.reglInstance || !this.points) return
847
- if (selection) {
848
- const h = this.store.screenSize[1]
849
- this.store.selectedArea = [[selection[0][0], (h - selection[1][1])], [selection[1][0], (h - selection[0][1])]]
850
- this.points.findPointsOnAreaSelection()
851
- const pixels = readPixels(this.reglInstance, this.points.selectedFbo as regl.Framebuffer2D)
852
- this.store.selectedIndices = pixels
853
- .map((pixel, i) => {
854
- if (i % 4 === 0 && pixel !== 0) return i / 4
855
- else return -1
856
- })
857
- .filter(d => d !== -1)
858
- } else {
859
- this.store.selectedIndices = null
860
- }
861
- this.points.updateGreyoutStatus()
862
- }
863
-
864
- /** Select points inside a rectangular area.
865
- * @param selection - Array of two corner points `[[left, top], [right, bottom]]`.
866
- * The `left` and `right` coordinates should be from 0 to the width of the canvas.
867
- * The `top` and `bottom` coordinates should be from 0 to the height of the canvas.
868
- * @deprecated Use `selectPointsInRect` instead. This method will be removed in a future version.
869
- */
870
- public selectPointsInRange (selection: [[number, number], [number, number]] | null): void {
871
- return this.selectPointsInRect(selection)
872
- }
873
-
874
- /** Select points inside a polygon area.
875
- * @param polygonPath - Array of points `[[x1, y1], [x2, y2], ..., [xn, yn]]` that defines the polygon.
876
- * The coordinates should be from 0 to the width/height of the canvas.
877
- * Set to null to clear selection. */
878
- public selectPointsInPolygon (polygonPath: [number, number][] | null): void {
879
- if (this._isDestroyed || !this.reglInstance || !this.points) return
880
- if (polygonPath) {
881
- if (polygonPath.length < 3) {
882
- console.warn('Polygon path requires at least 3 points to form a polygon.')
883
- return
884
- }
885
-
886
- const h = this.store.screenSize[1]
887
- // Convert coordinates to WebGL coordinate system (flip Y)
888
- const convertedPath = polygonPath.map(([x, y]) => [x, h - y] as [number, number])
889
- this.points.updatePolygonPath(convertedPath)
890
- this.points.findPointsOnPolygonSelection()
891
- const pixels = readPixels(this.reglInstance, this.points.selectedFbo as regl.Framebuffer2D)
892
- this.store.selectedIndices = pixels
893
- .map((pixel, i) => {
894
- if (i % 4 === 0 && pixel !== 0) return i / 4
895
- else return -1
896
- })
897
- .filter(d => d !== -1)
898
- } else {
899
- this.store.selectedIndices = null
900
- }
901
- this.points.updateGreyoutStatus()
902
- }
903
-
904
- /**
905
- * Select a point by index. If you want the adjacent points to get selected too, provide `true` as the second argument.
906
- * @param index The index of the point in the array of points.
907
- * @param selectAdjacentPoints When set to `true`, selects adjacent points (`false` by default).
908
- */
909
- public selectPointByIndex (index: number, selectAdjacentPoints = false): void {
910
- if (this._isDestroyed) return
911
- if (selectAdjacentPoints) {
912
- const adjacentIndices = this.graph.getAdjacentIndices(index) ?? []
913
- this.selectPointsByIndices([index, ...adjacentIndices])
914
- } else this.selectPointsByIndices([index])
915
- }
916
-
917
- /**
918
- * Select multiples points by their indices.
919
- * @param indices Array of points indices.
920
- */
921
- public selectPointsByIndices (indices?: (number | undefined)[] | null): void {
922
- if (this._isDestroyed || !this.points) return
923
- if (!indices) {
924
- this.store.selectedIndices = null
925
- } else if (indices.length === 0) {
926
- this.store.selectedIndices = new Float32Array()
927
- } else {
928
- this.store.selectedIndices = new Float32Array(indices.filter(d => d !== undefined))
929
- }
930
-
931
- this.points.updateGreyoutStatus()
932
- }
933
-
934
- /**
935
- * Unselect all points.
936
- */
937
- public unselectPoints (): void {
938
- if (this._isDestroyed || !this.points) return
939
- this.store.selectedIndices = null
940
- this.points.updateGreyoutStatus()
941
- }
942
-
943
- /**
944
- * Get indices of points that are currently selected.
945
- * @returns Array of selected indices of points.
946
- */
947
- public getSelectedIndices (): number[] | null {
948
- if (this._isDestroyed) return null
949
- const { selectedIndices } = this.store
950
- if (!selectedIndices) return null
951
- return Array.from(selectedIndices)
952
- }
953
-
954
- /**
955
- * Get indices that are adjacent to a specific point by its index.
956
- * @param index Index of the point.
957
- * @returns Array of adjacent indices.
958
- */
959
-
960
- public getAdjacentIndices (index: number): number[] | undefined {
961
- if (this._isDestroyed) return undefined
962
- return this.graph.getAdjacentIndices(index)
963
- }
964
-
965
- /**
966
- * Converts the X and Y point coordinates from the space coordinate system to the screen coordinate system.
967
- * @param spacePosition Array of x and y coordinates in the space coordinate system.
968
- * @returns Array of x and y coordinates in the screen coordinate system.
969
- */
970
- public spaceToScreenPosition (spacePosition: [number, number]): [number, number] {
971
- if (this._isDestroyed) return [0, 0]
972
- return this.zoomInstance.convertSpaceToScreenPosition(spacePosition)
973
- }
974
-
975
- /**
976
- * Converts the X and Y point coordinates from the screen coordinate system to the space coordinate system.
977
- * @param screenPosition Array of x and y coordinates in the screen coordinate system.
978
- * @returns Array of x and y coordinates in the space coordinate system.
979
- */
980
- public screenToSpacePosition (screenPosition: [number, number]): [number, number] {
981
- if (this._isDestroyed) return [0, 0]
982
- return this.zoomInstance.convertScreenToSpacePosition(screenPosition)
983
- }
984
-
985
- /**
986
- * Converts the point radius value from the space coordinate system to the screen coordinate system.
987
- * @param spaceRadius Radius of point in the space coordinate system.
988
- * @returns Radius of point in the screen coordinate system.
989
- */
990
- public spaceToScreenRadius (spaceRadius: number): number {
991
- if (this._isDestroyed) return 0
992
- return this.zoomInstance.convertSpaceToScreenRadius(spaceRadius)
993
- }
994
-
995
- /**
996
- * Get point radius by its index.
997
- * @param index Index of the point.
998
- * @returns Radius of the point.
999
- */
1000
- public getPointRadiusByIndex (index: number): number | undefined {
1001
- if (this._isDestroyed) return undefined
1002
- return this.graph.pointSizes?.[index]
1003
- }
1004
-
1005
- /**
1006
- * Track multiple point positions by their indices on each Cosmos tick.
1007
- * @param indices Array of points indices.
1008
- */
1009
- public trackPointPositionsByIndices (indices: number[]): void {
1010
- if (this._isDestroyed || !this.points) return
1011
- this.points.trackPointsByIndices(indices)
1012
- }
1013
-
1014
- /**
1015
- * Get current X and Y coordinates of the tracked points.
1016
- * Do not mutate the returned map - it may affect future calls.
1017
- * @returns A ReadonlyMap where keys are point indices and values are their corresponding X and Y coordinates in the [number, number] format.
1018
- * @see trackPointPositionsByIndices To set which points should be tracked
1019
- */
1020
- public getTrackedPointPositionsMap (): ReadonlyMap<number, [number, number]> {
1021
- if (this._isDestroyed || !this.points) return new Map()
1022
- return this.points.getTrackedPositionsMap()
1023
- }
1024
-
1025
- /**
1026
- * Get current X and Y coordinates of the tracked points as an array.
1027
- * @returns Array of point positions in the format [x1, y1, x2, y2, ..., xn, yn] for tracked points only.
1028
- * The positions are ordered by the tracking indices (same order as provided to trackPointPositionsByIndices).
1029
- * Returns an empty array if no points are being tracked.
1030
- */
1031
- public getTrackedPointPositionsArray (): number[] {
1032
- if (this._isDestroyed || !this.points) return []
1033
- return this.points.getTrackedPositionsArray()
1034
- }
1035
-
1036
- /**
1037
- * For the points that are currently visible on the screen, get a sample of point indices with their coordinates.
1038
- * The resulting number of points will depend on the `pointSamplingDistance` configuration property,
1039
- * and the sampled points will be evenly distributed.
1040
- * @returns A Map object where keys are the index of the points and values are their corresponding X and Y coordinates in the [number, number] format.
1041
- */
1042
- public getSampledPointPositionsMap (): Map<number, [number, number]> {
1043
- if (this._isDestroyed || !this.points) return new Map()
1044
- return this.points.getSampledPointPositionsMap()
1045
- }
1046
-
1047
- /**
1048
- * For the points that are currently visible on the screen, get a sample of point indices and positions.
1049
- * The resulting number of points will depend on the `pointSamplingDistance` configuration property,
1050
- * and the sampled points will be evenly distributed.
1051
- * @returns An object containing arrays of point indices and positions.
1052
- */
1053
- public getSampledPoints (): { indices: number[]; positions: number[] } {
1054
- if (this._isDestroyed || !this.points) return { indices: [], positions: [] }
1055
- return this.points.getSampledPoints()
1056
- }
1057
-
1058
- /**
1059
- * Gets the X-axis of rescaling function.
1060
- *
1061
- * This scale is automatically created when position rescaling is enabled.
1062
- */
1063
- public getScaleX (): ((x: number) => number) | undefined {
1064
- if (this._isDestroyed || !this.points) return undefined
1065
- return this.points.scaleX
1066
- }
1067
-
1068
- /**
1069
- * Gets the Y-axis of rescaling function.
1070
- *
1071
- * This scale is automatically created when position rescaling is enabled.
1072
- */
1073
- public getScaleY (): ((y: number) => number) | undefined {
1074
- if (this._isDestroyed || !this.points) return undefined
1075
- return this.points.scaleY
1076
- }
1077
-
1078
- /**
1079
- * Start the simulation.
1080
- * @param alpha Value from 0 to 1. The higher the value, the more initial energy the simulation will get.
1081
- */
1082
- public start (alpha = 1): void {
1083
- if (this._isDestroyed) return
1084
- if (!this.graph.pointsNumber) return
1085
-
1086
- // Only start the simulation if alpha > 0
1087
- if (alpha > 0) {
1088
- this.store.isSimulationRunning = true
1089
- this.store.simulationProgress = 0
1090
- this.config.onSimulationStart?.()
1091
- }
1092
-
1093
- this.store.alpha = alpha
1094
- this.stopFrames()
1095
- this.frame()
1096
- }
1097
-
1098
- /**
1099
- * Pause the simulation. When paused, the simulation stops running
1100
- * and can be resumed using the unpause method.
1101
- */
1102
- public pause (): void {
1103
- if (this._isDestroyed) return
1104
- this.store.isSimulationRunning = false
1105
- this.config.onSimulationPause?.()
1106
- }
1107
-
1108
- /**
1109
- * Unpause the simulation. This method resumes a paused
1110
- * simulation and continues its execution.
1111
- */
1112
- public unpause (): void {
1113
- if (this._isDestroyed) return
1114
- this.store.isSimulationRunning = true
1115
- this.config.onSimulationUnpause?.()
1116
- }
1117
-
1118
- /**
1119
- * Restart/Resume the simulation. This method unpauses a paused
1120
- * simulation and resumes its execution.
1121
- * @deprecated Use `unpause()` instead. This method will be removed in a future version.
1122
- */
1123
- public restart (): void {
1124
- if (this._isDestroyed) return
1125
- this.store.isSimulationRunning = true
1126
- this.config.onSimulationRestart?.()
1127
- }
1128
-
1129
- /**
1130
- * Render only one frame of the simulation (stops the simulation if it was running).
1131
- */
1132
- public step (): void {
1133
- if (this._isDestroyed) return
1134
- this.store.isSimulationRunning = false
1135
- this.stopFrames()
1136
- this.frame()
1137
- }
1138
-
1139
- /**
1140
- * Destroy this Cosmos instance.
1141
- */
1142
- public destroy (): void {
1143
- if (this._isDestroyed || !this.reglInstance) return
1144
- window.clearTimeout(this._fitViewOnInitTimeoutID)
1145
- this.stopFrames()
1146
-
1147
- // Remove all event listeners
1148
- if (this.canvasD3Selection) {
1149
- this.canvasD3Selection
1150
- .on('mouseenter.cosmos', null)
1151
- .on('mousemove.cosmos', null)
1152
- .on('mouseleave.cosmos', null)
1153
- .on('click', null)
1154
- .on('mousemove', null)
1155
- .on('contextmenu', null)
1156
- .on('.drag', null)
1157
- .on('.zoom', null)
1158
- }
1159
-
1160
- select(document)
1161
- .on('keydown.cosmos', null)
1162
- .on('keyup.cosmos', null)
1163
-
1164
- if (this.zoomInstance?.behavior) {
1165
- this.zoomInstance.behavior
1166
- .on('start.detect', null)
1167
- .on('zoom.detect', null)
1168
- .on('end.detect', null)
1169
- }
1170
-
1171
- if (this.dragInstance?.behavior) {
1172
- this.dragInstance.behavior
1173
- .on('start.detect', null)
1174
- .on('drag.detect', null)
1175
- .on('end.detect', null)
1176
- }
1177
-
1178
- this.fpsMonitor?.destroy()
1179
- this.reglInstance.destroy()
1180
- // Clears the canvas after particle system is destroyed
1181
- this.reglInstance.clear({
1182
- color: this.store.backgroundColor,
1183
- depth: 1,
1184
- stencil: 0,
1185
- })
1186
-
1187
- if (this.canvas && this.canvas.parentNode) {
1188
- this.canvas.parentNode.removeChild(this.canvas)
1189
- }
1190
-
1191
- if (this.attributionDivElement && this.attributionDivElement.parentNode) {
1192
- this.attributionDivElement.parentNode.removeChild(this.attributionDivElement)
1193
- }
1194
-
1195
- document.getElementById('gl-bench-style')?.remove()
1196
-
1197
- this.canvasD3Selection = undefined
1198
- this.reglInstance = undefined
1199
- this.attributionDivElement = undefined
1200
-
1201
- this._isDestroyed = true
1202
- }
1203
-
1204
- /**
1205
- * Updates and recreates the graph visualization based on pending changes.
1206
- */
1207
- public create (): void {
1208
- if (this._isDestroyed || !this.points || !this.lines) return
1209
- if (this.isPointPositionsUpdateNeeded) this.points.updatePositions()
1210
- if (this.isPointColorUpdateNeeded) this.points.updateColor()
1211
- if (this.isPointSizeUpdateNeeded) this.points.updateSize()
1212
- if (this.isPointShapeUpdateNeeded) this.points.updateShape()
1213
- if (this.isPointImageIndicesUpdateNeeded) this.points.updateImageIndices()
1214
- if (this.isPointImageSizesUpdateNeeded) this.points.updateImageSizes()
1215
-
1216
- if (this.isLinksUpdateNeeded) this.lines.updatePointsBuffer()
1217
- if (this.isLinkColorUpdateNeeded) this.lines.updateColor()
1218
- if (this.isLinkWidthUpdateNeeded) this.lines.updateWidth()
1219
- if (this.isLinkArrowUpdateNeeded) this.lines.updateArrow()
1220
-
1221
- if (this.isForceManyBodyUpdateNeeded) this.forceManyBody?.create()
1222
- if (this.isForceLinkUpdateNeeded) {
1223
- this.forceLinkIncoming?.create(LinkDirection.INCOMING)
1224
- this.forceLinkOutgoing?.create(LinkDirection.OUTGOING)
1225
- }
1226
- if (this.isForceCenterUpdateNeeded) this.forceCenter?.create()
1227
- if (this.isPointClusterUpdateNeeded) this.clusters?.create()
1228
-
1229
- this.isPointPositionsUpdateNeeded = false
1230
- this.isPointColorUpdateNeeded = false
1231
- this.isPointSizeUpdateNeeded = false
1232
- this.isPointShapeUpdateNeeded = false
1233
- this.isPointImageIndicesUpdateNeeded = false
1234
- this.isPointImageSizesUpdateNeeded = false
1235
- this.isLinksUpdateNeeded = false
1236
- this.isLinkColorUpdateNeeded = false
1237
- this.isLinkWidthUpdateNeeded = false
1238
- this.isLinkArrowUpdateNeeded = false
1239
- this.isPointClusterUpdateNeeded = false
1240
- this.isForceManyBodyUpdateNeeded = false
1241
- this.isForceLinkUpdateNeeded = false
1242
- this.isForceCenterUpdateNeeded = false
1243
- }
1244
-
1245
- /**
1246
- * Converts an array of tuple positions to a single array containing all coordinates sequentially
1247
- * @param pointPositions An array of tuple positions
1248
- * @returns A flatten array of coordinates
1249
- */
1250
- public flatten (pointPositions: [number, number][]): number[] {
1251
- return pointPositions.flat()
1252
- }
1253
-
1254
- /**
1255
- * Converts a flat array of point positions to a tuple pairs representing coordinates
1256
- * @param pointPositions A flattened array of coordinates
1257
- * @returns An array of tuple positions
1258
- */
1259
- public pair (pointPositions: number[]): [number, number][] {
1260
- const arr = new Array(pointPositions.length / 2) as [number, number][]
1261
- for (let i = 0; i < pointPositions.length / 2; i++) {
1262
- arr[i] = [pointPositions[i * 2] as number, pointPositions[i * 2 + 1] as number]
1263
- }
1264
-
1265
- return arr
1266
- }
1267
-
1268
- private update (simulationAlpha = this.store.alpha): void {
1269
- const { graph } = this
1270
- this.store.pointsTextureSize = Math.ceil(Math.sqrt(graph.pointsNumber ?? 0))
1271
- this.store.linksTextureSize = Math.ceil(Math.sqrt((graph.linksNumber ?? 0) * 2))
1272
- this.create()
1273
- this.initPrograms()
1274
- this.store.hoveredPoint = undefined
1275
- this.start(simulationAlpha)
1276
- }
1277
-
1278
- private initPrograms (): void {
1279
- if (this._isDestroyed || !this.points || !this.lines || !this.clusters) return
1280
- this.points.initPrograms()
1281
- this.lines.initPrograms()
1282
- this.forceGravity?.initPrograms()
1283
- this.forceLinkIncoming?.initPrograms()
1284
- this.forceLinkOutgoing?.initPrograms()
1285
- this.forceMouse?.initPrograms()
1286
- this.forceManyBody?.initPrograms()
1287
- this.forceCenter?.initPrograms()
1288
- this.clusters.initPrograms()
1289
- }
1290
-
1291
- private frame (): void {
1292
- if (this._isDestroyed) return
1293
- const { config: { simulationGravity, simulationCenter, renderLinks, enableSimulation }, store: { alpha, isSimulationRunning } } = this
1294
- if (alpha < ALPHA_MIN && isSimulationRunning) this.end()
1295
- if (!this.store.pointsTextureSize) return
1296
-
1297
- this.requestAnimationFrameId = window.requestAnimationFrame((now) => {
1298
- this.fpsMonitor?.begin()
1299
- this.resizeCanvas()
1300
- if (!this.dragInstance.isActive) {
1301
- this.findHoveredItem()
1302
- }
1303
-
1304
- if (enableSimulation) {
1305
- if (this.isRightClickMouse && this.config.enableRightClickRepulsion) {
1306
- this.forceMouse?.run()
1307
- this.points?.updatePosition()
1308
- }
1309
- if ((isSimulationRunning && !(this.zoomInstance.isRunning && !this.config.enableSimulationDuringZoom))) {
1310
- if (simulationGravity) {
1311
- this.forceGravity?.run()
1312
- this.points?.updatePosition()
1313
- }
1314
-
1315
- if (simulationCenter) {
1316
- this.forceCenter?.run()
1317
- this.points?.updatePosition()
1318
- }
1319
-
1320
- this.forceManyBody?.run()
1321
- this.points?.updatePosition()
1322
-
1323
- if (this.store.linksTextureSize) {
1324
- this.forceLinkIncoming?.run()
1325
- this.points?.updatePosition()
1326
- this.forceLinkOutgoing?.run()
1327
- this.points?.updatePosition()
1328
- }
1329
-
1330
- if (this.graph.pointClusters || this.graph.clusterPositions) {
1331
- this.clusters?.run()
1332
- this.points?.updatePosition()
1333
- }
1334
-
1335
- this.store.alpha += this.store.addAlpha(this.config.simulationDecay ?? defaultConfigValues.simulation.decay)
1336
- if (this.isRightClickMouse && this.config.enableRightClickRepulsion) this.store.alpha = Math.max(this.store.alpha, 0.1)
1337
- this.store.simulationProgress = Math.sqrt(Math.min(1, ALPHA_MIN / this.store.alpha))
1338
- this.config.onSimulationTick?.(
1339
- this.store.alpha,
1340
- this.store.hoveredPoint?.index,
1341
- this.store.hoveredPoint?.position
1342
- )
1343
- }
1344
-
1345
- this.points?.trackPoints()
1346
- }
1347
-
1348
- // Clear canvas
1349
- this.reglInstance?.clear({
1350
- color: this.store.backgroundColor,
1351
- depth: 1,
1352
- stencil: 0,
1353
- })
1354
-
1355
- if (renderLinks && this.store.linksTextureSize) {
1356
- this.lines?.draw()
1357
- }
1358
-
1359
- this.points?.draw()
1360
- if (this.dragInstance.isActive) {
1361
- // To prevent the dragged point from suddenly jumping, run the drag function twice
1362
- this.points?.drag()
1363
- this.points?.drag()
1364
- }
1365
- this.fpsMonitor?.end(now)
1366
-
1367
- this.currentEvent = undefined
1368
- if (!this._isDestroyed) {
1369
- this.frame()
1370
- }
1371
- })
1372
- }
1373
-
1374
- private stopFrames (): void {
1375
- if (this.requestAnimationFrameId) window.cancelAnimationFrame(this.requestAnimationFrameId)
1376
- }
1377
-
1378
- private end (): void {
1379
- this.store.isSimulationRunning = false
1380
- this.store.simulationProgress = 1
1381
- this.config.onSimulationEnd?.()
1382
- }
1383
-
1384
- private onClick (event: MouseEvent): void {
1385
- this.config.onClick?.(
1386
- this.store.hoveredPoint?.index,
1387
- this.store.hoveredPoint?.position,
1388
- event
1389
- )
1390
-
1391
- if (this.store.hoveredPoint) {
1392
- this.config.onPointClick?.(
1393
- this.store.hoveredPoint.index,
1394
- this.store.hoveredPoint.position,
1395
- event
1396
- )
1397
- } else if (this.store.hoveredLinkIndex !== undefined) {
1398
- this.config.onLinkClick?.(
1399
- this.store.hoveredLinkIndex,
1400
- event
1401
- )
1402
- } else {
1403
- this.config.onBackgroundClick?.(
1404
- event
1405
- )
1406
- }
1407
- }
1408
-
1409
- private updateMousePosition (event: MouseEvent | D3DragEvent<HTMLCanvasElement, undefined, Hovered>): void {
1410
- if (!event) return
1411
- const mouseX = (event as MouseEvent).offsetX ?? (event as D3DragEvent<HTMLCanvasElement, undefined, Hovered>).x
1412
- const mouseY = (event as MouseEvent).offsetY ?? (event as D3DragEvent<HTMLCanvasElement, undefined, Hovered>).y
1413
- if (mouseX === undefined || mouseY === undefined) return
1414
- this.store.mousePosition = this.zoomInstance.convertScreenToSpacePosition([mouseX, mouseY])
1415
- this.store.screenMousePosition = [mouseX, (this.store.screenSize[1] - mouseY)]
1416
- }
1417
-
1418
- private onMouseMove (event: MouseEvent): void {
1419
- this.currentEvent = event
1420
- this.updateMousePosition(event)
1421
- this.isRightClickMouse = event.which === 3
1422
- this.config.onMouseMove?.(
1423
- this.store.hoveredPoint?.index,
1424
- this.store.hoveredPoint?.position,
1425
- this.currentEvent
1426
- )
1427
- }
1428
-
1429
- private onRightClickMouse (event: MouseEvent): void {
1430
- event.preventDefault()
1431
- }
1432
-
1433
- private resizeCanvas (forceResize = false): void {
1434
- if (this._isDestroyed) return
1435
- const prevWidth = this.canvas.width
1436
- const prevHeight = this.canvas.height
1437
- const w = this.canvas.clientWidth
1438
- const h = this.canvas.clientHeight
1439
-
1440
- if (forceResize || prevWidth !== w * this.config.pixelRatio || prevHeight !== h * this.config.pixelRatio) {
1441
- const [prevW, prevH] = this.store.screenSize
1442
- const { k } = this.zoomInstance.eventTransform
1443
- const centerPosition = this.zoomInstance.convertScreenToSpacePosition([prevW / 2, prevH / 2])
1444
-
1445
- this.store.updateScreenSize(w, h)
1446
- this.canvas.width = w * this.config.pixelRatio
1447
- this.canvas.height = h * this.config.pixelRatio
1448
- this.reglInstance?.poll()
1449
- this.canvasD3Selection
1450
- ?.call(this.zoomInstance.behavior.transform, this.zoomInstance.getTransform([centerPosition], k))
1451
- this.points?.updateSampledPointsGrid()
1452
- // Only update link index FBO if link hovering is enabled
1453
- if (this.store.isLinkHoveringEnabled) {
1454
- this.lines?.updateLinkIndexFbo()
1455
- }
1456
- }
1457
- }
1458
-
1459
- private setZoomTransformByPointPositions (positions: number[], duration = 250, scale?: number, padding?: number): void {
1460
- this.resizeCanvas()
1461
- const transform = this.zoomInstance.getTransform(this.pair(positions), scale, padding)
1462
- this.canvasD3Selection
1463
- ?.transition()
1464
- .ease(easeQuadInOut)
1465
- .duration(duration)
1466
- .call(this.zoomInstance.behavior.transform, transform)
1467
- }
1468
-
1469
- private updateZoomDragBehaviors (): void {
1470
- if (this.config.enableDrag) {
1471
- this.canvasD3Selection?.call(this.dragInstance.behavior)
1472
- } else {
1473
- this.canvasD3Selection
1474
- ?.call(this.dragInstance.behavior)
1475
- .on('.drag', null)
1476
- }
1477
-
1478
- if (this.config.enableZoom) {
1479
- this.canvasD3Selection?.call(this.zoomInstance.behavior)
1480
- } else {
1481
- this.canvasD3Selection
1482
- ?.call(this.zoomInstance.behavior)
1483
- .on('wheel.zoom', null)
1484
- }
1485
- }
1486
-
1487
- private findHoveredItem (): void {
1488
- if (this._isDestroyed || !this._isMouseOnCanvas || !this.reglInstance) return
1489
- if (this._findHoveredItemExecutionCount < MAX_HOVER_DETECTION_DELAY) {
1490
- this._findHoveredItemExecutionCount += 1
1491
- return
1492
- }
1493
- this._findHoveredItemExecutionCount = 0
1494
- this.findHoveredPoint()
1495
-
1496
- if (this.graph.linksNumber && this.store.isLinkHoveringEnabled) {
1497
- this.findHoveredLine()
1498
- } else if (this.store.hoveredLinkIndex !== undefined) {
1499
- // Clear stale hoveredLinkIndex when there are no links
1500
- const wasHovered = this.store.hoveredLinkIndex !== undefined
1501
- this.store.hoveredLinkIndex = undefined
1502
- if (wasHovered && this.config.onLinkMouseOut) {
1503
- this.config.onLinkMouseOut(this.currentEvent)
1504
- }
1505
- }
1506
-
1507
- this.updateCanvasCursor()
1508
- }
1509
-
1510
- private findHoveredPoint (): void {
1511
- if (this._isDestroyed || !this.reglInstance || !this.points) return
1512
- this.points.findHoveredPoint()
1513
- let isMouseover = false
1514
- let isMouseout = false
1515
- const pixels = readPixels(this.reglInstance, this.points.hoveredFbo as regl.Framebuffer2D)
1516
- const pointSize = pixels[1] as number
1517
- if (pointSize) {
1518
- const hoveredIndex = pixels[0] as number
1519
- if (this.store.hoveredPoint?.index !== hoveredIndex) isMouseover = true
1520
- const pointX = pixels[2] as number
1521
- const pointY = pixels[3] as number
1522
- this.store.hoveredPoint = {
1523
- index: hoveredIndex,
1524
- position: [pointX, pointY],
1525
- }
1526
- } else {
1527
- if (this.store.hoveredPoint) isMouseout = true
1528
- this.store.hoveredPoint = undefined
1529
- }
1530
-
1531
- if (isMouseover && this.store.hoveredPoint) {
1532
- this.config.onPointMouseOver?.(
1533
- this.store.hoveredPoint.index,
1534
- this.store.hoveredPoint.position,
1535
- this.currentEvent
1536
- )
1537
- }
1538
- if (isMouseout) this.config.onPointMouseOut?.(this.currentEvent)
1539
- }
1540
-
1541
- private findHoveredLine (): void {
1542
- if (this._isDestroyed || !this.reglInstance || !this.lines) return
1543
- if (this.store.hoveredPoint) {
1544
- if (this.store.hoveredLinkIndex !== undefined) {
1545
- this.store.hoveredLinkIndex = undefined
1546
- this.config.onLinkMouseOut?.(this.currentEvent)
1547
- }
1548
- return
1549
- }
1550
- this.lines.findHoveredLine()
1551
- let isMouseover = false
1552
- let isMouseout = false
1553
-
1554
- const pixels = readPixels(this.reglInstance, this.lines.hoveredLineIndexFbo as regl.Framebuffer2D)
1555
- const hoveredLineIndex = pixels[0] as number
1556
-
1557
- if (hoveredLineIndex >= 0) {
1558
- if (this.store.hoveredLinkIndex !== hoveredLineIndex) isMouseover = true
1559
- this.store.hoveredLinkIndex = hoveredLineIndex
1560
- } else {
1561
- if (this.store.hoveredLinkIndex !== undefined) isMouseout = true
1562
- this.store.hoveredLinkIndex = undefined
1563
- }
1564
-
1565
- if (isMouseover && this.store.hoveredLinkIndex !== undefined) {
1566
- this.config.onLinkMouseOver?.(this.store.hoveredLinkIndex)
1567
- }
1568
- if (isMouseout) this.config.onLinkMouseOut?.(this.currentEvent)
1569
- }
1570
-
1571
- private updateCanvasCursor (): void {
1572
- const { hoveredPointCursor, hoveredLinkCursor } = this.config
1573
- if (this.dragInstance.isActive) select(this.canvas).style('cursor', 'grabbing')
1574
- else if (this.store.hoveredPoint) {
1575
- if (!this.config.enableDrag || this.store.isSpaceKeyPressed) select(this.canvas).style('cursor', hoveredPointCursor)
1576
- else select(this.canvas).style('cursor', 'grab')
1577
- } else if (this.store.isLinkHoveringEnabled && this.store.hoveredLinkIndex !== undefined) {
1578
- select(this.canvas).style('cursor', hoveredLinkCursor)
1579
- } else select(this.canvas).style('cursor', null)
1580
- }
1581
-
1582
- private addAttribution (): void {
1583
- if (!this.config.attribution) return
1584
- this.attributionDivElement = document.createElement('div')
1585
- this.attributionDivElement.style.cssText = `
1586
- user-select: none;
1587
- position: absolute;
1588
- bottom: 0;
1589
- right: 0;
1590
- color: var(--cosmosgl-attribution-color);
1591
- margin: 0 0.6rem 0.6rem 0;
1592
- font-size: 0.7rem;
1593
- font-family: inherit;
1594
- `
1595
- // Sanitize the attribution HTML content to prevent XSS attacks
1596
- // Use more permissive settings for attribution since it's controlled by the library user
1597
- this.attributionDivElement.innerHTML = sanitizeHtml(this.config.attribution, {
1598
- ALLOWED_TAGS: ['a', 'b', 'i', 'em', 'strong', 'span', 'div', 'p', 'br', 'img'],
1599
- ALLOWED_ATTR: ['href', 'target', 'class', 'id', 'style', 'src', 'alt', 'title'],
1600
- })
1601
- this.store.div?.appendChild(this.attributionDivElement)
1602
- }
1603
- }
1604
-
1605
- export type { GraphConfigInterface } from './config'
1606
- export { PointShape } from './modules/GraphData'
1607
-
1608
- export * from './helper'