@cosmos.gl/graph 2.0.0 → 2.2.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/dist/config.d.ts +15 -1
- package/dist/index.d.ts +12 -0
- package/dist/index.js +2825 -2635
- package/dist/index.js.map +1 -1
- package/dist/index.min.js +165 -54
- package/dist/index.min.js.map +1 -1
- package/dist/modules/Points/index.d.ts +6 -0
- package/dist/stories/clusters/lasso-selection/index.d.ts +6 -0
- package/dist/stories/clusters/lasso-selection/lasso.d.ts +20 -0
- package/dist/stories/clusters.stories.d.ts +1 -0
- package/dist/variables.d.ts +2 -0
- package/package.json +1 -1
- package/src/config.ts +15 -1
- package/src/index.ts +104 -5
- package/src/modules/Lines/draw-curve-line.frag +7 -8
- package/src/modules/Lines/draw-curve-line.vert +65 -15
- package/src/modules/Lines/index.ts +2 -3
- package/src/modules/Points/find-points-on-lasso-selection.frag +65 -0
- package/src/modules/Points/index.ts +69 -0
- package/src/stories/2. configuration.mdx +3 -1
- package/src/stories/3. api-reference.mdx +19 -0
- package/src/stories/beginners/basic-set-up/index.ts +2 -1
- package/src/stories/beginners/point-labels/index.ts +2 -1
- package/src/stories/beginners/quick-start.ts +1 -0
- package/src/stories/beginners/remove-points/config.ts +2 -1
- package/src/stories/clusters/lasso-selection/index.ts +53 -0
- package/src/stories/clusters/lasso-selection/lasso.ts +143 -0
- package/src/stories/clusters/lasso-selection/style.css +8 -0
- package/src/stories/clusters.stories.ts +16 -0
- package/src/stories/create-cosmos.ts +2 -1
- package/src/stories/generate-mesh-data.ts +1 -1
- package/src/variables.ts +3 -1
|
@@ -21,6 +21,7 @@ export declare class Points extends CoreModule {
|
|
|
21
21
|
private updatePositionCommand;
|
|
22
22
|
private dragPointCommand;
|
|
23
23
|
private findPointsOnAreaSelectionCommand;
|
|
24
|
+
private findPointsOnLassoSelectionCommand;
|
|
24
25
|
private findHoveredPointCommand;
|
|
25
26
|
private clearHoveredFboCommand;
|
|
26
27
|
private clearSampledPointsFboCommand;
|
|
@@ -31,6 +32,9 @@ export declare class Points extends CoreModule {
|
|
|
31
32
|
private greyoutStatusTexture;
|
|
32
33
|
private sizeTexture;
|
|
33
34
|
private trackedIndicesTexture;
|
|
35
|
+
private lassoPathTexture;
|
|
36
|
+
private lassoPathFbo;
|
|
37
|
+
private lassoPathLength;
|
|
34
38
|
private drawPointIndices;
|
|
35
39
|
private hoveredPointIndices;
|
|
36
40
|
private sampledPointIndices;
|
|
@@ -45,6 +49,8 @@ export declare class Points extends CoreModule {
|
|
|
45
49
|
updatePosition(): void;
|
|
46
50
|
drag(): void;
|
|
47
51
|
findPointsOnAreaSelection(): void;
|
|
52
|
+
findPointsOnLassoSelection(): void;
|
|
53
|
+
updateLassoPath(lassoPath: [number, number][]): void;
|
|
48
54
|
findHoveredPoint(): void;
|
|
49
55
|
trackPointsByIndices(indices?: number[] | undefined): void;
|
|
50
56
|
getTrackedPositionsMap(): Map<number, [number, number]>;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export declare class LassoSelection {
|
|
2
|
+
private canvas;
|
|
3
|
+
private ctx;
|
|
4
|
+
private isDrawing;
|
|
5
|
+
private points;
|
|
6
|
+
private graphDiv;
|
|
7
|
+
private onLassoComplete?;
|
|
8
|
+
private boundStartDrawing;
|
|
9
|
+
private boundDraw;
|
|
10
|
+
private boundStopDrawing;
|
|
11
|
+
private resizeObserver;
|
|
12
|
+
constructor(graphDiv: HTMLElement, onLassoComplete?: (points: [number, number][]) => void);
|
|
13
|
+
enableLassoMode(): void;
|
|
14
|
+
disableLassoMode(): void;
|
|
15
|
+
destroy(): void;
|
|
16
|
+
private resizeCanvas;
|
|
17
|
+
private startDrawing;
|
|
18
|
+
private draw;
|
|
19
|
+
private stopDrawing;
|
|
20
|
+
}
|
package/dist/variables.d.ts
CHANGED
|
@@ -43,6 +43,7 @@ export declare const defaultConfigValues: {
|
|
|
43
43
|
showFPSMonitor: boolean;
|
|
44
44
|
pixelRatio: number;
|
|
45
45
|
scalePointsOnZoom: boolean;
|
|
46
|
+
scaleLinksOnZoom: boolean;
|
|
46
47
|
enableZoom: boolean;
|
|
47
48
|
enableSimulationDuringZoom: boolean;
|
|
48
49
|
enableDrag: boolean;
|
|
@@ -53,6 +54,7 @@ export declare const defaultConfigValues: {
|
|
|
53
54
|
pointSamplingDistance: number;
|
|
54
55
|
attribution: string;
|
|
55
56
|
rescalePositions: undefined;
|
|
57
|
+
enableRightClickRepulsion: boolean;
|
|
56
58
|
};
|
|
57
59
|
export declare const hoveredPointRingOpacity = 0.7;
|
|
58
60
|
export declare const focusedPointRingOpacity = 0.95;
|
package/package.json
CHANGED
package/src/config.ts
CHANGED
|
@@ -143,6 +143,11 @@ export interface GraphConfigInterface {
|
|
|
143
143
|
* Default value: `1`
|
|
144
144
|
*/
|
|
145
145
|
linkWidthScale?: number;
|
|
146
|
+
/**
|
|
147
|
+
* Increase or decrease the size of the links when zooming in or out.
|
|
148
|
+
* Default value: `false`
|
|
149
|
+
*/
|
|
150
|
+
scaleLinksOnZoom?: boolean;
|
|
146
151
|
/**
|
|
147
152
|
* If set to true, links are rendered as curved lines.
|
|
148
153
|
* Otherwise as straight lines.
|
|
@@ -252,6 +257,13 @@ export interface GraphConfigInterface {
|
|
|
252
257
|
* Default value: `2`
|
|
253
258
|
*/
|
|
254
259
|
simulationRepulsionFromMouse?: number;
|
|
260
|
+
/**
|
|
261
|
+
* Enable or disable the repulsion force from mouse when right-clicking.
|
|
262
|
+
* When set to `true`, holding the right mouse button will activate the mouse repulsion force.
|
|
263
|
+
* When set to `false`, right-clicking will not trigger any repulsion force.
|
|
264
|
+
* Default value: `false`
|
|
265
|
+
*/
|
|
266
|
+
enableRightClickRepulsion?: boolean;
|
|
255
267
|
/**
|
|
256
268
|
* Friction coefficient.
|
|
257
269
|
* Values range from 0 (high friction, stops quickly) to 1 (no friction, keeps moving).
|
|
@@ -405,7 +417,7 @@ export interface GraphConfigInterface {
|
|
|
405
417
|
pixelRatio?: number;
|
|
406
418
|
/**
|
|
407
419
|
* Increase or decrease the size of the points when zooming in or out.
|
|
408
|
-
* Default value:
|
|
420
|
+
* Default value: `false`
|
|
409
421
|
*/
|
|
410
422
|
scalePointsOnZoom?: boolean;
|
|
411
423
|
/**
|
|
@@ -522,6 +534,7 @@ export class GraphConfig implements GraphConfigInterface {
|
|
|
522
534
|
public curvedLinkControlPointDistance = defaultConfigValues.curvedLinkControlPointDistance
|
|
523
535
|
public linkArrows = defaultConfigValues.arrowLinks
|
|
524
536
|
public linkArrowsSizeScale = defaultConfigValues.arrowSizeScale
|
|
537
|
+
public scaleLinksOnZoom = defaultConfigValues.scaleLinksOnZoom
|
|
525
538
|
public linkVisibilityDistanceRange = defaultConfigValues.linkVisibilityDistanceRange
|
|
526
539
|
public linkVisibilityMinTransparency = defaultConfigValues.linkVisibilityMinTransparency
|
|
527
540
|
public useClassicQuadtree = defaultConfigValues.useClassicQuadtree
|
|
@@ -536,6 +549,7 @@ export class GraphConfig implements GraphConfigInterface {
|
|
|
536
549
|
public simulationLinkDistance = defaultConfigValues.simulation.linkDistance
|
|
537
550
|
public simulationLinkDistRandomVariationRange = defaultConfigValues.simulation.linkDistRandomVariationRange
|
|
538
551
|
public simulationRepulsionFromMouse = defaultConfigValues.simulation.repulsionFromMouse
|
|
552
|
+
public enableRightClickRepulsion = defaultConfigValues.enableRightClickRepulsion
|
|
539
553
|
public simulationFriction = defaultConfigValues.simulation.friction
|
|
540
554
|
public simulationCluster = defaultConfigValues.simulation.cluster
|
|
541
555
|
|
package/src/index.ts
CHANGED
|
@@ -663,6 +663,31 @@ export class Graph {
|
|
|
663
663
|
.filter(d => d !== -1)
|
|
664
664
|
}
|
|
665
665
|
|
|
666
|
+
/**
|
|
667
|
+
* Get points indices inside a lasso (polygon) area.
|
|
668
|
+
* @param lassoPath - Array of points `[[x1, y1], [x2, y2], ..., [xn, yn]]` that defines the lasso polygon.
|
|
669
|
+
* The coordinates should be from 0 to the width/height of the canvas.
|
|
670
|
+
* @returns A Float32Array containing the indices of points inside the lasso area.
|
|
671
|
+
*/
|
|
672
|
+
public getPointsInLasso (lassoPath: [number, number][]): Float32Array {
|
|
673
|
+
if (this._isDestroyed || !this.reglInstance || !this.points) return new Float32Array()
|
|
674
|
+
if (lassoPath.length < 3) return new Float32Array() // Need at least 3 points for a polygon
|
|
675
|
+
|
|
676
|
+
const h = this.store.screenSize[1]
|
|
677
|
+
// Convert coordinates to WebGL coordinate system (flip Y)
|
|
678
|
+
const convertedPath = lassoPath.map(([x, y]) => [x, h - y] as [number, number])
|
|
679
|
+
this.points.updateLassoPath(convertedPath)
|
|
680
|
+
this.points.findPointsOnLassoSelection()
|
|
681
|
+
const pixels = readPixels(this.reglInstance, this.points.selectedFbo as regl.Framebuffer2D)
|
|
682
|
+
|
|
683
|
+
return pixels
|
|
684
|
+
.map((pixel, i) => {
|
|
685
|
+
if (i % 4 === 0 && pixel !== 0) return i / 4
|
|
686
|
+
else return -1
|
|
687
|
+
})
|
|
688
|
+
.filter(d => d !== -1)
|
|
689
|
+
}
|
|
690
|
+
|
|
666
691
|
/** Select points inside a rectangular area.
|
|
667
692
|
* @param selection - Array of two corner points `[[left, top], [right, bottom]]`.
|
|
668
693
|
* The `left` and `right` coordinates should be from 0 to the width of the canvas.
|
|
@@ -686,6 +711,36 @@ export class Graph {
|
|
|
686
711
|
this.points.updateGreyoutStatus()
|
|
687
712
|
}
|
|
688
713
|
|
|
714
|
+
/** Select points inside a lasso (polygon) area.
|
|
715
|
+
* @param lassoPath - Array of points `[[x1, y1], [x2, y2], ..., [xn, yn]]` that defines the lasso polygon.
|
|
716
|
+
* The coordinates should be from 0 to the width/height of the canvas.
|
|
717
|
+
* Set to null to clear selection. */
|
|
718
|
+
public selectPointsInLasso (lassoPath: [number, number][] | null): void {
|
|
719
|
+
if (this._isDestroyed || !this.reglInstance || !this.points) return
|
|
720
|
+
if (lassoPath) {
|
|
721
|
+
if (lassoPath.length < 3) {
|
|
722
|
+
console.warn('Lasso path requires at least 3 points to form a polygon.')
|
|
723
|
+
return
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
const h = this.store.screenSize[1]
|
|
727
|
+
// Convert coordinates to WebGL coordinate system (flip Y)
|
|
728
|
+
const convertedPath = lassoPath.map(([x, y]) => [x, h - y] as [number, number])
|
|
729
|
+
this.points.updateLassoPath(convertedPath)
|
|
730
|
+
this.points.findPointsOnLassoSelection()
|
|
731
|
+
const pixels = readPixels(this.reglInstance, this.points.selectedFbo as regl.Framebuffer2D)
|
|
732
|
+
this.store.selectedIndices = pixels
|
|
733
|
+
.map((pixel, i) => {
|
|
734
|
+
if (i % 4 === 0 && pixel !== 0) return i / 4
|
|
735
|
+
else return -1
|
|
736
|
+
})
|
|
737
|
+
.filter(d => d !== -1)
|
|
738
|
+
} else {
|
|
739
|
+
this.store.selectedIndices = null
|
|
740
|
+
}
|
|
741
|
+
this.points.updateGreyoutStatus()
|
|
742
|
+
}
|
|
743
|
+
|
|
689
744
|
/**
|
|
690
745
|
* Select a point by index. If you want the adjacent points to get selected too, provide `true` as the second argument.
|
|
691
746
|
* @param index The index of the point in the array of points.
|
|
@@ -886,6 +941,39 @@ export class Graph {
|
|
|
886
941
|
if (this._isDestroyed || !this.reglInstance) return
|
|
887
942
|
window.clearTimeout(this._fitViewOnInitTimeoutID)
|
|
888
943
|
this.stopFrames()
|
|
944
|
+
|
|
945
|
+
// Remove all event listeners
|
|
946
|
+
if (this.canvasD3Selection) {
|
|
947
|
+
this.canvasD3Selection
|
|
948
|
+
.on('mouseenter.cosmos', null)
|
|
949
|
+
.on('mousemove.cosmos', null)
|
|
950
|
+
.on('mouseleave.cosmos', null)
|
|
951
|
+
.on('click', null)
|
|
952
|
+
.on('mousemove', null)
|
|
953
|
+
.on('contextmenu', null)
|
|
954
|
+
.on('.drag', null)
|
|
955
|
+
.on('.zoom', null)
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
select(document)
|
|
959
|
+
.on('keydown.cosmos', null)
|
|
960
|
+
.on('keyup.cosmos', null)
|
|
961
|
+
|
|
962
|
+
if (this.zoomInstance?.behavior) {
|
|
963
|
+
this.zoomInstance.behavior
|
|
964
|
+
.on('start.detect', null)
|
|
965
|
+
.on('zoom.detect', null)
|
|
966
|
+
.on('end.detect', null)
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
if (this.dragInstance?.behavior) {
|
|
970
|
+
this.dragInstance.behavior
|
|
971
|
+
.on('start.detect', null)
|
|
972
|
+
.on('drag.detect', null)
|
|
973
|
+
.on('end.detect', null)
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
this.fpsMonitor?.destroy()
|
|
889
977
|
this.reglInstance.destroy()
|
|
890
978
|
// Clears the canvas after particle system is destroyed
|
|
891
979
|
this.reglInstance.clear({
|
|
@@ -893,9 +981,21 @@ export class Graph {
|
|
|
893
981
|
depth: 1,
|
|
894
982
|
stencil: 0,
|
|
895
983
|
})
|
|
896
|
-
|
|
897
|
-
this.
|
|
984
|
+
|
|
985
|
+
if (this.canvas && this.canvas.parentNode) {
|
|
986
|
+
this.canvas.parentNode.removeChild(this.canvas)
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
if (this.attributionDivElement && this.attributionDivElement.parentNode) {
|
|
990
|
+
this.attributionDivElement.parentNode.removeChild(this.attributionDivElement)
|
|
991
|
+
}
|
|
992
|
+
|
|
898
993
|
document.getElementById('gl-bench-style')?.remove()
|
|
994
|
+
|
|
995
|
+
this.canvasD3Selection = undefined
|
|
996
|
+
this.reglInstance = undefined
|
|
997
|
+
this.attributionDivElement = undefined
|
|
998
|
+
|
|
899
999
|
this._isDestroyed = true
|
|
900
1000
|
}
|
|
901
1001
|
|
|
@@ -991,8 +1091,7 @@ export class Graph {
|
|
|
991
1091
|
if (!this.dragInstance.isActive) this.findHoveredPoint()
|
|
992
1092
|
|
|
993
1093
|
if (enableSimulation) {
|
|
994
|
-
if (this.isRightClickMouse) {
|
|
995
|
-
if (!isSimulationRunning) this.start(0.1)
|
|
1094
|
+
if (this.isRightClickMouse && this.config.enableRightClickRepulsion) {
|
|
996
1095
|
this.forceMouse?.run()
|
|
997
1096
|
this.points?.updatePosition()
|
|
998
1097
|
}
|
|
@@ -1023,7 +1122,7 @@ export class Graph {
|
|
|
1023
1122
|
}
|
|
1024
1123
|
|
|
1025
1124
|
this.store.alpha += this.store.addAlpha(this.config.simulationDecay ?? defaultConfigValues.simulation.decay)
|
|
1026
|
-
if (this.isRightClickMouse) this.store.alpha = Math.max(this.store.alpha, 0.1)
|
|
1125
|
+
if (this.isRightClickMouse && this.config.enableRightClickRepulsion) this.store.alpha = Math.max(this.store.alpha, 0.1)
|
|
1027
1126
|
this.store.simulationProgress = Math.sqrt(Math.min(1, ALPHA_MIN / this.store.alpha))
|
|
1028
1127
|
this.config.onSimulationTick?.(
|
|
1029
1128
|
this.store.alpha,
|
|
@@ -3,9 +3,8 @@ precision highp float;
|
|
|
3
3
|
varying vec4 rgbaColor;
|
|
4
4
|
varying vec2 pos;
|
|
5
5
|
varying float arrowLength;
|
|
6
|
-
varying float linkWidthArrowWidthRatio;
|
|
7
|
-
varying float smoothWidthRatio;
|
|
8
6
|
varying float useArrow;
|
|
7
|
+
varying float smoothing;
|
|
9
8
|
|
|
10
9
|
float map(float value, float min1, float max1, float min2, float max2) {
|
|
11
10
|
return min2 + (value - min1) * (max2 - min2) / (max1 - min1);
|
|
@@ -14,22 +13,22 @@ float map(float value, float min1, float max1, float min2, float max2) {
|
|
|
14
13
|
void main() {
|
|
15
14
|
float opacity = 1.0;
|
|
16
15
|
vec3 color = rgbaColor.rgb;
|
|
17
|
-
|
|
16
|
+
|
|
18
17
|
if (useArrow > 0.5) {
|
|
19
18
|
float end_arrow = 0.5 + arrowLength / 2.0;
|
|
20
19
|
float start_arrow = end_arrow - arrowLength;
|
|
21
|
-
float arrowWidthDelta =
|
|
22
|
-
float linkOpacity = rgbaColor.a * smoothstep(0.5 - arrowWidthDelta, 0.5 - arrowWidthDelta -
|
|
20
|
+
float arrowWidthDelta = 0.25;
|
|
21
|
+
float linkOpacity = rgbaColor.a * smoothstep(0.5 - arrowWidthDelta, 0.5 - arrowWidthDelta - smoothing / 2.0, abs(pos.y));
|
|
23
22
|
float arrowOpacity = 1.0;
|
|
24
23
|
if (pos.x > start_arrow && pos.x < start_arrow + arrowLength) {
|
|
25
24
|
float xmapped = map(pos.x, start_arrow, end_arrow, 0.0, 1.0);
|
|
26
|
-
arrowOpacity = rgbaColor.a * smoothstep(xmapped -
|
|
25
|
+
arrowOpacity = rgbaColor.a * smoothstep(xmapped - smoothing, xmapped, map(abs(pos.y), 0.5, 0.0, 0.0, 1.0));
|
|
27
26
|
if (linkOpacity != arrowOpacity) {
|
|
28
|
-
linkOpacity
|
|
27
|
+
linkOpacity = max(linkOpacity, arrowOpacity);
|
|
29
28
|
}
|
|
30
29
|
}
|
|
31
30
|
opacity = linkOpacity;
|
|
32
|
-
} else opacity = rgbaColor.a * smoothstep(0.5, 0.5 -
|
|
31
|
+
} else opacity = rgbaColor.a * smoothstep(0.5, 0.5 - smoothing, abs(pos.y));
|
|
33
32
|
|
|
34
33
|
gl_FragColor = vec4(color, opacity);
|
|
35
34
|
}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
precision highp float;
|
|
2
|
+
|
|
2
3
|
attribute vec2 position, pointA, pointB;
|
|
3
4
|
attribute vec4 color;
|
|
4
5
|
attribute float width;
|
|
5
6
|
attribute float arrow;
|
|
7
|
+
|
|
6
8
|
uniform sampler2D positionsTexture;
|
|
7
9
|
uniform sampler2D pointGreyoutStatus;
|
|
8
10
|
uniform mat3 transformationMatrix;
|
|
@@ -11,20 +13,20 @@ uniform float widthScale;
|
|
|
11
13
|
uniform float arrowSizeScale;
|
|
12
14
|
uniform float spaceSize;
|
|
13
15
|
uniform vec2 screenSize;
|
|
14
|
-
uniform float ratio;
|
|
15
16
|
uniform vec2 linkVisibilityDistanceRange;
|
|
16
17
|
uniform float linkVisibilityMinTransparency;
|
|
17
18
|
uniform float greyoutOpacity;
|
|
18
19
|
uniform float curvedWeight;
|
|
19
20
|
uniform float curvedLinkControlPointDistance;
|
|
20
21
|
uniform float curvedLinkSegments;
|
|
22
|
+
uniform bool scaleLinksOnZoom;
|
|
23
|
+
uniform float maxPointSize;
|
|
21
24
|
|
|
22
25
|
varying vec4 rgbaColor;
|
|
23
26
|
varying vec2 pos;
|
|
24
27
|
varying float arrowLength;
|
|
25
|
-
varying float linkWidthArrowWidthRatio;
|
|
26
|
-
varying float smoothWidthRatio;
|
|
27
28
|
varying float useArrow;
|
|
29
|
+
varying float smoothing;
|
|
28
30
|
|
|
29
31
|
float map(float value, float min1, float max1, float min2, float max2) {
|
|
30
32
|
return min2 + (value - min1) * (max2 - min2) / (max1 - min1);
|
|
@@ -36,19 +38,48 @@ vec2 conicParametricCurve(vec2 A, vec2 B, vec2 ControlPoint, float t, float w) {
|
|
|
36
38
|
return divident / divisor;
|
|
37
39
|
}
|
|
38
40
|
|
|
41
|
+
float calculateLinkWidth(float width) {
|
|
42
|
+
float linkWidth;
|
|
43
|
+
if (scaleLinksOnZoom) {
|
|
44
|
+
// Use original width if links should scale with zoom
|
|
45
|
+
linkWidth = width;
|
|
46
|
+
} else {
|
|
47
|
+
// Adjust width based on zoom level to maintain visual size
|
|
48
|
+
linkWidth = width / transformationMatrix[0][0];
|
|
49
|
+
// Apply a non-linear scaling to avoid extreme widths
|
|
50
|
+
linkWidth *= min(5.0, max(1.0, transformationMatrix[0][0] * 0.01));
|
|
51
|
+
}
|
|
52
|
+
// Limit link width based on whether it has an arrow
|
|
53
|
+
if (useArrow > 0.5) {
|
|
54
|
+
return min(linkWidth, (maxPointSize * 2.0) / transformationMatrix[0][0]);
|
|
55
|
+
} else {
|
|
56
|
+
return min(linkWidth, maxPointSize / transformationMatrix[0][0]);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
float calculateArrowWidth(float arrowWidth) {
|
|
61
|
+
if (scaleLinksOnZoom) {
|
|
62
|
+
return arrowWidth;
|
|
63
|
+
} else {
|
|
64
|
+
return arrowWidth / transformationMatrix[0][0];
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
39
68
|
void main() {
|
|
40
69
|
pos = position;
|
|
41
70
|
|
|
42
71
|
vec2 pointTexturePosA = (pointA + 0.5) / pointsTextureSize;
|
|
43
72
|
vec2 pointTexturePosB = (pointB + 0.5) / pointsTextureSize;
|
|
44
|
-
|
|
73
|
+
|
|
45
74
|
vec4 greyoutStatusA = texture2D(pointGreyoutStatus, pointTexturePosA);
|
|
46
75
|
vec4 greyoutStatusB = texture2D(pointGreyoutStatus, pointTexturePosB);
|
|
47
|
-
|
|
76
|
+
|
|
48
77
|
vec4 pointPositionA = texture2D(positionsTexture, pointTexturePosA);
|
|
49
78
|
vec4 pointPositionB = texture2D(positionsTexture, pointTexturePosB);
|
|
50
79
|
vec2 a = pointPositionA.xy;
|
|
51
80
|
vec2 b = pointPositionB.xy;
|
|
81
|
+
|
|
82
|
+
// Calculate direction vector and its perpendicular
|
|
52
83
|
vec2 xBasis = b - a;
|
|
53
84
|
vec2 yBasis = normalize(vec2(-xBasis.y, xBasis.x));
|
|
54
85
|
|
|
@@ -57,50 +88,69 @@ void main() {
|
|
|
57
88
|
float h = curvedLinkControlPointDistance;
|
|
58
89
|
vec2 controlPoint = (a + b) / 2.0 + yBasis * linkDist * h;
|
|
59
90
|
|
|
91
|
+
// Convert link distance to screen pixels
|
|
60
92
|
float linkDistPx = linkDist * transformationMatrix[0][0];
|
|
61
93
|
|
|
94
|
+
// Calculate line width using the width scale
|
|
62
95
|
float linkWidth = width * widthScale;
|
|
63
96
|
float k = 2.0;
|
|
97
|
+
// Arrow width is proportionally larger than the line width
|
|
64
98
|
float arrowWidth = max(5.0, linkWidth * k);
|
|
65
99
|
arrowWidth *= arrowSizeScale;
|
|
66
100
|
|
|
67
|
-
|
|
101
|
+
// Calculate arrow width in pixels
|
|
102
|
+
float arrowWidthPx = calculateArrowWidth(arrowWidth);
|
|
103
|
+
|
|
104
|
+
// Calculate arrow length proportional to its width
|
|
105
|
+
// 0.866 is approximately sqrt(3)/2 - related to equilateral triangle geometry
|
|
106
|
+
// Cap the length to avoid overly long arrows on short links
|
|
68
107
|
arrowLength = min(0.3, (0.866 * arrowWidthPx * 2.0) / linkDist);
|
|
69
108
|
|
|
70
|
-
float smoothWidth = 2.0;
|
|
71
|
-
float arrowExtraWidth = arrowWidth - linkWidth;
|
|
72
|
-
linkWidth += smoothWidth / 2.0;
|
|
73
109
|
useArrow = arrow;
|
|
74
110
|
if (useArrow > 0.5) {
|
|
75
|
-
linkWidth
|
|
111
|
+
linkWidth *= 2.0;
|
|
76
112
|
}
|
|
77
|
-
smoothWidthRatio = smoothWidth / linkWidth;
|
|
78
|
-
linkWidthArrowWidthRatio = arrowExtraWidth / linkWidth;
|
|
79
113
|
|
|
80
|
-
|
|
114
|
+
// Calculate final link width in pixels with smoothing
|
|
115
|
+
float linkWidthPx = calculateLinkWidth(linkWidth);
|
|
116
|
+
float smoothingPx = 0.5 / transformationMatrix[0][0];
|
|
117
|
+
smoothing = smoothingPx / linkWidthPx;
|
|
118
|
+
linkWidthPx += smoothingPx;
|
|
81
119
|
|
|
82
|
-
//
|
|
120
|
+
// Calculate final color with opacity based on link distance
|
|
83
121
|
vec3 rgbColor = color.rgb;
|
|
122
|
+
// Adjust opacity based on link distance
|
|
84
123
|
float opacity = color.a * max(linkVisibilityMinTransparency, map(linkDistPx, linkVisibilityDistanceRange.g, linkVisibilityDistanceRange.r, 0.0, 1.0));
|
|
85
124
|
|
|
125
|
+
// Apply greyed out opacity if either endpoint is greyed out
|
|
86
126
|
if (greyoutStatusA.r > 0.0 || greyoutStatusB.r > 0.0) {
|
|
87
127
|
opacity *= greyoutOpacity;
|
|
88
128
|
}
|
|
89
129
|
|
|
130
|
+
// Pass final color to fragment shader
|
|
90
131
|
rgbaColor = vec4(rgbColor, opacity);
|
|
91
132
|
|
|
133
|
+
// Calculate position on the curved path
|
|
92
134
|
float t = position.x;
|
|
93
135
|
float w = curvedWeight;
|
|
136
|
+
|
|
94
137
|
float tPrev = t - 1.0 / curvedLinkSegments;
|
|
95
138
|
float tNext = t + 1.0 / curvedLinkSegments;
|
|
139
|
+
|
|
96
140
|
vec2 pointCurr = conicParametricCurve(a, b, controlPoint, t, w);
|
|
141
|
+
|
|
97
142
|
vec2 pointPrev = conicParametricCurve(a, b, controlPoint, max(0.0, tPrev), w);
|
|
98
143
|
vec2 pointNext = conicParametricCurve(a, b, controlPoint, min(tNext, 1.0), w);
|
|
144
|
+
|
|
99
145
|
vec2 xBasisCurved = pointNext - pointPrev;
|
|
100
146
|
vec2 yBasisCurved = normalize(vec2(-xBasisCurved.y, xBasisCurved.x));
|
|
147
|
+
|
|
101
148
|
pointCurr += yBasisCurved * linkWidthPx * position.y;
|
|
149
|
+
|
|
150
|
+
// Transform to clip space coordinates
|
|
102
151
|
vec2 p = 2.0 * pointCurr / spaceSize - 1.0;
|
|
103
152
|
p *= spaceSize / screenSize;
|
|
104
|
-
vec3 final =
|
|
153
|
+
vec3 final = transformationMatrix * vec3(p, 1);
|
|
154
|
+
|
|
105
155
|
gl_Position = vec4(final.rg, 0, 1);
|
|
106
156
|
}
|
|
@@ -63,16 +63,15 @@ export class Lines extends CoreModule {
|
|
|
63
63
|
pointGreyoutStatus: () => this.points?.greyoutStatusFbo,
|
|
64
64
|
transformationMatrix: () => store.transform,
|
|
65
65
|
pointsTextureSize: () => store.pointsTextureSize,
|
|
66
|
-
pointSizeScale: () => config.pointSizeScale,
|
|
67
66
|
widthScale: () => config.linkWidthScale,
|
|
68
67
|
arrowSizeScale: () => config.linkArrowsSizeScale,
|
|
69
68
|
spaceSize: () => store.adjustedSpaceSize,
|
|
70
69
|
screenSize: () => store.screenSize,
|
|
71
|
-
ratio: () => config.pixelRatio,
|
|
72
70
|
linkVisibilityDistanceRange: () => config.linkVisibilityDistanceRange,
|
|
73
71
|
linkVisibilityMinTransparency: () => config.linkVisibilityMinTransparency,
|
|
74
72
|
greyoutOpacity: () => config.linkGreyoutOpacity,
|
|
75
|
-
|
|
73
|
+
scaleLinksOnZoom: () => config.scaleLinksOnZoom,
|
|
74
|
+
maxPointSize: () => store.maxPointSize,
|
|
76
75
|
curvedWeight: () => config.curvedLinkWeight,
|
|
77
76
|
curvedLinkControlPointDistance: () => config.curvedLinkControlPointDistance,
|
|
78
77
|
curvedLinkSegments: () => config.curvedLinks ? config.curvedLinkSegments ?? defaultConfigValues.curvedLinkSegments : 1,
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
#ifdef GL_ES
|
|
2
|
+
precision highp float;
|
|
3
|
+
#endif
|
|
4
|
+
|
|
5
|
+
uniform sampler2D positionsTexture;
|
|
6
|
+
uniform sampler2D lassoPathTexture; // Texture containing lasso path points
|
|
7
|
+
uniform int lassoPathLength;
|
|
8
|
+
uniform float spaceSize;
|
|
9
|
+
uniform vec2 screenSize;
|
|
10
|
+
uniform mat3 transformationMatrix;
|
|
11
|
+
|
|
12
|
+
varying vec2 textureCoords;
|
|
13
|
+
|
|
14
|
+
// Get a point from the lasso path texture at a specific index
|
|
15
|
+
vec2 getLassoPoint(sampler2D pathTexture, int index, int pathLength) {
|
|
16
|
+
if (index >= pathLength) return vec2(0.0);
|
|
17
|
+
|
|
18
|
+
// Calculate texture coordinates for the index
|
|
19
|
+
int textureSize = int(ceil(sqrt(float(pathLength))));
|
|
20
|
+
int x = index - (index / textureSize) * textureSize;
|
|
21
|
+
int y = index / textureSize;
|
|
22
|
+
|
|
23
|
+
vec2 texCoord = (vec2(float(x), float(y)) + 0.5) / float(textureSize);
|
|
24
|
+
vec4 pathData = texture2D(pathTexture, texCoord);
|
|
25
|
+
|
|
26
|
+
return pathData.xy;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Point-in-polygon algorithm using ray casting
|
|
30
|
+
bool pointInPolygon(vec2 point, sampler2D pathTexture, int pathLength) {
|
|
31
|
+
bool inside = false;
|
|
32
|
+
|
|
33
|
+
for (int i = 0; i < 2048; i++) {
|
|
34
|
+
if (i >= pathLength) break;
|
|
35
|
+
|
|
36
|
+
int j = int(mod(float(i + 1), float(pathLength)));
|
|
37
|
+
|
|
38
|
+
vec2 pi = getLassoPoint(pathTexture, i, pathLength);
|
|
39
|
+
vec2 pj = getLassoPoint(pathTexture, j, pathLength);
|
|
40
|
+
|
|
41
|
+
if (((pi.y > point.y) != (pj.y > point.y)) &&
|
|
42
|
+
(point.x < (pj.x - pi.x) * (point.y - pi.y) / (pj.y - pi.y) + pi.x)) {
|
|
43
|
+
inside = !inside;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return inside;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
void main() {
|
|
51
|
+
vec4 pointPosition = texture2D(positionsTexture, textureCoords);
|
|
52
|
+
vec2 p = 2.0 * pointPosition.rg / spaceSize - 1.0;
|
|
53
|
+
p *= spaceSize / screenSize;
|
|
54
|
+
vec3 final = transformationMatrix * vec3(p, 1);
|
|
55
|
+
|
|
56
|
+
// Convert to screen coordinates for polygon check
|
|
57
|
+
vec2 screenPos = (final.xy + 1.0) * screenSize / 2.0;
|
|
58
|
+
|
|
59
|
+
gl_FragColor = vec4(0.0, 0.0, pointPosition.rg);
|
|
60
|
+
|
|
61
|
+
// Check if point center is inside the lasso polygon
|
|
62
|
+
if (pointInPolygon(screenPos, lassoPathTexture, lassoPathLength)) {
|
|
63
|
+
gl_FragColor.r = 1.0;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -6,6 +6,7 @@ import { defaultConfigValues } from '@/graph/variables'
|
|
|
6
6
|
import drawPointsFrag from '@/graph/modules/Points/draw-points.frag'
|
|
7
7
|
import drawPointsVert from '@/graph/modules/Points/draw-points.vert'
|
|
8
8
|
import findPointsOnAreaSelectionFrag from '@/graph/modules/Points/find-points-on-area-selection.frag'
|
|
9
|
+
import findPointsOnLassoSelectionFrag from '@/graph/modules/Points/find-points-on-lasso-selection.frag'
|
|
9
10
|
import drawHighlightedFrag from '@/graph/modules/Points/draw-highlighted.frag'
|
|
10
11
|
import drawHighlightedVert from '@/graph/modules/Points/draw-highlighted.vert'
|
|
11
12
|
import findHoveredPointFrag from '@/graph/modules/Points/find-hovered-point.frag'
|
|
@@ -41,6 +42,7 @@ export class Points extends CoreModule {
|
|
|
41
42
|
private updatePositionCommand: regl.DrawCommand | undefined
|
|
42
43
|
private dragPointCommand: regl.DrawCommand | undefined
|
|
43
44
|
private findPointsOnAreaSelectionCommand: regl.DrawCommand | undefined
|
|
45
|
+
private findPointsOnLassoSelectionCommand: regl.DrawCommand | undefined
|
|
44
46
|
private findHoveredPointCommand: regl.DrawCommand | undefined
|
|
45
47
|
private clearHoveredFboCommand: regl.DrawCommand | undefined
|
|
46
48
|
private clearSampledPointsFboCommand: regl.DrawCommand | undefined
|
|
@@ -51,6 +53,9 @@ export class Points extends CoreModule {
|
|
|
51
53
|
private greyoutStatusTexture: regl.Texture2D | undefined
|
|
52
54
|
private sizeTexture: regl.Texture2D | undefined
|
|
53
55
|
private trackedIndicesTexture: regl.Texture2D | undefined
|
|
56
|
+
private lassoPathTexture: regl.Texture2D | undefined
|
|
57
|
+
private lassoPathFbo: regl.Framebuffer2D | undefined
|
|
58
|
+
private lassoPathLength = 0
|
|
54
59
|
private drawPointIndices: regl.Buffer | undefined
|
|
55
60
|
private hoveredPointIndices: regl.Buffer | undefined
|
|
56
61
|
private sampledPointIndices: regl.Buffer | undefined
|
|
@@ -280,6 +285,27 @@ export class Points extends CoreModule {
|
|
|
280
285
|
})
|
|
281
286
|
}
|
|
282
287
|
|
|
288
|
+
if (!this.findPointsOnLassoSelectionCommand) {
|
|
289
|
+
this.findPointsOnLassoSelectionCommand = reglInstance({
|
|
290
|
+
frag: findPointsOnLassoSelectionFrag,
|
|
291
|
+
vert: updateVert,
|
|
292
|
+
framebuffer: () => this.selectedFbo as regl.Framebuffer2D,
|
|
293
|
+
primitive: 'triangle strip',
|
|
294
|
+
count: 4,
|
|
295
|
+
attributes: {
|
|
296
|
+
vertexCoord: createQuadBuffer(reglInstance),
|
|
297
|
+
},
|
|
298
|
+
uniforms: {
|
|
299
|
+
positionsTexture: () => this.currentPositionFbo,
|
|
300
|
+
spaceSize: () => store.adjustedSpaceSize,
|
|
301
|
+
screenSize: () => store.screenSize,
|
|
302
|
+
transformationMatrix: () => store.transform,
|
|
303
|
+
lassoPathTexture: () => this.lassoPathTexture,
|
|
304
|
+
lassoPathLength: () => this.lassoPathLength,
|
|
305
|
+
},
|
|
306
|
+
})
|
|
307
|
+
}
|
|
308
|
+
|
|
283
309
|
if (!this.clearHoveredFboCommand) {
|
|
284
310
|
this.clearHoveredFboCommand = reglInstance({
|
|
285
311
|
frag: clearFrag,
|
|
@@ -547,6 +573,49 @@ export class Points extends CoreModule {
|
|
|
547
573
|
this.findPointsOnAreaSelectionCommand?.()
|
|
548
574
|
}
|
|
549
575
|
|
|
576
|
+
public findPointsOnLassoSelection (): void {
|
|
577
|
+
this.findPointsOnLassoSelectionCommand?.()
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
public updateLassoPath (lassoPath: [number, number][]): void {
|
|
581
|
+
const { reglInstance } = this
|
|
582
|
+
this.lassoPathLength = lassoPath.length
|
|
583
|
+
|
|
584
|
+
if (lassoPath.length === 0) {
|
|
585
|
+
this.lassoPathTexture = undefined
|
|
586
|
+
this.lassoPathFbo = undefined
|
|
587
|
+
return
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
// Calculate texture size (square texture)
|
|
591
|
+
const textureSize = Math.ceil(Math.sqrt(lassoPath.length))
|
|
592
|
+
const textureData = new Float32Array(textureSize * textureSize * 4)
|
|
593
|
+
|
|
594
|
+
// Fill texture with lasso path points
|
|
595
|
+
for (const [i, point] of lassoPath.entries()) {
|
|
596
|
+
const [x, y] = point
|
|
597
|
+
textureData[i * 4] = x
|
|
598
|
+
textureData[i * 4 + 1] = y
|
|
599
|
+
textureData[i * 4 + 2] = 0 // unused
|
|
600
|
+
textureData[i * 4 + 3] = 0 // unused
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
if (!this.lassoPathTexture) this.lassoPathTexture = reglInstance.texture()
|
|
604
|
+
this.lassoPathTexture({
|
|
605
|
+
data: textureData,
|
|
606
|
+
width: textureSize,
|
|
607
|
+
height: textureSize,
|
|
608
|
+
type: 'float',
|
|
609
|
+
})
|
|
610
|
+
|
|
611
|
+
if (!this.lassoPathFbo) this.lassoPathFbo = reglInstance.framebuffer()
|
|
612
|
+
this.lassoPathFbo({
|
|
613
|
+
color: this.lassoPathTexture,
|
|
614
|
+
depth: false,
|
|
615
|
+
stencil: false,
|
|
616
|
+
})
|
|
617
|
+
}
|
|
618
|
+
|
|
550
619
|
public findHoveredPoint (): void {
|
|
551
620
|
this.clearHoveredFboCommand?.()
|
|
552
621
|
this.findHoveredPointCommand?.()
|