@cosmos.gl/graph 2.4.0 → 2.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/SECURITY.md +7 -1
- package/dist/config.d.ts +73 -1
- package/dist/index.d.ts +34 -6
- package/dist/index.js +4087 -3837
- package/dist/index.js.map +1 -1
- package/dist/index.min.js +124 -44
- package/dist/index.min.js.map +1 -1
- package/dist/modules/GraphData/index.d.ts +1 -0
- package/dist/modules/Lines/index.d.ts +8 -0
- package/dist/modules/Points/index.d.ts +3 -0
- package/dist/modules/Store/index.d.ts +14 -2
- package/dist/modules/core-module.d.ts +1 -0
- package/dist/stories/beginners/link-hovering/data-generator.d.ts +19 -0
- package/dist/stories/beginners/link-hovering/index.d.ts +5 -0
- package/dist/stories/beginners/pinned-points/data-gen.d.ts +5 -0
- package/dist/stories/beginners/pinned-points/index.d.ts +5 -0
- package/dist/stories/beginners.stories.d.ts +2 -0
- package/dist/variables.d.ts +5 -2
- package/package.json +1 -1
- package/src/config.ts +95 -3
- package/src/index.ts +179 -32
- package/src/modules/GraphData/index.ts +2 -1
- package/src/modules/Lines/draw-curve-line.frag +12 -1
- package/src/modules/Lines/draw-curve-line.vert +29 -2
- package/src/modules/Lines/hovered-line-index.frag +27 -0
- package/src/modules/Lines/hovered-line-index.vert +8 -0
- package/src/modules/Lines/index.ts +112 -2
- package/src/modules/Points/index.ts +34 -0
- package/src/modules/Points/update-position.frag +12 -0
- package/src/modules/Store/index.ts +33 -2
- package/src/modules/core-module.ts +1 -0
- package/src/stories/1. welcome.mdx +11 -4
- package/src/stories/2. configuration.mdx +13 -3
- package/src/stories/3. api-reference.mdx +13 -4
- package/src/stories/beginners/basic-set-up/index.ts +21 -11
- package/src/stories/beginners/link-hovering/data-generator.ts +198 -0
- package/src/stories/beginners/link-hovering/index.ts +61 -0
- package/src/stories/beginners/link-hovering/style.css +73 -0
- package/src/stories/beginners/pinned-points/data-gen.ts +153 -0
- package/src/stories/beginners/pinned-points/index.ts +61 -0
- package/src/stories/beginners/quick-start.ts +3 -2
- package/src/stories/beginners/remove-points/config.ts +1 -1
- package/src/stories/beginners/remove-points/index.ts +28 -30
- package/src/stories/beginners.stories.ts +31 -0
- package/src/stories/clusters/polygon-selection/index.ts +2 -4
- package/src/stories/create-cosmos.ts +1 -1
- package/src/stories/geospatial/moscow-metro-stations/index.ts +1 -1
- package/src/stories/shapes/image-example/index.ts +7 -8
- package/src/variables.ts +5 -2
package/src/index.ts
CHANGED
|
@@ -17,10 +17,10 @@ import { FPSMonitor } from '@/graph/modules/FPSMonitor'
|
|
|
17
17
|
import { GraphData } from '@/graph/modules/GraphData'
|
|
18
18
|
import { Lines } from '@/graph/modules/Lines'
|
|
19
19
|
import { Points } from '@/graph/modules/Points'
|
|
20
|
-
import { Store, ALPHA_MIN, MAX_POINT_SIZE, type Hovered } from '@/graph/modules/Store'
|
|
20
|
+
import { Store, ALPHA_MIN, MAX_POINT_SIZE, MAX_HOVER_DETECTION_DELAY, type Hovered } from '@/graph/modules/Store'
|
|
21
21
|
import { Zoom } from '@/graph/modules/Zoom'
|
|
22
22
|
import { Drag } from '@/graph/modules/Drag'
|
|
23
|
-
import { defaultConfigValues, defaultScaleToZoom } from '@/graph/variables'
|
|
23
|
+
import { defaultConfigValues, defaultScaleToZoom, defaultGreyoutPointColor, defaultBackgroundColor } from '@/graph/variables'
|
|
24
24
|
import { createWebGLErrorMessage } from './graph/utils/error-message'
|
|
25
25
|
|
|
26
26
|
export class Graph {
|
|
@@ -50,12 +50,12 @@ export class Graph {
|
|
|
50
50
|
|
|
51
51
|
private currentEvent: D3ZoomEvent<HTMLCanvasElement, undefined> | D3DragEvent<HTMLCanvasElement, undefined, Hovered> | MouseEvent | undefined
|
|
52
52
|
/**
|
|
53
|
-
* The value of `
|
|
54
|
-
* When the counter reaches
|
|
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
55
|
*/
|
|
56
|
-
private
|
|
56
|
+
private _findHoveredItemExecutionCount = 0
|
|
57
57
|
/**
|
|
58
|
-
* If the mouse is not on the Canvas, the `findHoveredPoint` method will not be executed.
|
|
58
|
+
* If the mouse is not on the Canvas, the `findHoveredPoint` or `findHoveredLine` method will not be executed.
|
|
59
59
|
*/
|
|
60
60
|
private _isMouseOnCanvas = false
|
|
61
61
|
/**
|
|
@@ -123,7 +123,30 @@ export class Graph {
|
|
|
123
123
|
this.canvasD3Selection
|
|
124
124
|
.on('mouseenter.cosmos', () => { this._isMouseOnCanvas = true })
|
|
125
125
|
.on('mousemove.cosmos', () => { this._isMouseOnCanvas = true })
|
|
126
|
-
.on('mouseleave.cosmos', () => {
|
|
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
|
+
})
|
|
127
150
|
select(document)
|
|
128
151
|
.on('keydown.cosmos', (event) => { if (event.code === 'Space') this.store.isSpaceKeyPressed = true })
|
|
129
152
|
.on('keyup.cosmos', (event) => { if (event.code === 'Space') this.store.isSpaceKeyPressed = false })
|
|
@@ -176,18 +199,15 @@ export class Graph {
|
|
|
176
199
|
this.clusters = new Clusters(this.reglInstance, this.config, this.store, this.graph, this.points)
|
|
177
200
|
|
|
178
201
|
this.store.backgroundColor = getRgbaColor(this.config.backgroundColor)
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
}
|
|
182
|
-
if (this.config.focusedPointRingColor) {
|
|
183
|
-
this.store.setFocusedPointRingColor(this.config.focusedPointRingColor)
|
|
184
|
-
}
|
|
202
|
+
this.store.setHoveredPointRingColor(this.config.hoveredPointRingColor ?? defaultConfigValues.hoveredPointRingColor)
|
|
203
|
+
this.store.setFocusedPointRingColor(this.config.focusedPointRingColor ?? defaultConfigValues.focusedPointRingColor)
|
|
185
204
|
if (this.config.focusedPointIndex !== undefined) {
|
|
186
205
|
this.store.setFocusedPoint(this.config.focusedPointIndex)
|
|
187
206
|
}
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
207
|
+
this.store.setGreyoutPointColor(this.config.pointGreyoutColor ?? defaultGreyoutPointColor)
|
|
208
|
+
this.store.setHoveredLinkColor(this.config.hoveredLinkColor ?? defaultConfigValues.hoveredLinkColor)
|
|
209
|
+
|
|
210
|
+
this.store.updateLinkHoveringEnabled(this.config)
|
|
191
211
|
|
|
192
212
|
if (this.config.showFPSMonitor) this.fpsMonitor = new FPSMonitor(this.canvas)
|
|
193
213
|
|
|
@@ -227,7 +247,8 @@ export class Graph {
|
|
|
227
247
|
if (this._isDestroyed || !this.reglInstance || !this.points || !this.lines || !this.clusters) return
|
|
228
248
|
const prevConfig = { ...this.config }
|
|
229
249
|
this.config.init(config)
|
|
230
|
-
if (prevConfig.
|
|
250
|
+
if ((prevConfig.pointDefaultColor !== this.config.pointDefaultColor) ||
|
|
251
|
+
(prevConfig.pointColor !== this.config.pointColor)) {
|
|
231
252
|
this.graph.updatePointColor()
|
|
232
253
|
this.points.updateColor()
|
|
233
254
|
}
|
|
@@ -251,15 +272,20 @@ export class Graph {
|
|
|
251
272
|
prevConfig.curvedLinks !== this.config.curvedLinks) {
|
|
252
273
|
this.lines.updateCurveLineGeometry()
|
|
253
274
|
}
|
|
254
|
-
if (prevConfig.backgroundColor !== this.config.backgroundColor)
|
|
275
|
+
if (prevConfig.backgroundColor !== this.config.backgroundColor) {
|
|
276
|
+
this.store.backgroundColor = getRgbaColor(this.config.backgroundColor ?? defaultBackgroundColor)
|
|
277
|
+
}
|
|
255
278
|
if (prevConfig.hoveredPointRingColor !== this.config.hoveredPointRingColor) {
|
|
256
|
-
this.store.setHoveredPointRingColor(this.config.hoveredPointRingColor)
|
|
279
|
+
this.store.setHoveredPointRingColor(this.config.hoveredPointRingColor ?? defaultConfigValues.hoveredPointRingColor)
|
|
257
280
|
}
|
|
258
281
|
if (prevConfig.focusedPointRingColor !== this.config.focusedPointRingColor) {
|
|
259
|
-
this.store.setFocusedPointRingColor(this.config.focusedPointRingColor)
|
|
282
|
+
this.store.setFocusedPointRingColor(this.config.focusedPointRingColor ?? defaultConfigValues.focusedPointRingColor)
|
|
260
283
|
}
|
|
261
284
|
if (prevConfig.pointGreyoutColor !== this.config.pointGreyoutColor) {
|
|
262
|
-
this.store.setGreyoutPointColor(this.config.pointGreyoutColor)
|
|
285
|
+
this.store.setGreyoutPointColor(this.config.pointGreyoutColor ?? defaultGreyoutPointColor)
|
|
286
|
+
}
|
|
287
|
+
if (prevConfig.hoveredLinkColor !== this.config.hoveredLinkColor) {
|
|
288
|
+
this.store.setHoveredLinkColor(this.config.hoveredLinkColor ?? defaultConfigValues.hoveredLinkColor)
|
|
263
289
|
}
|
|
264
290
|
if (prevConfig.focusedPointIndex !== this.config.focusedPointIndex) {
|
|
265
291
|
this.store.setFocusedPoint(this.config.focusedPointIndex)
|
|
@@ -285,6 +311,12 @@ export class Graph {
|
|
|
285
311
|
if (prevConfig.enableZoom !== this.config.enableZoom || prevConfig.enableDrag !== this.config.enableDrag) {
|
|
286
312
|
this.updateZoomDragBehaviors()
|
|
287
313
|
}
|
|
314
|
+
|
|
315
|
+
if (prevConfig.onLinkClick !== this.config.onLinkClick ||
|
|
316
|
+
prevConfig.onLinkMouseOver !== this.config.onLinkMouseOver ||
|
|
317
|
+
prevConfig.onLinkMouseOut !== this.config.onLinkMouseOut) {
|
|
318
|
+
this.store.updateLinkHoveringEnabled(this.config)
|
|
319
|
+
}
|
|
288
320
|
}
|
|
289
321
|
|
|
290
322
|
/**
|
|
@@ -309,6 +341,7 @@ export class Graph {
|
|
|
309
341
|
this.isPointSizeUpdateNeeded = true
|
|
310
342
|
this.isPointShapeUpdateNeeded = true
|
|
311
343
|
this.isPointImageIndicesUpdateNeeded = true
|
|
344
|
+
this.isPointImageSizesUpdateNeeded = true
|
|
312
345
|
this.isPointClusterUpdateNeeded = true
|
|
313
346
|
this.isForceManyBodyUpdateNeeded = true
|
|
314
347
|
this.isForceLinkUpdateNeeded = true
|
|
@@ -561,6 +594,29 @@ export class Graph {
|
|
|
561
594
|
this.isPointClusterUpdateNeeded = true
|
|
562
595
|
}
|
|
563
596
|
|
|
597
|
+
/**
|
|
598
|
+
* Sets which points are pinned (fixed) in position.
|
|
599
|
+
*
|
|
600
|
+
* Pinned points:
|
|
601
|
+
* - Do not move due to physics forces (gravity, repulsion, link forces, etc.)
|
|
602
|
+
* - Still participate in force calculations (other nodes are attracted to/repelled by them)
|
|
603
|
+
* - Can still be dragged by the user if `enableDrag` is true
|
|
604
|
+
*
|
|
605
|
+
* @param {number[] | null} pinnedIndices - Array of point indices to pin. Set to `[]` or `null` to unpin all points.
|
|
606
|
+
* @example
|
|
607
|
+
* // Pin points 0 and 5
|
|
608
|
+
* graph.setPinnedPoints([0, 5])
|
|
609
|
+
*
|
|
610
|
+
* // Unpin all points
|
|
611
|
+
* graph.setPinnedPoints([])
|
|
612
|
+
* graph.setPinnedPoints(null)
|
|
613
|
+
*/
|
|
614
|
+
public setPinnedPoints (pinnedIndices: number[] | null): void {
|
|
615
|
+
if (this._isDestroyed || !this.points) return
|
|
616
|
+
this.graph.inputPinnedPoints = pinnedIndices && pinnedIndices.length > 0 ? pinnedIndices : undefined
|
|
617
|
+
this.points.updatePinnedStatus()
|
|
618
|
+
}
|
|
619
|
+
|
|
564
620
|
/**
|
|
565
621
|
* Renders the graph.
|
|
566
622
|
*
|
|
@@ -1065,7 +1121,8 @@ export class Graph {
|
|
|
1065
1121
|
}
|
|
1066
1122
|
|
|
1067
1123
|
/**
|
|
1068
|
-
* Pause the simulation.
|
|
1124
|
+
* Pause the simulation. When paused, the simulation stops running
|
|
1125
|
+
* and can be resumed using the unpause method.
|
|
1069
1126
|
*/
|
|
1070
1127
|
public pause (): void {
|
|
1071
1128
|
if (this._isDestroyed) return
|
|
@@ -1074,7 +1131,19 @@ export class Graph {
|
|
|
1074
1131
|
}
|
|
1075
1132
|
|
|
1076
1133
|
/**
|
|
1077
|
-
*
|
|
1134
|
+
* Unpause the simulation. This method resumes a paused
|
|
1135
|
+
* simulation and continues its execution.
|
|
1136
|
+
*/
|
|
1137
|
+
public unpause (): void {
|
|
1138
|
+
if (this._isDestroyed) return
|
|
1139
|
+
this.store.isSimulationRunning = true
|
|
1140
|
+
this.config.onSimulationUnpause?.()
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
/**
|
|
1144
|
+
* Restart/Resume the simulation. This method unpauses a paused
|
|
1145
|
+
* simulation and resumes its execution.
|
|
1146
|
+
* @deprecated Use `unpause()` instead. This method will be removed in a future version.
|
|
1078
1147
|
*/
|
|
1079
1148
|
public restart (): void {
|
|
1080
1149
|
if (this._isDestroyed) return
|
|
@@ -1245,6 +1314,7 @@ export class Graph {
|
|
|
1245
1314
|
}
|
|
1246
1315
|
|
|
1247
1316
|
private frame (): void {
|
|
1317
|
+
if (this._isDestroyed) return
|
|
1248
1318
|
const { config: { simulationGravity, simulationCenter, renderLinks, enableSimulation }, store: { alpha, isSimulationRunning } } = this
|
|
1249
1319
|
if (alpha < ALPHA_MIN && isSimulationRunning) this.end()
|
|
1250
1320
|
if (!this.store.pointsTextureSize) return
|
|
@@ -1252,7 +1322,9 @@ export class Graph {
|
|
|
1252
1322
|
this.requestAnimationFrameId = window.requestAnimationFrame((now) => {
|
|
1253
1323
|
this.fpsMonitor?.begin()
|
|
1254
1324
|
this.resizeCanvas()
|
|
1255
|
-
if (!this.dragInstance.isActive)
|
|
1325
|
+
if (!this.dragInstance.isActive) {
|
|
1326
|
+
this.findHoveredItem()
|
|
1327
|
+
}
|
|
1256
1328
|
|
|
1257
1329
|
if (enableSimulation) {
|
|
1258
1330
|
if (this.isRightClickMouse && this.config.enableRightClickRepulsion) {
|
|
@@ -1314,11 +1386,15 @@ export class Graph {
|
|
|
1314
1386
|
// To prevent the dragged point from suddenly jumping, run the drag function twice
|
|
1315
1387
|
this.points?.drag()
|
|
1316
1388
|
this.points?.drag()
|
|
1389
|
+
// Update tracked positions after drag, even when simulation is disabled
|
|
1390
|
+
this.points?.trackPoints()
|
|
1317
1391
|
}
|
|
1318
1392
|
this.fpsMonitor?.end(now)
|
|
1319
1393
|
|
|
1320
1394
|
this.currentEvent = undefined
|
|
1321
|
-
this.
|
|
1395
|
+
if (!this._isDestroyed) {
|
|
1396
|
+
this.frame()
|
|
1397
|
+
}
|
|
1322
1398
|
})
|
|
1323
1399
|
}
|
|
1324
1400
|
|
|
@@ -1338,6 +1414,23 @@ export class Graph {
|
|
|
1338
1414
|
this.store.hoveredPoint?.position,
|
|
1339
1415
|
event
|
|
1340
1416
|
)
|
|
1417
|
+
|
|
1418
|
+
if (this.store.hoveredPoint) {
|
|
1419
|
+
this.config.onPointClick?.(
|
|
1420
|
+
this.store.hoveredPoint.index,
|
|
1421
|
+
this.store.hoveredPoint.position,
|
|
1422
|
+
event
|
|
1423
|
+
)
|
|
1424
|
+
} else if (this.store.hoveredLinkIndex !== undefined) {
|
|
1425
|
+
this.config.onLinkClick?.(
|
|
1426
|
+
this.store.hoveredLinkIndex,
|
|
1427
|
+
event
|
|
1428
|
+
)
|
|
1429
|
+
} else {
|
|
1430
|
+
this.config.onBackgroundClick?.(
|
|
1431
|
+
event
|
|
1432
|
+
)
|
|
1433
|
+
}
|
|
1341
1434
|
}
|
|
1342
1435
|
|
|
1343
1436
|
private updateMousePosition (event: MouseEvent | D3DragEvent<HTMLCanvasElement, undefined, Hovered>): void {
|
|
@@ -1365,6 +1458,7 @@ export class Graph {
|
|
|
1365
1458
|
}
|
|
1366
1459
|
|
|
1367
1460
|
private resizeCanvas (forceResize = false): void {
|
|
1461
|
+
if (this._isDestroyed) return
|
|
1368
1462
|
const prevWidth = this.canvas.width
|
|
1369
1463
|
const prevHeight = this.canvas.height
|
|
1370
1464
|
const w = this.canvas.clientWidth
|
|
@@ -1382,6 +1476,10 @@ export class Graph {
|
|
|
1382
1476
|
this.canvasD3Selection
|
|
1383
1477
|
?.call(this.zoomInstance.behavior.transform, this.zoomInstance.getTransform([centerPosition], k))
|
|
1384
1478
|
this.points?.updateSampledPointsGrid()
|
|
1479
|
+
// Only update link index FBO if link hovering is enabled
|
|
1480
|
+
if (this.store.isLinkHoveringEnabled) {
|
|
1481
|
+
this.lines?.updateLinkIndexFbo()
|
|
1482
|
+
}
|
|
1385
1483
|
}
|
|
1386
1484
|
}
|
|
1387
1485
|
|
|
@@ -1413,13 +1511,31 @@ export class Graph {
|
|
|
1413
1511
|
}
|
|
1414
1512
|
}
|
|
1415
1513
|
|
|
1416
|
-
private
|
|
1417
|
-
if (
|
|
1418
|
-
if (this.
|
|
1419
|
-
this.
|
|
1514
|
+
private findHoveredItem (): void {
|
|
1515
|
+
if (this._isDestroyed || !this._isMouseOnCanvas || !this.reglInstance) return
|
|
1516
|
+
if (this._findHoveredItemExecutionCount < MAX_HOVER_DETECTION_DELAY) {
|
|
1517
|
+
this._findHoveredItemExecutionCount += 1
|
|
1420
1518
|
return
|
|
1421
1519
|
}
|
|
1422
|
-
this.
|
|
1520
|
+
this._findHoveredItemExecutionCount = 0
|
|
1521
|
+
this.findHoveredPoint()
|
|
1522
|
+
|
|
1523
|
+
if (this.graph.linksNumber && this.store.isLinkHoveringEnabled) {
|
|
1524
|
+
this.findHoveredLine()
|
|
1525
|
+
} else if (this.store.hoveredLinkIndex !== undefined) {
|
|
1526
|
+
// Clear stale hoveredLinkIndex when there are no links
|
|
1527
|
+
const wasHovered = this.store.hoveredLinkIndex !== undefined
|
|
1528
|
+
this.store.hoveredLinkIndex = undefined
|
|
1529
|
+
if (wasHovered && this.config.onLinkMouseOut) {
|
|
1530
|
+
this.config.onLinkMouseOut(this.currentEvent)
|
|
1531
|
+
}
|
|
1532
|
+
}
|
|
1533
|
+
|
|
1534
|
+
this.updateCanvasCursor()
|
|
1535
|
+
}
|
|
1536
|
+
|
|
1537
|
+
private findHoveredPoint (): void {
|
|
1538
|
+
if (this._isDestroyed || !this.reglInstance || !this.points) return
|
|
1423
1539
|
this.points.findHoveredPoint()
|
|
1424
1540
|
let isMouseover = false
|
|
1425
1541
|
let isMouseout = false
|
|
@@ -1447,15 +1563,46 @@ export class Graph {
|
|
|
1447
1563
|
)
|
|
1448
1564
|
}
|
|
1449
1565
|
if (isMouseout) this.config.onPointMouseOut?.(this.currentEvent)
|
|
1450
|
-
|
|
1566
|
+
}
|
|
1567
|
+
|
|
1568
|
+
private findHoveredLine (): void {
|
|
1569
|
+
if (this._isDestroyed || !this.reglInstance || !this.lines) return
|
|
1570
|
+
if (this.store.hoveredPoint) {
|
|
1571
|
+
if (this.store.hoveredLinkIndex !== undefined) {
|
|
1572
|
+
this.store.hoveredLinkIndex = undefined
|
|
1573
|
+
this.config.onLinkMouseOut?.(this.currentEvent)
|
|
1574
|
+
}
|
|
1575
|
+
return
|
|
1576
|
+
}
|
|
1577
|
+
this.lines.findHoveredLine()
|
|
1578
|
+
let isMouseover = false
|
|
1579
|
+
let isMouseout = false
|
|
1580
|
+
|
|
1581
|
+
const pixels = readPixels(this.reglInstance, this.lines.hoveredLineIndexFbo as regl.Framebuffer2D)
|
|
1582
|
+
const hoveredLineIndex = pixels[0] as number
|
|
1583
|
+
|
|
1584
|
+
if (hoveredLineIndex >= 0) {
|
|
1585
|
+
if (this.store.hoveredLinkIndex !== hoveredLineIndex) isMouseover = true
|
|
1586
|
+
this.store.hoveredLinkIndex = hoveredLineIndex
|
|
1587
|
+
} else {
|
|
1588
|
+
if (this.store.hoveredLinkIndex !== undefined) isMouseout = true
|
|
1589
|
+
this.store.hoveredLinkIndex = undefined
|
|
1590
|
+
}
|
|
1591
|
+
|
|
1592
|
+
if (isMouseover && this.store.hoveredLinkIndex !== undefined) {
|
|
1593
|
+
this.config.onLinkMouseOver?.(this.store.hoveredLinkIndex)
|
|
1594
|
+
}
|
|
1595
|
+
if (isMouseout) this.config.onLinkMouseOut?.(this.currentEvent)
|
|
1451
1596
|
}
|
|
1452
1597
|
|
|
1453
1598
|
private updateCanvasCursor (): void {
|
|
1454
|
-
const { hoveredPointCursor } = this.config
|
|
1599
|
+
const { hoveredPointCursor, hoveredLinkCursor } = this.config
|
|
1455
1600
|
if (this.dragInstance.isActive) select(this.canvas).style('cursor', 'grabbing')
|
|
1456
1601
|
else if (this.store.hoveredPoint) {
|
|
1457
1602
|
if (!this.config.enableDrag || this.store.isSpaceKeyPressed) select(this.canvas).style('cursor', hoveredPointCursor)
|
|
1458
1603
|
else select(this.canvas).style('cursor', 'grab')
|
|
1604
|
+
} else if (this.store.isLinkHoveringEnabled && this.store.hoveredLinkIndex !== undefined) {
|
|
1605
|
+
select(this.canvas).style('cursor', hoveredLinkCursor)
|
|
1459
1606
|
} else select(this.canvas).style('cursor', null)
|
|
1460
1607
|
}
|
|
1461
1608
|
|
|
@@ -27,6 +27,7 @@ export class GraphData {
|
|
|
27
27
|
public inputPointClusters: (number | undefined)[] | undefined
|
|
28
28
|
public inputClusterPositions: (number | undefined)[] | undefined
|
|
29
29
|
public inputClusterStrength: Float32Array | undefined
|
|
30
|
+
public inputPinnedPoints: number[] | undefined
|
|
30
31
|
|
|
31
32
|
public pointPositions: Float32Array | undefined
|
|
32
33
|
public pointColors: Float32Array | undefined
|
|
@@ -87,7 +88,7 @@ export class GraphData {
|
|
|
87
88
|
}
|
|
88
89
|
|
|
89
90
|
// 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
|
+
const defaultRgba = getRgbaColor(this._config.pointDefaultColor ?? this._config.pointColor)
|
|
91
92
|
if (this.inputPointColors === undefined || this.inputPointColors.length / 4 !== this.pointsNumber) {
|
|
92
93
|
this.pointColors = new Float32Array(this.pointsNumber * 4)
|
|
93
94
|
for (let i = 0; i < this.pointColors.length / 4; i++) {
|
|
@@ -6,6 +6,10 @@ varying float arrowLength;
|
|
|
6
6
|
varying float useArrow;
|
|
7
7
|
varying float smoothing;
|
|
8
8
|
varying float arrowWidthFactor;
|
|
9
|
+
varying float linkIndex;
|
|
10
|
+
|
|
11
|
+
// renderMode: 0.0 = normal rendering, 1.0 = index buffer rendering for picking
|
|
12
|
+
uniform float renderMode;
|
|
9
13
|
|
|
10
14
|
float map(float value, float min1, float max1, float min2, float max2) {
|
|
11
15
|
return min2 + (value - min1) * (max2 - min2) / (max1 - min1);
|
|
@@ -31,5 +35,12 @@ void main() {
|
|
|
31
35
|
opacity = linkOpacity;
|
|
32
36
|
} else opacity = rgbaColor.a * smoothstep(0.5, 0.5 - smoothing, abs(pos.y));
|
|
33
37
|
|
|
34
|
-
|
|
38
|
+
if (renderMode > 0.0) {
|
|
39
|
+
if (opacity > 0.0) {
|
|
40
|
+
gl_FragColor = vec4(linkIndex, 0.0, 0.0, 1.0);
|
|
41
|
+
} else {
|
|
42
|
+
gl_FragColor = vec4(-1.0, 0.0, 0.0, 0.0);
|
|
43
|
+
}
|
|
44
|
+
} else gl_FragColor = vec4(color, opacity);
|
|
45
|
+
|
|
35
46
|
}
|
|
@@ -4,13 +4,14 @@ attribute vec2 position, pointA, pointB;
|
|
|
4
4
|
attribute vec4 color;
|
|
5
5
|
attribute float width;
|
|
6
6
|
attribute float arrow;
|
|
7
|
+
attribute float linkIndices;
|
|
7
8
|
|
|
8
9
|
uniform sampler2D positionsTexture;
|
|
9
10
|
uniform sampler2D pointGreyoutStatus;
|
|
10
11
|
uniform mat3 transformationMatrix;
|
|
11
12
|
uniform float pointsTextureSize;
|
|
12
13
|
uniform float widthScale;
|
|
13
|
-
uniform float
|
|
14
|
+
uniform float linkArrowsSizeScale;
|
|
14
15
|
uniform float spaceSize;
|
|
15
16
|
uniform vec2 screenSize;
|
|
16
17
|
uniform vec2 linkVisibilityDistanceRange;
|
|
@@ -22,6 +23,11 @@ uniform float curvedLinkControlPointDistance;
|
|
|
22
23
|
uniform float curvedLinkSegments;
|
|
23
24
|
uniform bool scaleLinksOnZoom;
|
|
24
25
|
uniform float maxPointSize;
|
|
26
|
+
// renderMode: 0.0 = normal rendering, 1.0 = index buffer rendering for picking
|
|
27
|
+
uniform float renderMode;
|
|
28
|
+
uniform float hoveredLinkIndex;
|
|
29
|
+
uniform vec4 hoveredLinkColor;
|
|
30
|
+
uniform float hoveredLinkWidthIncrease;
|
|
25
31
|
|
|
26
32
|
varying vec4 rgbaColor;
|
|
27
33
|
varying vec2 pos;
|
|
@@ -29,6 +35,7 @@ varying float arrowLength;
|
|
|
29
35
|
varying float useArrow;
|
|
30
36
|
varying float smoothing;
|
|
31
37
|
varying float arrowWidthFactor;
|
|
38
|
+
varying float linkIndex;
|
|
32
39
|
|
|
33
40
|
float map(float value, float min1, float max1, float min2, float max2) {
|
|
34
41
|
return min2 + (value - min1) * (max2 - min2) / (max1 - min1);
|
|
@@ -73,6 +80,7 @@ float calculateArrowWidth(float arrowWidth) {
|
|
|
73
80
|
|
|
74
81
|
void main() {
|
|
75
82
|
pos = position;
|
|
83
|
+
linkIndex = linkIndices;
|
|
76
84
|
|
|
77
85
|
vec2 pointTexturePosA = (pointA + 0.5) / pointsTextureSize;
|
|
78
86
|
vec2 pointTexturePosB = (pointB + 0.5) / pointsTextureSize;
|
|
@@ -102,7 +110,7 @@ void main() {
|
|
|
102
110
|
float k = 2.0;
|
|
103
111
|
// Arrow width is proportionally larger than the line width
|
|
104
112
|
float arrowWidth = linkWidth * k;
|
|
105
|
-
arrowWidth *=
|
|
113
|
+
arrowWidth *= linkArrowsSizeScale;
|
|
106
114
|
|
|
107
115
|
// Ensure arrow width difference is non-negative to prevent unwanted changes to link width
|
|
108
116
|
float arrowWidthDifference = max(0.0, arrowWidth - linkWidth);
|
|
@@ -124,10 +132,22 @@ void main() {
|
|
|
124
132
|
|
|
125
133
|
// Calculate final link width in pixels with smoothing
|
|
126
134
|
float linkWidthPx = calculateLinkWidth(linkWidth);
|
|
135
|
+
|
|
136
|
+
if (renderMode > 0.0) {
|
|
137
|
+
// Add 5 pixels padding for better hover detection
|
|
138
|
+
linkWidthPx += 5.0 / transformationMatrix[0][0];
|
|
139
|
+
} else {
|
|
140
|
+
// Add pixel increase if this is the hovered link
|
|
141
|
+
if (hoveredLinkIndex == linkIndex) {
|
|
142
|
+
linkWidthPx += hoveredLinkWidthIncrease / transformationMatrix[0][0];
|
|
143
|
+
}
|
|
144
|
+
}
|
|
127
145
|
float smoothingPx = 0.5 / transformationMatrix[0][0];
|
|
128
146
|
smoothing = smoothingPx / linkWidthPx;
|
|
129
147
|
linkWidthPx += smoothingPx;
|
|
130
148
|
|
|
149
|
+
|
|
150
|
+
|
|
131
151
|
// Calculate final color with opacity based on link distance
|
|
132
152
|
vec3 rgbColor = color.rgb;
|
|
133
153
|
// Adjust opacity based on link distance
|
|
@@ -141,6 +161,13 @@ void main() {
|
|
|
141
161
|
// Pass final color to fragment shader
|
|
142
162
|
rgbaColor = vec4(rgbColor, opacity);
|
|
143
163
|
|
|
164
|
+
// Apply hover color if this is the hovered link and hover color is defined
|
|
165
|
+
if (hoveredLinkIndex == linkIndex && hoveredLinkColor.a > -0.5) {
|
|
166
|
+
// Keep existing RGB values but multiply opacity with hover color opacity
|
|
167
|
+
rgbaColor.rgb = hoveredLinkColor.rgb;
|
|
168
|
+
rgbaColor.a *= hoveredLinkColor.a;
|
|
169
|
+
}
|
|
170
|
+
|
|
144
171
|
// Calculate position on the curved path
|
|
145
172
|
float t = position.x;
|
|
146
173
|
float w = curvedWeight;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
precision highp float;
|
|
2
|
+
|
|
3
|
+
uniform sampler2D linkIndexTexture;
|
|
4
|
+
uniform vec2 mousePosition;
|
|
5
|
+
uniform vec2 screenSize;
|
|
6
|
+
|
|
7
|
+
varying vec2 vTexCoord;
|
|
8
|
+
|
|
9
|
+
void main() {
|
|
10
|
+
// Convert mouse position to texture coordinates
|
|
11
|
+
vec2 texCoord = mousePosition / screenSize;
|
|
12
|
+
|
|
13
|
+
// Read the link index from the linkIndexFbo texture at mouse position
|
|
14
|
+
vec4 linkIndexData = texture2D(linkIndexTexture, texCoord);
|
|
15
|
+
|
|
16
|
+
// Extract the link index (stored in the red channel)
|
|
17
|
+
float linkIndex = linkIndexData.r;
|
|
18
|
+
|
|
19
|
+
// Check if there's a valid link at this position (alpha > 0)
|
|
20
|
+
if (linkIndexData.a > 0.0 && linkIndex >= 0.0) {
|
|
21
|
+
// Output the link index
|
|
22
|
+
gl_FragColor = vec4(linkIndex, 0.0, 0.0, 1.0);
|
|
23
|
+
} else {
|
|
24
|
+
// No link at this position, output -1 to indicate no hover
|
|
25
|
+
gl_FragColor = vec4(-1.0, 0.0, 0.0, 0.0);
|
|
26
|
+
}
|
|
27
|
+
}
|