@cosmos.gl/graph 2.6.2 → 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.
Files changed (186) hide show
  1. package/.eslintrc +147 -0
  2. package/.github/SECURITY.md +13 -0
  3. package/.github/dco.yml +4 -0
  4. package/.github/workflows/github_pages.yml +54 -0
  5. package/.storybook/main.ts +26 -0
  6. package/.storybook/manager-head.html +1 -0
  7. package/.storybook/manager.ts +14 -0
  8. package/.storybook/preview.ts +29 -0
  9. package/.storybook/style.css +3 -0
  10. package/CHARTER.md +69 -0
  11. package/CODE_OF_CONDUCT.md +178 -0
  12. package/CONTRIBUTING.md +22 -0
  13. package/GOVERNANCE.md +21 -0
  14. package/cosmos-2-0-migration-notes.md +98 -0
  15. package/cosmos_awesome.md +96 -0
  16. package/dist/config.d.ts +5 -18
  17. package/dist/graph/utils/error-message.d.ts +1 -1
  18. package/dist/helper.d.ts +39 -2
  19. package/dist/index-FUIgayhu.js +19827 -0
  20. package/dist/index-FUIgayhu.js.map +1 -0
  21. package/dist/index.d.ts +17 -64
  22. package/dist/index.js +14 -14658
  23. package/dist/index.js.map +1 -1
  24. package/dist/index.min.js +1062 -475
  25. package/dist/index.min.js.map +1 -1
  26. package/dist/modules/Clusters/index.d.ts +11 -3
  27. package/dist/modules/ForceCenter/index.d.ts +10 -3
  28. package/dist/modules/ForceGravity/index.d.ts +5 -1
  29. package/dist/modules/ForceLink/index.d.ts +8 -5
  30. package/dist/modules/ForceManyBody/index.d.ts +16 -7
  31. package/dist/modules/ForceMouse/index.d.ts +5 -1
  32. package/dist/modules/GraphData/index.d.ts +0 -1
  33. package/dist/modules/Lines/index.d.ts +11 -5
  34. package/dist/modules/Points/index.d.ts +31 -13
  35. package/dist/modules/Store/index.d.ts +93 -0
  36. package/dist/modules/core-module.d.ts +3 -3
  37. package/dist/stories/beginners/basic-set-up/data-gen.d.ts +4 -0
  38. package/dist/stories/beginners/basic-set-up/index.d.ts +6 -0
  39. package/dist/stories/beginners/link-hovering/data-generator.d.ts +19 -0
  40. package/dist/stories/beginners/link-hovering/index.d.ts +6 -0
  41. package/dist/stories/beginners/point-labels/data.d.ts +13 -0
  42. package/dist/stories/beginners/point-labels/index.d.ts +10 -0
  43. package/dist/stories/beginners/point-labels/labels.d.ts +8 -0
  44. package/dist/stories/beginners/quick-start.d.ts +6 -0
  45. package/dist/stories/beginners/remove-points/config.d.ts +2 -0
  46. package/dist/stories/beginners/remove-points/data-gen.d.ts +4 -0
  47. package/dist/stories/beginners/remove-points/index.d.ts +6 -0
  48. package/dist/stories/beginners.stories.d.ts +10 -0
  49. package/dist/stories/clusters/polygon-selection/index.d.ts +6 -0
  50. package/dist/stories/clusters/polygon-selection/polygon.d.ts +20 -0
  51. package/dist/stories/clusters/radial.d.ts +6 -0
  52. package/dist/stories/clusters/with-labels.d.ts +6 -0
  53. package/dist/stories/clusters/worm.d.ts +6 -0
  54. package/dist/stories/clusters.stories.d.ts +9 -0
  55. package/dist/stories/create-cluster-labels.d.ts +4 -0
  56. package/dist/stories/create-cosmos.d.ts +17 -0
  57. package/dist/stories/create-story.d.ts +16 -0
  58. package/dist/stories/experiments/full-mesh.d.ts +6 -0
  59. package/dist/stories/experiments/mesh-with-holes.d.ts +6 -0
  60. package/dist/stories/experiments.stories.d.ts +7 -0
  61. package/dist/stories/generate-mesh-data.d.ts +12 -0
  62. package/dist/stories/geospatial/moscow-metro-stations/index.d.ts +16 -0
  63. package/dist/stories/geospatial/moscow-metro-stations/moscow-metro-coords.d.ts +1 -0
  64. package/dist/stories/geospatial/moscow-metro-stations/point-colors.d.ts +1 -0
  65. package/dist/stories/geospatial.stories.d.ts +6 -0
  66. package/dist/stories/shapes/all-shapes/index.d.ts +6 -0
  67. package/dist/stories/shapes/image-example/index.d.ts +6 -0
  68. package/dist/stories/shapes.stories.d.ts +7 -0
  69. package/dist/stories/test-luma-migration.d.ts +6 -0
  70. package/dist/stories/test.stories.d.ts +6 -0
  71. package/dist/webgl-device-B9ewDj5L.js +3923 -0
  72. package/dist/webgl-device-B9ewDj5L.js.map +1 -0
  73. package/logo.svg +3 -0
  74. package/package.json +5 -7
  75. package/rollup.config.js +70 -0
  76. package/src/config.ts +728 -0
  77. package/src/declaration.d.ts +12 -0
  78. package/src/graph/utils/error-message.ts +23 -0
  79. package/src/helper.ts +113 -0
  80. package/src/index.ts +1769 -0
  81. package/src/modules/Clusters/calculate-centermass.frag +12 -0
  82. package/src/modules/Clusters/calculate-centermass.vert +38 -0
  83. package/src/modules/Clusters/force-cluster.frag +55 -0
  84. package/src/modules/Clusters/index.ts +578 -0
  85. package/src/modules/Drag/index.ts +33 -0
  86. package/src/modules/FPSMonitor/css.ts +53 -0
  87. package/src/modules/FPSMonitor/index.ts +28 -0
  88. package/src/modules/ForceCenter/calculate-centermass.frag +9 -0
  89. package/src/modules/ForceCenter/calculate-centermass.vert +26 -0
  90. package/src/modules/ForceCenter/force-center.frag +37 -0
  91. package/src/modules/ForceCenter/index.ts +284 -0
  92. package/src/modules/ForceGravity/force-gravity.frag +40 -0
  93. package/src/modules/ForceGravity/index.ts +107 -0
  94. package/src/modules/ForceLink/force-spring.ts +89 -0
  95. package/src/modules/ForceLink/index.ts +293 -0
  96. package/src/modules/ForceManyBody/calculate-level.frag +9 -0
  97. package/src/modules/ForceManyBody/calculate-level.vert +37 -0
  98. package/src/modules/ForceManyBody/force-centermass.frag +61 -0
  99. package/src/modules/ForceManyBody/force-level.frag +138 -0
  100. package/src/modules/ForceManyBody/index.ts +525 -0
  101. package/src/modules/ForceManyBody/quadtree-frag-shader.ts +89 -0
  102. package/src/modules/ForceManyBodyQuadtree/calculate-level.frag +9 -0
  103. package/src/modules/ForceManyBodyQuadtree/calculate-level.vert +25 -0
  104. package/src/modules/ForceManyBodyQuadtree/index.ts +157 -0
  105. package/src/modules/ForceManyBodyQuadtree/quadtree-frag-shader.ts +93 -0
  106. package/src/modules/ForceMouse/force-mouse.frag +35 -0
  107. package/src/modules/ForceMouse/index.ts +102 -0
  108. package/src/modules/GraphData/index.ts +383 -0
  109. package/src/modules/Lines/draw-curve-line.frag +59 -0
  110. package/src/modules/Lines/draw-curve-line.vert +248 -0
  111. package/src/modules/Lines/geometry.ts +18 -0
  112. package/src/modules/Lines/hovered-line-index.frag +43 -0
  113. package/src/modules/Lines/hovered-line-index.vert +13 -0
  114. package/src/modules/Lines/index.ts +661 -0
  115. package/src/modules/Points/atlas-utils.ts +137 -0
  116. package/src/modules/Points/drag-point.frag +34 -0
  117. package/src/modules/Points/draw-highlighted.frag +44 -0
  118. package/src/modules/Points/draw-highlighted.vert +145 -0
  119. package/src/modules/Points/draw-points.frag +259 -0
  120. package/src/modules/Points/draw-points.vert +203 -0
  121. package/src/modules/Points/fill-sampled-points.frag +12 -0
  122. package/src/modules/Points/fill-sampled-points.vert +51 -0
  123. package/src/modules/Points/find-hovered-point.frag +15 -0
  124. package/src/modules/Points/find-hovered-point.vert +90 -0
  125. package/src/modules/Points/find-points-on-area-selection.frag +88 -0
  126. package/src/modules/Points/find-points-on-polygon-selection.frag +89 -0
  127. package/src/modules/Points/index.ts +2292 -0
  128. package/src/modules/Points/track-positions.frag +30 -0
  129. package/src/modules/Points/update-position.frag +39 -0
  130. package/src/modules/Shared/buffer.ts +39 -0
  131. package/src/modules/Shared/clear.frag +10 -0
  132. package/src/modules/Shared/quad.vert +13 -0
  133. package/src/modules/Store/index.ts +283 -0
  134. package/src/modules/Zoom/index.ts +148 -0
  135. package/src/modules/core-module.ts +28 -0
  136. package/src/stories/1. welcome.mdx +75 -0
  137. package/src/stories/2. configuration.mdx +111 -0
  138. package/src/stories/3. api-reference.mdx +591 -0
  139. package/src/stories/beginners/basic-set-up/data-gen.ts +33 -0
  140. package/src/stories/beginners/basic-set-up/index.ts +167 -0
  141. package/src/stories/beginners/basic-set-up/style.css +35 -0
  142. package/src/stories/beginners/link-hovering/data-generator.ts +198 -0
  143. package/src/stories/beginners/link-hovering/index.ts +65 -0
  144. package/src/stories/beginners/link-hovering/style.css +73 -0
  145. package/src/stories/beginners/point-labels/data.ts +73 -0
  146. package/src/stories/beginners/point-labels/index.ts +69 -0
  147. package/src/stories/beginners/point-labels/labels.ts +46 -0
  148. package/src/stories/beginners/point-labels/style.css +16 -0
  149. package/src/stories/beginners/quick-start.ts +54 -0
  150. package/src/stories/beginners/remove-points/config.ts +25 -0
  151. package/src/stories/beginners/remove-points/data-gen.ts +30 -0
  152. package/src/stories/beginners/remove-points/index.ts +96 -0
  153. package/src/stories/beginners/remove-points/style.css +31 -0
  154. package/src/stories/beginners.stories.ts +130 -0
  155. package/src/stories/clusters/polygon-selection/index.ts +52 -0
  156. package/src/stories/clusters/polygon-selection/polygon.ts +143 -0
  157. package/src/stories/clusters/polygon-selection/style.css +8 -0
  158. package/src/stories/clusters/radial.ts +24 -0
  159. package/src/stories/clusters/with-labels.ts +54 -0
  160. package/src/stories/clusters/worm.ts +40 -0
  161. package/src/stories/clusters.stories.ts +77 -0
  162. package/src/stories/create-cluster-labels.ts +50 -0
  163. package/src/stories/create-cosmos.ts +72 -0
  164. package/src/stories/create-story.ts +51 -0
  165. package/src/stories/experiments/full-mesh.ts +13 -0
  166. package/src/stories/experiments/mesh-with-holes.ts +13 -0
  167. package/src/stories/experiments.stories.ts +43 -0
  168. package/src/stories/generate-mesh-data.ts +125 -0
  169. package/src/stories/geospatial/moscow-metro-stations/index.ts +66 -0
  170. package/src/stories/geospatial/moscow-metro-stations/moscow-metro-coords.ts +1 -0
  171. package/src/stories/geospatial/moscow-metro-stations/point-colors.ts +46 -0
  172. package/src/stories/geospatial/moscow-metro-stations/style.css +30 -0
  173. package/src/stories/geospatial.stories.ts +30 -0
  174. package/src/stories/shapes/all-shapes/index.ts +73 -0
  175. package/src/stories/shapes/image-example/icons/box.png +0 -0
  176. package/src/stories/shapes/image-example/icons/lego.png +0 -0
  177. package/src/stories/shapes/image-example/icons/s.png +0 -0
  178. package/src/stories/shapes/image-example/icons/swift.png +0 -0
  179. package/src/stories/shapes/image-example/icons/toolbox.png +0 -0
  180. package/src/stories/shapes/image-example/index.ts +246 -0
  181. package/src/stories/shapes.stories.ts +37 -0
  182. package/src/stories/test-luma-migration.ts +195 -0
  183. package/src/stories/test.stories.ts +25 -0
  184. package/src/variables.ts +68 -0
  185. package/tsconfig.json +41 -0
  186. package/vite.config.ts +52 -0
@@ -0,0 +1,383 @@
1
+ import { getRgbaColor, isNumber } from '@/graph/helper'
2
+ import { GraphConfig } from '@/graph/config'
3
+
4
+ export enum PointShape {
5
+ Circle = 0,
6
+ Square = 1,
7
+ Triangle = 2,
8
+ Diamond = 3,
9
+ Pentagon = 4,
10
+ Hexagon = 5,
11
+ Star = 6,
12
+ Cross = 7,
13
+ None = 8
14
+ }
15
+
16
+ export class GraphData {
17
+ public inputPointPositions: Float32Array | undefined
18
+ public inputPointColors: Float32Array | undefined
19
+ public inputPointSizes: Float32Array | undefined
20
+ public inputPointShapes: Float32Array | undefined
21
+ public inputImageData: ImageData[] | undefined
22
+ public inputPointImageIndices: Float32Array | undefined
23
+ public inputPointImageSizes: Float32Array | undefined
24
+ public inputLinkColors: Float32Array | undefined
25
+ public inputLinkWidths: Float32Array | undefined
26
+ public inputLinkStrength: Float32Array | undefined
27
+ public inputPointClusters: (number | undefined)[] | undefined
28
+ public inputClusterPositions: (number | undefined)[] | undefined
29
+ public inputClusterStrength: Float32Array | undefined
30
+
31
+ public pointPositions: Float32Array | undefined
32
+ public pointColors: Float32Array | undefined
33
+ public pointSizes: Float32Array | undefined
34
+ public pointShapes: Float32Array | undefined
35
+ public pointImageIndices: Float32Array | undefined
36
+ public pointImageSizes: Float32Array | undefined
37
+
38
+ public inputLinks: Float32Array | undefined
39
+ public links: Float32Array | undefined
40
+ public linkColors: Float32Array | undefined
41
+ public linkWidths: Float32Array | undefined
42
+ public linkArrowsBoolean: boolean[] | undefined
43
+ public linkArrows: number[] | undefined
44
+ public linkStrength: Float32Array | undefined
45
+
46
+ public pointClusters: (number | undefined)[] | undefined
47
+ public clusterPositions: (number | undefined)[] | undefined
48
+ public clusterStrength: Float32Array | undefined
49
+
50
+ /**
51
+ * Each inner array of `sourceIndexToTargetIndices` and `targetIndexToSourceIndices` contains pairs where:
52
+ * - The first value is the target/source index in the point array.
53
+ * - The second value is the link index in the array of links.
54
+ */
55
+ public sourceIndexToTargetIndices: ([number, number][] | undefined)[] | undefined
56
+ public targetIndexToSourceIndices: ([number, number][] | undefined)[] | undefined
57
+
58
+ public degree: number[] | undefined
59
+ public inDegree: number[] | undefined
60
+ public outDegree: number[] | undefined
61
+
62
+ private _config: GraphConfig
63
+
64
+ public constructor (config: GraphConfig) {
65
+ this._config = config
66
+ }
67
+
68
+ public get pointsNumber (): number | undefined {
69
+ return this.pointPositions && this.pointPositions.length / 2
70
+ }
71
+
72
+ public get linksNumber (): number | undefined {
73
+ return this.links && this.links.length / 2
74
+ }
75
+
76
+ public updatePoints (): void {
77
+ this.pointPositions = this.inputPointPositions
78
+ }
79
+
80
+ /**
81
+ * Updates the point colors based on the input data or default config value.
82
+ */
83
+ public updatePointColor (): void {
84
+ if (this.pointsNumber === undefined) {
85
+ this.pointColors = undefined
86
+ return
87
+ }
88
+
89
+ // Sets point colors to default values from config if the input is missing or does not match input points number.
90
+ const defaultRgba = getRgbaColor(this._config.pointColor)
91
+ if (this.inputPointColors === undefined || this.inputPointColors.length / 4 !== this.pointsNumber) {
92
+ this.pointColors = new Float32Array(this.pointsNumber * 4)
93
+ for (let i = 0; i < this.pointColors.length / 4; i++) {
94
+ this.pointColors[i * 4] = defaultRgba[0]
95
+ this.pointColors[i * 4 + 1] = defaultRgba[1]
96
+ this.pointColors[i * 4 + 2] = defaultRgba[2]
97
+ this.pointColors[i * 4 + 3] = defaultRgba[3]
98
+ }
99
+ } else {
100
+ this.pointColors = this.inputPointColors
101
+ for (let i = 0; i < this.pointColors.length / 4; i++) {
102
+ if (!isNumber(this.pointColors[i * 4])) this.pointColors[i * 4] = defaultRgba[0]
103
+ if (!isNumber(this.pointColors[i * 4 + 1])) this.pointColors[i * 4 + 1] = defaultRgba[1]
104
+ if (!isNumber(this.pointColors[i * 4 + 2])) this.pointColors[i * 4 + 2] = defaultRgba[2]
105
+ if (!isNumber(this.pointColors[i * 4 + 3])) this.pointColors[i * 4 + 3] = defaultRgba[3]
106
+ }
107
+ }
108
+ }
109
+
110
+ /**
111
+ * Updates the point sizes based on the input data or default config value.
112
+ */
113
+ public updatePointSize (): void {
114
+ if (this.pointsNumber === undefined) {
115
+ this.pointSizes = undefined
116
+ return
117
+ }
118
+
119
+ // Sets point sizes to default values from config if the input is missing or does not match input points number.
120
+ if (this.inputPointSizes === undefined || this.inputPointSizes.length !== this.pointsNumber) {
121
+ this.pointSizes = new Float32Array(this.pointsNumber).fill(this._config.pointSize)
122
+ } else {
123
+ this.pointSizes = this.inputPointSizes
124
+ for (let i = 0; i < this.pointSizes.length; i++) {
125
+ if (!isNumber(this.pointSizes[i])) {
126
+ this.pointSizes[i] = this._config.pointSize
127
+ }
128
+ }
129
+ }
130
+ }
131
+
132
+ /**
133
+ * Updates the point shapes based on the input data or default shape.
134
+ * Default behavior: Circle (0).
135
+ * Images are rendered above shapes.
136
+ */
137
+ public updatePointShape (): void {
138
+ if (this.pointsNumber === undefined) {
139
+ this.pointShapes = undefined
140
+ return
141
+ }
142
+
143
+ // Determine default shape: Circle
144
+ const defaultShape = PointShape.Circle
145
+
146
+ // Sets point shapes to default values if the input is missing or does not match input points number.
147
+ if (this.inputPointShapes === undefined || this.inputPointShapes.length !== this.pointsNumber) {
148
+ this.pointShapes = new Float32Array(this.pointsNumber).fill(defaultShape)
149
+ } else {
150
+ this.pointShapes = new Float32Array(this.inputPointShapes)
151
+ const pointShapes = this.pointShapes
152
+ for (let i = 0; i < pointShapes.length; i++) {
153
+ const shape = pointShapes[i]
154
+ if (shape == null || !isNumber(shape) || shape < 0 || shape > 8) {
155
+ pointShapes[i] = defaultShape
156
+ }
157
+ }
158
+ }
159
+ }
160
+
161
+ /**
162
+ * Updates the point image indices based on the input data or default value (-1 for no image).
163
+ */
164
+ public updatePointImageIndices (): void {
165
+ if (this.pointsNumber === undefined) {
166
+ this.pointImageIndices = undefined
167
+ return
168
+ }
169
+
170
+ // Sets point image indices to -1 if input is missing or doesn't match points count
171
+ if (this.inputPointImageIndices === undefined || this.inputPointImageIndices.length !== this.pointsNumber) {
172
+ this.pointImageIndices = new Float32Array(this.pointsNumber).fill(-1)
173
+ } else {
174
+ const pointImageIndices = new Float32Array(this.inputPointImageIndices)
175
+ for (let i = 0; i < pointImageIndices.length; i++) {
176
+ const rawIndex = pointImageIndices[i]
177
+ const imageIndex = (rawIndex === undefined) ? NaN : rawIndex
178
+ if (!Number.isFinite(imageIndex) || imageIndex < 0) {
179
+ pointImageIndices[i] = -1
180
+ } else {
181
+ pointImageIndices[i] = Math.trunc(imageIndex)
182
+ }
183
+ }
184
+ this.pointImageIndices = pointImageIndices
185
+ }
186
+ }
187
+
188
+ /**
189
+ * Updates the point image sizes based on the input data or default to point sizes.
190
+ */
191
+ public updatePointImageSizes (): void {
192
+ if (this.pointsNumber === undefined) {
193
+ this.pointImageSizes = undefined
194
+ return
195
+ }
196
+
197
+ // Sets point image sizes to point sizes if the input is missing or does not match input points number.
198
+ if (this.inputPointImageSizes === undefined || this.inputPointImageSizes.length !== this.pointsNumber) {
199
+ this.pointImageSizes = this.pointSizes ? new Float32Array(this.pointSizes) : new Float32Array(this.pointsNumber).fill(this._config.pointSize)
200
+ } else {
201
+ this.pointImageSizes = new Float32Array(this.inputPointImageSizes)
202
+ for (let i = 0; i < this.pointImageSizes.length; i++) {
203
+ if (!isNumber(this.pointImageSizes[i])) {
204
+ this.pointImageSizes[i] = this.pointSizes?.[i] ?? this._config.pointSize
205
+ }
206
+ }
207
+ }
208
+ }
209
+
210
+ public updateLinks (): void {
211
+ this.links = this.inputLinks
212
+ }
213
+
214
+ /**
215
+ * Updates the link colors based on the input data or default config value.
216
+ */
217
+ public updateLinkColor (): void {
218
+ if (this.linksNumber === undefined) {
219
+ this.linkColors = undefined
220
+ return
221
+ }
222
+
223
+ // Sets link colors to default values from config if the input is missing or does not match input links number.
224
+ const defaultRgba = getRgbaColor(this._config.linkColor)
225
+ if (this.inputLinkColors === undefined || this.inputLinkColors.length / 4 !== this.linksNumber) {
226
+ this.linkColors = new Float32Array(this.linksNumber * 4)
227
+
228
+ for (let i = 0; i < this.linkColors.length / 4; i++) {
229
+ this.linkColors[i * 4] = defaultRgba[0]
230
+ this.linkColors[i * 4 + 1] = defaultRgba[1]
231
+ this.linkColors[i * 4 + 2] = defaultRgba[2]
232
+ this.linkColors[i * 4 + 3] = defaultRgba[3]
233
+ }
234
+ } else {
235
+ this.linkColors = this.inputLinkColors
236
+ for (let i = 0; i < this.linkColors.length / 4; i++) {
237
+ if (!isNumber(this.linkColors[i * 4])) this.linkColors[i * 4] = defaultRgba[0]
238
+ if (!isNumber(this.linkColors[i * 4 + 1])) this.linkColors[i * 4 + 1] = defaultRgba[1]
239
+ if (!isNumber(this.linkColors[i * 4 + 2])) this.linkColors[i * 4 + 2] = defaultRgba[2]
240
+ if (!isNumber(this.linkColors[i * 4 + 3])) this.linkColors[i * 4 + 3] = defaultRgba[3]
241
+ }
242
+ }
243
+ }
244
+
245
+ /**
246
+ * Updates the link width based on the input data or default config value.
247
+ */
248
+ public updateLinkWidth (): void {
249
+ if (this.linksNumber === undefined) {
250
+ this.linkWidths = undefined
251
+ return
252
+ }
253
+
254
+ // Sets link widths to default values from config if the input is missing or does not match input links number.
255
+ if (this.inputLinkWidths === undefined || this.inputLinkWidths.length !== this.linksNumber) {
256
+ this.linkWidths = new Float32Array(this.linksNumber).fill(this._config.linkWidth)
257
+ } else {
258
+ this.linkWidths = this.inputLinkWidths
259
+ for (let i = 0; i < this.linkWidths.length; i++) {
260
+ if (!isNumber(this.linkWidths[i])) {
261
+ this.linkWidths[i] = this._config.linkWidth
262
+ }
263
+ }
264
+ }
265
+ }
266
+
267
+ /**
268
+ * Updates the link arrows based on the input data or default config value.
269
+ */
270
+ public updateArrows (): void {
271
+ if (this.linksNumber === undefined) {
272
+ this.linkArrows = undefined
273
+ return
274
+ }
275
+
276
+ // Sets link arrows to default values from config if the input is missing or does not match input links number.
277
+ if (this.linkArrowsBoolean === undefined || this.linkArrowsBoolean.length !== this.linksNumber) {
278
+ this.linkArrows = new Array(this.linksNumber).fill(+this._config.linkArrows)
279
+ } else {
280
+ this.linkArrows = this.linkArrowsBoolean.map(d => +d)
281
+ }
282
+ }
283
+
284
+ public updateLinkStrength (): void {
285
+ if (this.linksNumber === undefined) {
286
+ this.linkStrength = undefined
287
+ }
288
+
289
+ if (this.inputLinkStrength === undefined || this.inputLinkStrength.length !== this.linksNumber) {
290
+ this.linkStrength = undefined
291
+ } else {
292
+ this.linkStrength = this.inputLinkStrength
293
+ }
294
+ }
295
+
296
+ public updateClusters (): void {
297
+ if (this.pointsNumber === undefined) {
298
+ this.pointClusters = undefined
299
+ this.clusterPositions = undefined
300
+ return
301
+ }
302
+ if (this.inputPointClusters === undefined || this.inputPointClusters.length !== this.pointsNumber) {
303
+ this.pointClusters = undefined
304
+ } else {
305
+ this.pointClusters = this.inputPointClusters
306
+ }
307
+ if (this.inputClusterPositions === undefined) {
308
+ this.clusterPositions = undefined
309
+ } else {
310
+ this.clusterPositions = this.inputClusterPositions
311
+ }
312
+ if (this.inputClusterStrength === undefined || this.inputClusterStrength.length !== this.pointsNumber) {
313
+ this.clusterStrength = undefined
314
+ } else {
315
+ this.clusterStrength = this.inputClusterStrength
316
+ }
317
+ }
318
+
319
+ public update (): void {
320
+ this.updatePoints()
321
+ this.updatePointColor()
322
+ this.updatePointSize()
323
+ this.updatePointShape()
324
+ this.updatePointImageIndices()
325
+ this.updatePointImageSizes()
326
+
327
+ this.updateLinks()
328
+ this.updateLinkColor()
329
+ this.updateLinkWidth()
330
+ this.updateArrows()
331
+ this.updateLinkStrength()
332
+
333
+ this.updateClusters()
334
+
335
+ this._createAdjacencyLists()
336
+ this._calculateDegrees()
337
+ }
338
+
339
+ public getAdjacentIndices (index: number): number[] | undefined {
340
+ return [...(this.sourceIndexToTargetIndices?.[index]?.map(d => d[0]) || []), ...(this.targetIndexToSourceIndices?.[index]?.map(d => d[0]) || [])]
341
+ }
342
+
343
+ private _createAdjacencyLists (): void {
344
+ if (this.linksNumber === undefined || this.links === undefined) {
345
+ this.sourceIndexToTargetIndices = undefined
346
+ this.targetIndexToSourceIndices = undefined
347
+ return
348
+ }
349
+
350
+ this.sourceIndexToTargetIndices = new Array(this.pointsNumber).fill(undefined)
351
+ this.targetIndexToSourceIndices = new Array(this.pointsNumber).fill(undefined)
352
+ for (let i = 0; i < this.linksNumber; i++) {
353
+ const sourceIndex = this.links[i * 2]
354
+ const targetIndex = this.links[i * 2 + 1]
355
+ if (sourceIndex !== undefined && targetIndex !== undefined) {
356
+ if (this.sourceIndexToTargetIndices[sourceIndex] === undefined) this.sourceIndexToTargetIndices[sourceIndex] = []
357
+ this.sourceIndexToTargetIndices[sourceIndex]?.push([targetIndex, i])
358
+
359
+ if (this.targetIndexToSourceIndices[targetIndex] === undefined) this.targetIndexToSourceIndices[targetIndex] = []
360
+ this.targetIndexToSourceIndices[targetIndex]?.push([sourceIndex, i])
361
+ }
362
+ }
363
+ }
364
+
365
+ private _calculateDegrees (): void {
366
+ if (this.pointsNumber === undefined) {
367
+ this.degree = undefined
368
+ this.inDegree = undefined
369
+ this.outDegree = undefined
370
+ return
371
+ }
372
+
373
+ this.degree = new Array(this.pointsNumber).fill(0)
374
+ this.inDegree = new Array(this.pointsNumber).fill(0)
375
+ this.outDegree = new Array(this.pointsNumber).fill(0)
376
+
377
+ for (let i = 0; i < this.pointsNumber; i++) {
378
+ this.inDegree[i] = this.targetIndexToSourceIndices?.[i]?.length ?? 0
379
+ this.outDegree[i] = this.sourceIndexToTargetIndices?.[i]?.length ?? 0
380
+ this.degree[i] = (this.inDegree[i] ?? 0) + (this.outDegree[i] ?? 0)
381
+ }
382
+ }
383
+ }
@@ -0,0 +1,59 @@
1
+ #version 300 es
2
+ #ifdef GL_ES
3
+ precision highp float;
4
+ #endif
5
+
6
+ in vec4 rgbaColor;
7
+ in vec2 pos;
8
+ in float arrowLength;
9
+ in float useArrow;
10
+ in float smoothing;
11
+ in float arrowWidthFactor;
12
+ in float linkIndex;
13
+
14
+ #ifdef USE_UNIFORM_BUFFERS
15
+ layout(std140) uniform drawLineFragmentUniforms {
16
+ float renderMode;
17
+ } drawLineFrag;
18
+
19
+ #define renderMode drawLineFrag.renderMode
20
+ #else
21
+ // renderMode: 0.0 = normal rendering, 1.0 = index buffer rendering for picking
22
+ uniform float renderMode;
23
+ #endif
24
+
25
+ out vec4 fragColor;
26
+
27
+ float map(float value, float min1, float max1, float min2, float max2) {
28
+ return min2 + (value - min1) * (max2 - min2) / (max1 - min1);
29
+ }
30
+
31
+ void main() {
32
+ float opacity = 1.0;
33
+ vec3 color = rgbaColor.rgb;
34
+
35
+ if (useArrow > 0.5) {
36
+ float end_arrow = 0.5 + arrowLength / 2.0;
37
+ float start_arrow = end_arrow - arrowLength;
38
+ float arrowWidthDelta = arrowWidthFactor / 2.0;
39
+ float linkOpacity = rgbaColor.a * smoothstep(0.5 - arrowWidthDelta, 0.5 - arrowWidthDelta - smoothing / 2.0, abs(pos.y));
40
+ float arrowOpacity = 1.0;
41
+ if (pos.x > start_arrow && pos.x < start_arrow + arrowLength) {
42
+ float xmapped = map(pos.x, start_arrow, end_arrow, 0.0, 1.0);
43
+ arrowOpacity = rgbaColor.a * smoothstep(xmapped - smoothing, xmapped, map(abs(pos.y), 0.5, 0.0, 0.0, 1.0));
44
+ if (linkOpacity != arrowOpacity) {
45
+ linkOpacity = max(linkOpacity, arrowOpacity);
46
+ }
47
+ }
48
+ opacity = linkOpacity;
49
+ } else opacity = rgbaColor.a * smoothstep(0.5, 0.5 - smoothing, abs(pos.y));
50
+
51
+ if (renderMode > 0.0) {
52
+ if (opacity > 0.0) {
53
+ fragColor = vec4(linkIndex, 0.0, 0.0, 1.0);
54
+ } else {
55
+ fragColor = vec4(-1.0, 0.0, 0.0, 0.0);
56
+ }
57
+ } else fragColor = vec4(color, opacity);
58
+
59
+ }
@@ -0,0 +1,248 @@
1
+ #version 300 es
2
+ #ifdef GL_ES
3
+ precision highp float;
4
+ #endif
5
+
6
+ in vec2 position, pointA, pointB;
7
+ in vec4 color;
8
+ in float width;
9
+ in float arrow;
10
+ in float linkIndices;
11
+
12
+ uniform sampler2D positionsTexture;
13
+ uniform sampler2D pointGreyoutStatus;
14
+
15
+ #ifdef USE_UNIFORM_BUFFERS
16
+ layout(std140) uniform drawLineUniforms {
17
+ mat4 transformationMatrix;
18
+ float pointsTextureSize;
19
+ float widthScale;
20
+ float linkArrowsSizeScale;
21
+ float spaceSize;
22
+ vec2 screenSize;
23
+ vec2 linkVisibilityDistanceRange;
24
+ float linkVisibilityMinTransparency;
25
+ float linkOpacity;
26
+ float greyoutOpacity;
27
+ float curvedWeight;
28
+ float curvedLinkControlPointDistance;
29
+ float curvedLinkSegments;
30
+ float scaleLinksOnZoom;
31
+ float maxPointSize;
32
+ float renderMode;
33
+ float hoveredLinkIndex;
34
+ vec4 hoveredLinkColor;
35
+ float hoveredLinkWidthIncrease;
36
+ } drawLine;
37
+
38
+ #define transformationMatrix drawLine.transformationMatrix
39
+ #define pointsTextureSize drawLine.pointsTextureSize
40
+ #define widthScale drawLine.widthScale
41
+ #define linkArrowsSizeScale drawLine.linkArrowsSizeScale
42
+ #define spaceSize drawLine.spaceSize
43
+ #define screenSize drawLine.screenSize
44
+ #define linkVisibilityDistanceRange drawLine.linkVisibilityDistanceRange
45
+ #define linkVisibilityMinTransparency drawLine.linkVisibilityMinTransparency
46
+ #define linkOpacity drawLine.linkOpacity
47
+ #define greyoutOpacity drawLine.greyoutOpacity
48
+ #define curvedWeight drawLine.curvedWeight
49
+ #define curvedLinkControlPointDistance drawLine.curvedLinkControlPointDistance
50
+ #define curvedLinkSegments drawLine.curvedLinkSegments
51
+ #define scaleLinksOnZoom drawLine.scaleLinksOnZoom
52
+ #define maxPointSize drawLine.maxPointSize
53
+ #define renderMode drawLine.renderMode
54
+ #define hoveredLinkIndex drawLine.hoveredLinkIndex
55
+ #define hoveredLinkColor drawLine.hoveredLinkColor
56
+ #define hoveredLinkWidthIncrease drawLine.hoveredLinkWidthIncrease
57
+ #else
58
+ uniform mat3 transformationMatrix;
59
+ uniform float pointsTextureSize;
60
+ uniform float widthScale;
61
+ uniform float linkArrowsSizeScale;
62
+ uniform float spaceSize;
63
+ uniform vec2 screenSize;
64
+ uniform vec2 linkVisibilityDistanceRange;
65
+ uniform float linkVisibilityMinTransparency;
66
+ uniform float linkOpacity;
67
+ uniform float greyoutOpacity;
68
+ uniform float curvedWeight;
69
+ uniform float curvedLinkControlPointDistance;
70
+ uniform float curvedLinkSegments;
71
+ uniform bool scaleLinksOnZoom;
72
+ uniform float maxPointSize;
73
+ // renderMode: 0.0 = normal rendering, 1.0 = index buffer rendering for picking
74
+ uniform float renderMode;
75
+ uniform float hoveredLinkIndex;
76
+ uniform vec4 hoveredLinkColor;
77
+ uniform float hoveredLinkWidthIncrease;
78
+ #endif
79
+
80
+ out vec4 rgbaColor;
81
+ out vec2 pos;
82
+ out float arrowLength;
83
+ out float useArrow;
84
+ out float smoothing;
85
+ out float arrowWidthFactor;
86
+ out float linkIndex;
87
+
88
+ float map(float value, float min1, float max1, float min2, float max2) {
89
+ return min2 + (value - min1) * (max2 - min2) / (max1 - min1);
90
+ }
91
+
92
+ vec2 conicParametricCurve(vec2 A, vec2 B, vec2 ControlPoint, float t, float w) {
93
+ vec2 divident = (1.0 - t) * (1.0 - t) * A + 2.0 * (1.0 - t) * t * w * ControlPoint + t * t * B;
94
+ float divisor = (1.0 - t) * (1.0 - t) + 2.0 * (1.0 - t) * t * w + t * t;
95
+ return divident / divisor;
96
+ }
97
+
98
+ float calculateLinkWidth(float width) {
99
+ float linkWidth;
100
+ if (scaleLinksOnZoom > 0.0) {
101
+ // Use original width if links should scale with zoom
102
+ linkWidth = width;
103
+ } else {
104
+ // Adjust width based on zoom level to maintain visual size
105
+ linkWidth = width / transformationMatrix[0][0];
106
+ // Apply a non-linear scaling to avoid extreme widths
107
+ linkWidth *= min(5.0, max(1.0, transformationMatrix[0][0] * 0.01));
108
+ }
109
+ // Limit link width based on whether it has an arrow
110
+ if (useArrow > 0.5) {
111
+ return min(linkWidth, (maxPointSize * 2.0) / transformationMatrix[0][0]);
112
+ } else {
113
+ return min(linkWidth, maxPointSize / transformationMatrix[0][0]);
114
+ }
115
+ }
116
+
117
+ float calculateArrowWidth(float arrowWidth) {
118
+ if (scaleLinksOnZoom > 0.0) {
119
+ return arrowWidth;
120
+ } else {
121
+ // Apply the same scaling logic as calculateLinkWidth to maintain proportionality
122
+ arrowWidth = arrowWidth / transformationMatrix[0][0];
123
+ // Apply the same non-linear scaling to avoid extreme widths
124
+ arrowWidth *= min(5.0, max(1.0, transformationMatrix[0][0] * 0.01));
125
+ return arrowWidth;
126
+ }
127
+ }
128
+
129
+ void main() {
130
+ pos = position;
131
+ linkIndex = linkIndices;
132
+
133
+ vec2 pointTexturePosA = (pointA + 0.5) / pointsTextureSize;
134
+ vec2 pointTexturePosB = (pointB + 0.5) / pointsTextureSize;
135
+
136
+ vec4 greyoutStatusA = texture(pointGreyoutStatus, pointTexturePosA);
137
+ vec4 greyoutStatusB = texture(pointGreyoutStatus, pointTexturePosB);
138
+
139
+ vec4 pointPositionA = texture(positionsTexture, pointTexturePosA);
140
+ vec4 pointPositionB = texture(positionsTexture, pointTexturePosB);
141
+ vec2 a = pointPositionA.xy;
142
+ vec2 b = pointPositionB.xy;
143
+
144
+ // Calculate direction vector and its perpendicular
145
+ vec2 xBasis = b - a;
146
+ vec2 yBasis = normalize(vec2(-xBasis.y, xBasis.x));
147
+
148
+ // Calculate link distance and control point for curved link
149
+ float linkDist = length(xBasis);
150
+ float h = curvedLinkControlPointDistance;
151
+ vec2 controlPoint = (a + b) / 2.0 + yBasis * linkDist * h;
152
+
153
+ // Convert link distance to screen pixels
154
+ float linkDistPx = linkDist * transformationMatrix[0][0];
155
+
156
+ // Calculate line width using the width scale
157
+ float linkWidth = width * widthScale;
158
+ float k = 2.0;
159
+ // Arrow width is proportionally larger than the line width
160
+ float arrowWidth = linkWidth * k;
161
+ arrowWidth *= linkArrowsSizeScale;
162
+
163
+ // Ensure arrow width difference is non-negative to prevent unwanted changes to link width
164
+ float arrowWidthDifference = max(0.0, arrowWidth - linkWidth);
165
+
166
+ // Calculate arrow width in pixels
167
+ float arrowWidthPx = calculateArrowWidth(arrowWidth);
168
+
169
+ // Calculate arrow length proportional to its width
170
+ // 0.866 is approximately sqrt(3)/2 - related to equilateral triangle geometry
171
+ // Cap the length to avoid overly long arrows on short links
172
+ arrowLength = min(0.3, (0.866 * arrowWidthPx * 2.0) / linkDist);
173
+
174
+ useArrow = arrow;
175
+ if (useArrow > 0.5) {
176
+ linkWidth += arrowWidthDifference;
177
+ }
178
+
179
+ arrowWidthFactor = arrowWidthDifference / linkWidth;
180
+
181
+ // Calculate final link width in pixels with smoothing
182
+ float linkWidthPx = calculateLinkWidth(linkWidth);
183
+
184
+ if (renderMode > 0.0) {
185
+ // Add 5 pixels padding for better hover detection
186
+ linkWidthPx += 5.0 / transformationMatrix[0][0];
187
+ } else {
188
+ // Add pixel increase if this is the hovered link
189
+ if (hoveredLinkIndex == linkIndex) {
190
+ linkWidthPx += hoveredLinkWidthIncrease / transformationMatrix[0][0];
191
+ }
192
+ }
193
+ float smoothingPx = 0.5 / transformationMatrix[0][0];
194
+ smoothing = smoothingPx / linkWidthPx;
195
+ linkWidthPx += smoothingPx;
196
+
197
+
198
+
199
+ // Calculate final color with opacity based on link distance
200
+ vec3 rgbColor = color.rgb;
201
+ // Adjust opacity based on link distance
202
+ float opacity = color.a * linkOpacity * max(linkVisibilityMinTransparency, map(linkDistPx, linkVisibilityDistanceRange.g, linkVisibilityDistanceRange.r, 0.0, 1.0));
203
+
204
+ // Apply greyed out opacity if either endpoint is greyed out
205
+ if (greyoutStatusA.r > 0.0 || greyoutStatusB.r > 0.0) {
206
+ opacity *= greyoutOpacity;
207
+ }
208
+
209
+ // Pass final color to fragment shader
210
+ rgbaColor = vec4(rgbColor, opacity);
211
+
212
+ // Apply hover color if this is the hovered link and hover color is defined
213
+ if (hoveredLinkIndex == linkIndex && hoveredLinkColor.a > -0.5) {
214
+ // Keep existing RGB values but multiply opacity with hover color opacity
215
+ rgbaColor.rgb = hoveredLinkColor.rgb;
216
+ rgbaColor.a *= hoveredLinkColor.a;
217
+ }
218
+
219
+ // Calculate position on the curved path
220
+ float t = position.x;
221
+ float w = curvedWeight;
222
+
223
+ float tPrev = t - 1.0 / curvedLinkSegments;
224
+ float tNext = t + 1.0 / curvedLinkSegments;
225
+
226
+ vec2 pointCurr = conicParametricCurve(a, b, controlPoint, t, w);
227
+
228
+ vec2 pointPrev = conicParametricCurve(a, b, controlPoint, max(0.0, tPrev), w);
229
+ vec2 pointNext = conicParametricCurve(a, b, controlPoint, min(tNext, 1.0), w);
230
+
231
+ vec2 xBasisCurved = pointNext - pointPrev;
232
+ vec2 yBasisCurved = normalize(vec2(-xBasisCurved.y, xBasisCurved.x));
233
+
234
+ pointCurr += yBasisCurved * linkWidthPx * position.y;
235
+
236
+ // Transform to clip space coordinates
237
+ vec2 p = 2.0 * pointCurr / spaceSize - 1.0;
238
+ p *= spaceSize / screenSize;
239
+
240
+ #ifdef USE_UNIFORM_BUFFERS
241
+ mat3 transformMat3 = mat3(transformationMatrix);
242
+ vec3 final = transformMat3 * vec3(p, 1);
243
+ #else
244
+ vec3 final = transformationMatrix * vec3(p, 1);
245
+ #endif
246
+
247
+ gl_Position = vec4(final.rg, 0, 1);
248
+ }
@@ -0,0 +1,18 @@
1
+ import { scalePow } from 'd3-scale'
2
+ import { range } from 'd3-array'
3
+
4
+ export const getCurveLineGeometry = (segments: number): number[][] => {
5
+ const scale = scalePow()
6
+ .exponent(2)
7
+ .range([0, 1])
8
+ .domain([-1, 1])
9
+
10
+ const hodographValues = range(0, segments).map(d => -0.5 + d / segments)
11
+ hodographValues.push(0.5)
12
+ const result = new Array(hodographValues.length * 2)
13
+ hodographValues.forEach((d, i) => {
14
+ result[i * 2] = [scale(d * 2), 0.5]
15
+ result[i * 2 + 1] = [scale(d * 2), -0.5]
16
+ })
17
+ return result
18
+ }