@cosmos.gl/graph 2.3.0 → 2.3.1-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/config.d.ts +7 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +1140 -946
- package/dist/index.js.map +1 -1
- package/dist/index.min.js +193 -44
- package/dist/index.min.js.map +1 -1
- package/dist/modules/GraphData/index.d.ts +16 -0
- package/dist/modules/Points/index.d.ts +2 -0
- package/dist/stories/shapes/all-shapes/index.d.ts +5 -0
- package/dist/stories/shapes.stories.d.ts +6 -0
- package/package.json +1 -1
- package/src/config.ts +7 -0
- package/src/index.ts +22 -2
- package/src/modules/GraphData/index.ts +39 -0
- package/src/modules/Lines/draw-curve-line.vert +7 -2
- package/src/modules/Points/draw-points.frag +131 -6
- package/src/modules/Points/draw-points.vert +22 -1
- package/src/modules/Points/index.ts +26 -1
- package/src/stories/2. configuration.mdx +1 -0
- package/src/stories/3. api-reference.mdx +33 -0
- package/src/stories/shapes/all-shapes/index.ts +69 -0
- package/src/stories/shapes.stories.ts +25 -0
|
@@ -1,8 +1,19 @@
|
|
|
1
1
|
import { GraphConfig } from '../../config';
|
|
2
|
+
export declare enum PointShape {
|
|
3
|
+
Circle = 0,
|
|
4
|
+
Square = 1,
|
|
5
|
+
Triangle = 2,
|
|
6
|
+
Diamond = 3,
|
|
7
|
+
Pentagon = 4,
|
|
8
|
+
Hexagon = 5,
|
|
9
|
+
Star = 6,
|
|
10
|
+
Cross = 7
|
|
11
|
+
}
|
|
2
12
|
export declare class GraphData {
|
|
3
13
|
inputPointPositions: Float32Array | undefined;
|
|
4
14
|
inputPointColors: Float32Array | undefined;
|
|
5
15
|
inputPointSizes: Float32Array | undefined;
|
|
16
|
+
inputPointShapes: Float32Array | undefined;
|
|
6
17
|
inputLinkColors: Float32Array | undefined;
|
|
7
18
|
inputLinkWidths: Float32Array | undefined;
|
|
8
19
|
inputLinkStrength: Float32Array | undefined;
|
|
@@ -12,6 +23,7 @@ export declare class GraphData {
|
|
|
12
23
|
pointPositions: Float32Array | undefined;
|
|
13
24
|
pointColors: Float32Array | undefined;
|
|
14
25
|
pointSizes: Float32Array | undefined;
|
|
26
|
+
pointShapes: Float32Array | undefined;
|
|
15
27
|
inputLinks: Float32Array | undefined;
|
|
16
28
|
links: Float32Array | undefined;
|
|
17
29
|
linkColors: Float32Array | undefined;
|
|
@@ -45,6 +57,10 @@ export declare class GraphData {
|
|
|
45
57
|
* Updates the point sizes based on the input data or default config value.
|
|
46
58
|
*/
|
|
47
59
|
updatePointSize(): void;
|
|
60
|
+
/**
|
|
61
|
+
* Updates the point shapes based on the input data or default shape (Circle).
|
|
62
|
+
*/
|
|
63
|
+
updatePointShape(): void;
|
|
48
64
|
updateLinks(): void;
|
|
49
65
|
/**
|
|
50
66
|
* Updates the link colors based on the input data or default config value.
|
|
@@ -13,6 +13,7 @@ export declare class Points extends CoreModule {
|
|
|
13
13
|
private colorBuffer;
|
|
14
14
|
private sizeFbo;
|
|
15
15
|
private sizeBuffer;
|
|
16
|
+
private shapeBuffer;
|
|
16
17
|
private trackedIndicesFbo;
|
|
17
18
|
private trackedPositionsFbo;
|
|
18
19
|
private sampledPointsFbo;
|
|
@@ -43,6 +44,7 @@ export declare class Points extends CoreModule {
|
|
|
43
44
|
updateColor(): void;
|
|
44
45
|
updateGreyoutStatus(): void;
|
|
45
46
|
updateSize(): void;
|
|
47
|
+
updateShape(): void;
|
|
46
48
|
updateSampledPointsGrid(): void;
|
|
47
49
|
trackPoints(): void;
|
|
48
50
|
draw(): void;
|
package/package.json
CHANGED
package/src/config.ts
CHANGED
|
@@ -492,6 +492,12 @@ export interface GraphConfigInterface {
|
|
|
492
492
|
* Default: `undefined`
|
|
493
493
|
*/
|
|
494
494
|
fitViewByPointsInRect?: [[number, number], [number, number]] | [number, number][];
|
|
495
|
+
/**
|
|
496
|
+
* When `fitViewOnInit` is set to `true`, fits the view to show only the specified points by their indices.
|
|
497
|
+
* Takes precedence over `fitViewByPointsInRect` when both are provided.
|
|
498
|
+
* Default: `undefined`
|
|
499
|
+
*/
|
|
500
|
+
fitViewByPointIndices?: number[];
|
|
495
501
|
/**
|
|
496
502
|
* Providing a `randomSeed` value allows you to control
|
|
497
503
|
* the randomness of the layout across different simulation runs.
|
|
@@ -605,6 +611,7 @@ export class GraphConfig implements GraphConfigInterface {
|
|
|
605
611
|
public fitViewPadding = defaultConfigValues.fitViewPadding
|
|
606
612
|
public fitViewDuration = defaultConfigValues.fitViewDuration
|
|
607
613
|
public fitViewByPointsInRect = undefined
|
|
614
|
+
public fitViewByPointIndices = undefined
|
|
608
615
|
|
|
609
616
|
public randomSeed = undefined
|
|
610
617
|
public pointSamplingDistance = defaultConfigValues.pointSamplingDistance
|
package/src/index.ts
CHANGED
|
@@ -67,6 +67,7 @@ export class Graph {
|
|
|
67
67
|
private _needsPointPositionsUpdate = false
|
|
68
68
|
private _needsPointColorUpdate = false
|
|
69
69
|
private _needsPointSizeUpdate = false
|
|
70
|
+
private _needsPointShapeUpdate = false
|
|
70
71
|
private _needsLinksUpdate = false
|
|
71
72
|
private _needsLinkColorUpdate = false
|
|
72
73
|
private _needsLinkWidthUpdate = false
|
|
@@ -303,6 +304,7 @@ export class Graph {
|
|
|
303
304
|
// Point related textures depend on point positions length, so we need to update them
|
|
304
305
|
this._needsPointColorUpdate = true
|
|
305
306
|
this._needsPointSizeUpdate = true
|
|
307
|
+
this._needsPointShapeUpdate = true
|
|
306
308
|
this._needsPointClusterUpdate = true
|
|
307
309
|
this._needsForceManyBodyUpdate = true
|
|
308
310
|
this._needsForceLinkUpdate = true
|
|
@@ -346,6 +348,20 @@ export class Graph {
|
|
|
346
348
|
this._needsPointSizeUpdate = true
|
|
347
349
|
}
|
|
348
350
|
|
|
351
|
+
/**
|
|
352
|
+
* Sets the shapes for the graph points.
|
|
353
|
+
*
|
|
354
|
+
* @param {Float32Array} pointShapes - A Float32Array representing the shapes of points in the format [shape1, shape2, ..., shapen],
|
|
355
|
+
* where `n` is the index of the point and each shape value corresponds to a PointShape enum:
|
|
356
|
+
* 0 = Circle, 1 = Square, 2 = Triangle, 3 = Diamond, 4 = Pentagon, 5 = Hexagon, 6 = Star, 7 = Cross.
|
|
357
|
+
* Example: `new Float32Array([0, 1, 2])` sets the first point to Circle, the second point to Square, and the third point to Triangle.
|
|
358
|
+
*/
|
|
359
|
+
public setPointShapes (pointShapes: Float32Array): void {
|
|
360
|
+
if (this._isDestroyed) return
|
|
361
|
+
this.graph.inputPointShapes = pointShapes
|
|
362
|
+
this._needsPointShapeUpdate = true
|
|
363
|
+
}
|
|
364
|
+
|
|
349
365
|
/**
|
|
350
366
|
* Gets the current sizes of the graph points.
|
|
351
367
|
*
|
|
@@ -509,7 +525,7 @@ export class Graph {
|
|
|
509
525
|
public render (simulationAlpha?: number): void {
|
|
510
526
|
if (this._isDestroyed || !this.reglInstance) return
|
|
511
527
|
this.graph.update()
|
|
512
|
-
const { fitViewOnInit, fitViewDelay, fitViewPadding, fitViewDuration, fitViewByPointsInRect, initialZoomLevel } = this.config
|
|
528
|
+
const { fitViewOnInit, fitViewDelay, fitViewPadding, fitViewDuration, fitViewByPointsInRect, fitViewByPointIndices, initialZoomLevel } = this.config
|
|
513
529
|
if (!this.graph.pointsNumber && !this.graph.linksNumber) {
|
|
514
530
|
this.stopFrames()
|
|
515
531
|
select(this.canvas).style('cursor', null)
|
|
@@ -524,7 +540,8 @@ export class Graph {
|
|
|
524
540
|
// If `initialZoomLevel` is set, we don't need to fit the view
|
|
525
541
|
if (this._isFirstRenderAfterInit && fitViewOnInit && initialZoomLevel === undefined) {
|
|
526
542
|
this._fitViewOnInitTimeoutID = window.setTimeout(() => {
|
|
527
|
-
if (
|
|
543
|
+
if (fitViewByPointIndices) this.fitViewByPointIndices(fitViewByPointIndices, fitViewDuration, fitViewPadding)
|
|
544
|
+
else if (fitViewByPointsInRect) this.setZoomTransformByPointPositions(fitViewByPointsInRect, fitViewDuration, undefined, fitViewPadding)
|
|
528
545
|
else this.fitView(fitViewDuration, fitViewPadding)
|
|
529
546
|
}, fitViewDelay)
|
|
530
547
|
}
|
|
@@ -1100,6 +1117,7 @@ export class Graph {
|
|
|
1100
1117
|
if (this._needsPointPositionsUpdate) this.points.updatePositions()
|
|
1101
1118
|
if (this._needsPointColorUpdate) this.points.updateColor()
|
|
1102
1119
|
if (this._needsPointSizeUpdate) this.points.updateSize()
|
|
1120
|
+
if (this._needsPointShapeUpdate) this.points.updateShape()
|
|
1103
1121
|
|
|
1104
1122
|
if (this._needsLinksUpdate) this.lines.updatePointsBuffer()
|
|
1105
1123
|
if (this._needsLinkColorUpdate) this.lines.updateColor()
|
|
@@ -1117,6 +1135,7 @@ export class Graph {
|
|
|
1117
1135
|
this._needsPointPositionsUpdate = false
|
|
1118
1136
|
this._needsPointColorUpdate = false
|
|
1119
1137
|
this._needsPointSizeUpdate = false
|
|
1138
|
+
this._needsPointShapeUpdate = false
|
|
1120
1139
|
this._needsLinksUpdate = false
|
|
1121
1140
|
this._needsLinkColorUpdate = false
|
|
1122
1141
|
this._needsLinkWidthUpdate = false
|
|
@@ -1412,5 +1431,6 @@ export class Graph {
|
|
|
1412
1431
|
}
|
|
1413
1432
|
|
|
1414
1433
|
export type { GraphConfigInterface } from './config'
|
|
1434
|
+
export { PointShape } from './modules/GraphData'
|
|
1415
1435
|
|
|
1416
1436
|
export * from './helper'
|
|
@@ -1,9 +1,22 @@
|
|
|
1
1
|
import { getRgbaColor, isNumber } from '@/graph/helper'
|
|
2
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
|
+
}
|
|
14
|
+
|
|
3
15
|
export class GraphData {
|
|
4
16
|
public inputPointPositions: Float32Array | undefined
|
|
5
17
|
public inputPointColors: Float32Array | undefined
|
|
6
18
|
public inputPointSizes: Float32Array | undefined
|
|
19
|
+
public inputPointShapes: Float32Array | undefined
|
|
7
20
|
public inputLinkColors: Float32Array | undefined
|
|
8
21
|
public inputLinkWidths: Float32Array | undefined
|
|
9
22
|
public inputLinkStrength: Float32Array | undefined
|
|
@@ -14,6 +27,7 @@ export class GraphData {
|
|
|
14
27
|
public pointPositions: Float32Array | undefined
|
|
15
28
|
public pointColors: Float32Array | undefined
|
|
16
29
|
public pointSizes: Float32Array | undefined
|
|
30
|
+
public pointShapes: Float32Array | undefined
|
|
17
31
|
|
|
18
32
|
public inputLinks: Float32Array | undefined
|
|
19
33
|
public links: Float32Array | undefined
|
|
@@ -109,6 +123,30 @@ export class GraphData {
|
|
|
109
123
|
}
|
|
110
124
|
}
|
|
111
125
|
|
|
126
|
+
/**
|
|
127
|
+
* Updates the point shapes based on the input data or default shape (Circle).
|
|
128
|
+
*/
|
|
129
|
+
public updatePointShape (): void {
|
|
130
|
+
if (this.pointsNumber === undefined) {
|
|
131
|
+
this.pointShapes = undefined
|
|
132
|
+
return
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Sets point shapes to default values (Circle) if the input is missing or does not match input points number.
|
|
136
|
+
if (this.inputPointShapes === undefined || this.inputPointShapes.length !== this.pointsNumber) {
|
|
137
|
+
this.pointShapes = new Float32Array(this.pointsNumber).fill(PointShape.Circle)
|
|
138
|
+
} else {
|
|
139
|
+
this.pointShapes = new Float32Array(this.inputPointShapes)
|
|
140
|
+
const pointShapes = this.pointShapes
|
|
141
|
+
for (let i = 0; i < pointShapes.length; i++) {
|
|
142
|
+
const shape = pointShapes[i]
|
|
143
|
+
if (shape == null || !isNumber(shape) || shape < 0 || shape > 7) {
|
|
144
|
+
pointShapes[i] = PointShape.Circle
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
112
150
|
public updateLinks (): void {
|
|
113
151
|
this.links = this.inputLinks
|
|
114
152
|
}
|
|
@@ -222,6 +260,7 @@ export class GraphData {
|
|
|
222
260
|
this.updatePoints()
|
|
223
261
|
this.updatePointColor()
|
|
224
262
|
this.updatePointSize()
|
|
263
|
+
this.updatePointShape()
|
|
225
264
|
|
|
226
265
|
this.updateLinks()
|
|
227
266
|
this.updateLinkColor()
|
|
@@ -63,7 +63,11 @@ float calculateArrowWidth(float arrowWidth) {
|
|
|
63
63
|
if (scaleLinksOnZoom) {
|
|
64
64
|
return arrowWidth;
|
|
65
65
|
} else {
|
|
66
|
-
|
|
66
|
+
// Apply the same scaling logic as calculateLinkWidth to maintain proportionality
|
|
67
|
+
arrowWidth = arrowWidth / transformationMatrix[0][0];
|
|
68
|
+
// Apply the same non-linear scaling to avoid extreme widths
|
|
69
|
+
arrowWidth *= min(5.0, max(1.0, transformationMatrix[0][0] * 0.01));
|
|
70
|
+
return arrowWidth;
|
|
67
71
|
}
|
|
68
72
|
}
|
|
69
73
|
|
|
@@ -100,7 +104,8 @@ void main() {
|
|
|
100
104
|
float arrowWidth = linkWidth * k;
|
|
101
105
|
arrowWidth *= arrowSizeScale;
|
|
102
106
|
|
|
103
|
-
|
|
107
|
+
// Ensure arrow width difference is non-negative to prevent unwanted changes to link width
|
|
108
|
+
float arrowWidthDifference = max(0.0, arrowWidth - linkWidth);
|
|
104
109
|
|
|
105
110
|
// Calculate arrow width in pixels
|
|
106
111
|
float arrowWidthPx = calculateArrowWidth(arrowWidth);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// Fragment shader for rendering points with
|
|
1
|
+
// Fragment shader for rendering points with various shapes and smooth edges
|
|
2
2
|
|
|
3
3
|
#ifdef GL_ES
|
|
4
4
|
precision highp float;
|
|
@@ -7,20 +7,145 @@ precision highp float;
|
|
|
7
7
|
// The color and alpha values from the vertex shader
|
|
8
8
|
varying vec3 rgbColor;
|
|
9
9
|
varying float alpha;
|
|
10
|
+
varying float pointShape;
|
|
10
11
|
|
|
11
|
-
// Smoothing
|
|
12
|
+
// Smoothing controls the smoothness of the point's edge
|
|
12
13
|
const float smoothing = 0.9;
|
|
13
14
|
|
|
15
|
+
// Shape constants
|
|
16
|
+
const float CIRCLE = 0.0;
|
|
17
|
+
const float SQUARE = 1.0;
|
|
18
|
+
const float TRIANGLE = 2.0;
|
|
19
|
+
const float DIAMOND = 3.0;
|
|
20
|
+
const float PENTAGON = 4.0;
|
|
21
|
+
const float HEXAGON = 5.0;
|
|
22
|
+
const float STAR = 6.0;
|
|
23
|
+
const float CROSS = 7.0;
|
|
24
|
+
|
|
25
|
+
// Distance functions for different shapes
|
|
26
|
+
float circleDistance(vec2 p) {
|
|
27
|
+
return dot(p, p);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
float squareDistance(vec2 p) {
|
|
31
|
+
vec2 d = abs(p) - vec2(0.8);
|
|
32
|
+
return length(max(d, 0.0)) + min(max(d.x, d.y), 0.0);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
float triangleDistance(vec2 p) {
|
|
36
|
+
const float k = sqrt(3.0); // ≈1.732; slope of 60° lines for an equilateral triangle
|
|
37
|
+
p.x = abs(p.x) - 0.9; // fold the X axis and shift: brings left and right halves together
|
|
38
|
+
p.y = p.y + 0.55; // move the whole shape up slightly so it is centred vertically
|
|
39
|
+
|
|
40
|
+
// reflect points that fall outside the main triangle back inside, to reuse the same maths
|
|
41
|
+
if (p.x + k * p.y > 0.0)
|
|
42
|
+
p = vec2(p.x - k * p.y, -k * p.x - p.y) / 2.0;
|
|
43
|
+
|
|
44
|
+
p.x -= clamp(p.x, -1.0, 0.0); // clip any remainder on the left side
|
|
45
|
+
|
|
46
|
+
// Return signed distance: negative = inside; positive = outside
|
|
47
|
+
return -length(p) * sign(p.y);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
float diamondDistance(vec2 p) {
|
|
51
|
+
// aspect > 1 → taller diamond
|
|
52
|
+
const float aspect = 1.2;
|
|
53
|
+
return abs(p.x) + abs(p.y) / aspect - 0.8;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
float pentagonDistance(vec2 p) {
|
|
57
|
+
// Regular pentagon signed-distance (Inigo Quilez)
|
|
58
|
+
const vec3 k = vec3(0.809016994, 0.587785252, 0.726542528);
|
|
59
|
+
p.x = abs(p.x);
|
|
60
|
+
|
|
61
|
+
// Reflect across the two tilted edges ─ only if point is outside
|
|
62
|
+
p -= 2.0 * min(dot(vec2(-k.x, k.y), p), 0.0) * vec2(-k.x, k.y);
|
|
63
|
+
p -= 2.0 * min(dot(vec2( k.x, k.y), p), 0.0) * vec2( k.x, k.y);
|
|
64
|
+
|
|
65
|
+
// Clip against the top horizontal edge (keeps top point sharp)
|
|
66
|
+
p -= vec2(clamp(p.x, -k.z * k.x, k.z * k.x), k.z);
|
|
67
|
+
|
|
68
|
+
// Return signed distance (negative → inside, positive → outside)
|
|
69
|
+
return length(p) * sign(p.y);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
float hexagonDistance(vec2 p) {
|
|
73
|
+
const vec3 k = vec3(-0.866025404, 0.5, 0.577350269);
|
|
74
|
+
p = abs(p);
|
|
75
|
+
p -= 2.0 * min(dot(k.xy, p), 0.0) * k.xy;
|
|
76
|
+
p -= vec2(clamp(p.x, -k.z * 0.8, k.z * 0.8), 0.8);
|
|
77
|
+
return length(p) * sign(p.y);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
float starDistance(vec2 p) {
|
|
81
|
+
// 5-point star signed-distance function (adapted from Inigo Quilez)
|
|
82
|
+
// r – outer radius, rf – inner/outer radius ratio
|
|
83
|
+
const float r = 0.9;
|
|
84
|
+
const float rf = 0.45;
|
|
85
|
+
|
|
86
|
+
// Pre-computed rotation vectors for the star arms (36° increments)
|
|
87
|
+
const vec2 k1 = vec2(0.809016994, -0.587785252);
|
|
88
|
+
const vec2 k2 = vec2(-k1.x, k1.y);
|
|
89
|
+
|
|
90
|
+
// Fold the plane into a single arm sector
|
|
91
|
+
p.x = abs(p.x);
|
|
92
|
+
p -= 2.0 * max(dot(k1, p), 0.0) * k1;
|
|
93
|
+
p -= 2.0 * max(dot(k2, p), 0.0) * k2;
|
|
94
|
+
p.x = abs(p.x);
|
|
95
|
+
|
|
96
|
+
// Translate so the top tip of the star lies on the X-axis
|
|
97
|
+
p.y -= r;
|
|
98
|
+
|
|
99
|
+
// Vector describing the edge between an outer tip and its adjacent inner point
|
|
100
|
+
vec2 ba = rf * vec2(-k1.y, k1.x) - vec2(0.0, 1.0);
|
|
101
|
+
// Project the point onto that edge and clamp the projection to the segment
|
|
102
|
+
float h = clamp(dot(p, ba) / dot(ba, ba), 0.0, r);
|
|
103
|
+
|
|
104
|
+
// Return signed distance (negative => inside, positive => outside)
|
|
105
|
+
return length(p - ba * h) * sign(p.y * ba.x - p.x * ba.y);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
float crossDistance(vec2 p) {
|
|
109
|
+
// Signed distance function for a cross (union of two rectangles)
|
|
110
|
+
// Adapted from Inigo Quilez (https://iquilezles.org/)
|
|
111
|
+
// Each arm has half-sizes 0.3 (thickness) and 0.8 (length)
|
|
112
|
+
p = abs(p);
|
|
113
|
+
if (p.y > p.x) p = p.yx; // exploit symmetry
|
|
114
|
+
|
|
115
|
+
vec2 q = p - vec2(0.8, 0.3); // subtract half-sizes (length, thickness)
|
|
116
|
+
|
|
117
|
+
// Standard rectangle SDF, then take union of the two arms
|
|
118
|
+
return length(max(q, 0.0)) + min(max(q.x, q.y), 0.0);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
float getShapeDistance(vec2 p, float shape) {
|
|
122
|
+
if (shape == SQUARE) return squareDistance(p);
|
|
123
|
+
else if (shape == TRIANGLE) return triangleDistance(p);
|
|
124
|
+
else if (shape == DIAMOND) return diamondDistance(p);
|
|
125
|
+
else if (shape == PENTAGON) return pentagonDistance(p);
|
|
126
|
+
else if (shape == HEXAGON) return hexagonDistance(p);
|
|
127
|
+
else if (shape == STAR) return starDistance(p);
|
|
128
|
+
else if (shape == CROSS) return crossDistance(p);
|
|
129
|
+
else return circleDistance(p); // Default to circle
|
|
130
|
+
}
|
|
131
|
+
|
|
14
132
|
void main() {
|
|
15
133
|
// Discard the fragment if the point is fully transparent
|
|
16
134
|
if (alpha == 0.0) { discard; }
|
|
17
135
|
|
|
18
136
|
// Calculate coordinates within the point
|
|
19
137
|
vec2 pointCoord = 2.0 * gl_PointCoord - 1.0;
|
|
20
|
-
|
|
21
|
-
float
|
|
22
|
-
|
|
23
|
-
|
|
138
|
+
|
|
139
|
+
float opacity;
|
|
140
|
+
if (pointShape == CIRCLE) {
|
|
141
|
+
// For circles, use the original distance calculation
|
|
142
|
+
float pointCenterDistance = dot(pointCoord, pointCoord);
|
|
143
|
+
opacity = alpha * (1.0 - smoothstep(smoothing, 1.0, pointCenterDistance));
|
|
144
|
+
} else {
|
|
145
|
+
// For other shapes, use the shape distance function
|
|
146
|
+
float shapeDistance = getShapeDistance(pointCoord, pointShape);
|
|
147
|
+
opacity = alpha * (1.0 - smoothstep(-0.01, 0.01, shapeDistance));
|
|
148
|
+
}
|
|
24
149
|
|
|
25
150
|
gl_FragColor = vec4(rgbColor, opacity);
|
|
26
151
|
}
|
|
@@ -5,6 +5,7 @@ precision highp float;
|
|
|
5
5
|
attribute vec2 pointIndices;
|
|
6
6
|
attribute float size;
|
|
7
7
|
attribute vec4 color;
|
|
8
|
+
attribute float shape;
|
|
8
9
|
|
|
9
10
|
uniform sampler2D positionsTexture;
|
|
10
11
|
uniform sampler2D pointGreyoutStatus;
|
|
@@ -21,10 +22,13 @@ uniform vec4 backgroundColor;
|
|
|
21
22
|
uniform bool scalePointsOnZoom;
|
|
22
23
|
uniform float maxPointSize;
|
|
23
24
|
uniform bool darkenGreyout;
|
|
25
|
+
uniform bool skipSelected;
|
|
26
|
+
uniform bool skipUnselected;
|
|
24
27
|
|
|
25
28
|
varying vec2 textureCoords;
|
|
26
29
|
varying vec3 rgbColor;
|
|
27
30
|
varying float alpha;
|
|
31
|
+
varying float pointShape;
|
|
28
32
|
|
|
29
33
|
float calculatePointSize(float size) {
|
|
30
34
|
float pSize;
|
|
@@ -39,6 +43,23 @@ float calculatePointSize(float size) {
|
|
|
39
43
|
|
|
40
44
|
void main() {
|
|
41
45
|
textureCoords = pointIndices;
|
|
46
|
+
|
|
47
|
+
// Check greyout status for selective rendering
|
|
48
|
+
vec4 greyoutStatus = texture2D(pointGreyoutStatus, (textureCoords + 0.5) / pointsTextureSize);
|
|
49
|
+
bool isSelected = greyoutStatus.r == 0.0;
|
|
50
|
+
|
|
51
|
+
// Discard point based on rendering mode
|
|
52
|
+
if (skipSelected && isSelected) {
|
|
53
|
+
gl_Position = vec4(2.0, 2.0, 2.0, 1.0); // Move off-screen
|
|
54
|
+
gl_PointSize = 0.0;
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
if (skipUnselected && !isSelected) {
|
|
58
|
+
gl_Position = vec4(2.0, 2.0, 2.0, 1.0); // Move off-screen
|
|
59
|
+
gl_PointSize = 0.0;
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
|
|
42
63
|
// Position
|
|
43
64
|
vec4 pointPosition = texture2D(positionsTexture, (textureCoords + 0.5) / pointsTextureSize);
|
|
44
65
|
vec2 point = pointPosition.rg;
|
|
@@ -53,9 +74,9 @@ void main() {
|
|
|
53
74
|
|
|
54
75
|
rgbColor = color.rgb;
|
|
55
76
|
alpha = color.a * pointOpacity;
|
|
77
|
+
pointShape = shape;
|
|
56
78
|
|
|
57
79
|
// Adjust alpha of selected points
|
|
58
|
-
vec4 greyoutStatus = texture2D(pointGreyoutStatus, (textureCoords + 0.5) / pointsTextureSize);
|
|
59
80
|
if (greyoutStatus.r > 0.0) {
|
|
60
81
|
if (greyoutColor[0] != -1.0) {
|
|
61
82
|
rgbColor = greyoutColor.rgb;
|
|
@@ -34,6 +34,7 @@ export class Points extends CoreModule {
|
|
|
34
34
|
private colorBuffer: regl.Buffer | undefined
|
|
35
35
|
private sizeFbo: regl.Framebuffer2D | undefined
|
|
36
36
|
private sizeBuffer: regl.Buffer | undefined
|
|
37
|
+
private shapeBuffer: regl.Buffer | undefined
|
|
37
38
|
private trackedIndicesFbo: regl.Framebuffer2D | undefined
|
|
38
39
|
private trackedPositionsFbo: regl.Framebuffer2D | undefined
|
|
39
40
|
private sampledPointsFbo: regl.Framebuffer2D | undefined
|
|
@@ -222,6 +223,10 @@ export class Points extends CoreModule {
|
|
|
222
223
|
buffer: () => this.colorBuffer,
|
|
223
224
|
size: 4,
|
|
224
225
|
},
|
|
226
|
+
shape: {
|
|
227
|
+
buffer: () => this.shapeBuffer,
|
|
228
|
+
size: 1,
|
|
229
|
+
},
|
|
225
230
|
},
|
|
226
231
|
uniforms: {
|
|
227
232
|
positionsTexture: () => this.currentPositionFbo,
|
|
@@ -239,6 +244,8 @@ export class Points extends CoreModule {
|
|
|
239
244
|
darkenGreyout: () => store.darkenGreyout,
|
|
240
245
|
scalePointsOnZoom: () => config.scalePointsOnZoom,
|
|
241
246
|
maxPointSize: () => store.maxPointSize,
|
|
247
|
+
skipSelected: reglInstance.prop<{ skipSelected: boolean }, 'skipSelected'>('skipSelected'),
|
|
248
|
+
skipUnselected: reglInstance.prop<{ skipUnselected: boolean }, 'skipUnselected'>('skipUnselected'),
|
|
242
249
|
},
|
|
243
250
|
blend: {
|
|
244
251
|
enable: true,
|
|
@@ -518,6 +525,13 @@ export class Points extends CoreModule {
|
|
|
518
525
|
})
|
|
519
526
|
}
|
|
520
527
|
|
|
528
|
+
public updateShape (): void {
|
|
529
|
+
const { reglInstance, data } = this
|
|
530
|
+
if (data.pointsNumber === undefined || data.pointShapes === undefined) return
|
|
531
|
+
if (!this.shapeBuffer) this.shapeBuffer = reglInstance.buffer(0)
|
|
532
|
+
this.shapeBuffer(data.pointShapes)
|
|
533
|
+
}
|
|
534
|
+
|
|
521
535
|
public updateSampledPointsGrid (): void {
|
|
522
536
|
const { store: { screenSize }, config: { pointSamplingDistance }, reglInstance } = this
|
|
523
537
|
let dist = pointSamplingDistance ?? Math.min(...screenSize) / 2
|
|
@@ -542,7 +556,18 @@ export class Points extends CoreModule {
|
|
|
542
556
|
const { config: { renderHoveredPointRing, pointSize }, store, data } = this
|
|
543
557
|
if (!this.colorBuffer) this.updateColor()
|
|
544
558
|
if (!this.sizeBuffer) this.updateSize()
|
|
545
|
-
this.
|
|
559
|
+
if (!this.shapeBuffer) this.updateShape()
|
|
560
|
+
|
|
561
|
+
// Render in layers: unselected points first (behind), then selected points (in front)
|
|
562
|
+
if (store.selectedIndices && store.selectedIndices.length > 0) {
|
|
563
|
+
// First draw unselected points (they will appear behind)
|
|
564
|
+
this.drawCommand?.({ skipSelected: true, skipUnselected: false })
|
|
565
|
+
// Then draw selected points (they will appear in front)
|
|
566
|
+
this.drawCommand?.({ skipSelected: false, skipUnselected: true })
|
|
567
|
+
} else {
|
|
568
|
+
// If no selection, draw all points
|
|
569
|
+
this.drawCommand?.({ skipSelected: false, skipUnselected: false })
|
|
570
|
+
}
|
|
546
571
|
if ((renderHoveredPointRing) && store.hoveredPoint) {
|
|
547
572
|
this.drawHighlightedCommand?.({
|
|
548
573
|
width: 0.85,
|
|
@@ -48,6 +48,7 @@ import { Meta } from "@storybook/blocks";
|
|
|
48
48
|
| fitViewPadding | Extra space around points when fitting view | `0.1` |
|
|
49
49
|
| fitViewDuration | Animation duration for fit view operation in milliseconds | `250` |
|
|
50
50
|
| fitViewByPointsInRect | When `fitViewOnInit` is set to `true`, fits the view to show the points within a rectangle defined by its two corner coordinates `[[left, bottom], [right, top]]` in the scene space | `undefined` |
|
|
51
|
+
| fitViewByPointIndices | When `fitViewOnInit` is set to `true`, fits the view to show only the specified points by their indices. Takes precedence over `fitViewByPointsInRect` when both are provided. | `undefined` |
|
|
51
52
|
| randomSeed | Providing a value allows control over layout randomness for consistency across simulations. Applied only on initialization | `undefined` |
|
|
52
53
|
| pointSamplingDistance | Sampling density for point position methods (used in `getSampledPointPositionsMap`) in pixels | `150` |
|
|
53
54
|
| attribution | Controls the text shown in the bottom right corner. Provide HTML content as a string for custom attribution. Empty string hides attribution | `''` |
|
|
@@ -75,6 +75,39 @@ This method retrieves the current sizes of the graph points that were previously
|
|
|
75
75
|
|
|
76
76
|
The returned array contains the processed point sizes used for rendering, including any default values that were applied during processing.
|
|
77
77
|
|
|
78
|
+
### <a name="set_point_shapes" href="#set_point_shapes">#</a> graph.<b>setPointShapes</b>(<i>pointShapes</i>)
|
|
79
|
+
|
|
80
|
+
This method sets the shapes for the graph points.
|
|
81
|
+
|
|
82
|
+
* **`pointShapes`** (Float32Array): A Float32Array representing the shapes of points in the format `[shape1, shape2, ..., shapeN]`, where each `shape` value corresponds to the shape of the point at the same index in the graph's data. Each shape value should be one of the available shape constants:
|
|
83
|
+
|
|
84
|
+
| Shape Value | Shape Name |
|
|
85
|
+
|-------------|------------|
|
|
86
|
+
| 0 | Circle |
|
|
87
|
+
| 1 | Square |
|
|
88
|
+
| 2 | Triangle |
|
|
89
|
+
| 3 | Diamond |
|
|
90
|
+
| 4 | Pentagon |
|
|
91
|
+
| 5 | Hexagon |
|
|
92
|
+
| 6 | Star |
|
|
93
|
+
| 7 | Cross |
|
|
94
|
+
|
|
95
|
+
Each shape value in the array specifies the shape of a point using the same order as the points in the graph data. Invalid shape values (outside the range 0-7) will default to Circle (0).
|
|
96
|
+
|
|
97
|
+
**Example:**
|
|
98
|
+
```javascript
|
|
99
|
+
import { Graph, PointShape } from '@cosmos.gl/graph'
|
|
100
|
+
|
|
101
|
+
// Create a mixed set of shapes using the PointShape enum
|
|
102
|
+
graph.setPointShapes(new Float32Array([
|
|
103
|
+
PointShape.Circle, // Circle for first point
|
|
104
|
+
PointShape.Star, // Star for second point
|
|
105
|
+
PointShape.Triangle, // Triangle for third point
|
|
106
|
+
PointShape.Hexagon // Hexagon for fourth point
|
|
107
|
+
]));
|
|
108
|
+
graph.render();
|
|
109
|
+
```
|
|
110
|
+
|
|
78
111
|
### <a name="set_links" href="#set_links">#</a> graph.<b>setLinks</b>(<i>links</i>)
|
|
79
112
|
|
|
80
113
|
This method sets the links (connections) between points in a cosmos.gl graph.
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { Graph, PointShape } from '@cosmos.gl/graph'
|
|
2
|
+
|
|
3
|
+
export const allShapes = (): {div: HTMLDivElement; graph: Graph } => {
|
|
4
|
+
// Create container div
|
|
5
|
+
const div = document.createElement('div')
|
|
6
|
+
div.style.height = '100vh'
|
|
7
|
+
div.style.width = '100%'
|
|
8
|
+
|
|
9
|
+
// Create 8 points, one for each shape
|
|
10
|
+
const spaceSize = 4096
|
|
11
|
+
const pointCount = 8
|
|
12
|
+
const spacing = spaceSize / pointCount
|
|
13
|
+
|
|
14
|
+
const pointPositions = new Float32Array(pointCount * 2)
|
|
15
|
+
const pointColors = new Float32Array(pointCount * 4)
|
|
16
|
+
const pointShapes = new Float32Array(pointCount)
|
|
17
|
+
|
|
18
|
+
// Define distinct colors for each shape
|
|
19
|
+
const shapeColors: [number, number, number][] = [
|
|
20
|
+
[1.0, 0.42, 0.38], // Coral for Circle
|
|
21
|
+
[0.13, 0.55, 0.45], // Forest Green for Square
|
|
22
|
+
[0.25, 0.32, 0.71], // Royal Blue for Triangle
|
|
23
|
+
[0.96, 0.76, 0.19], // Amber Gold for Diamond
|
|
24
|
+
[0.74, 0.24, 0.45], // Deep Rose for Pentagon
|
|
25
|
+
[0.18, 0.55, 0.56], // Teal for Hexagon
|
|
26
|
+
[0.85, 0.45, 0.28], // Terracotta for Star
|
|
27
|
+
[0.58, 0.44, 0.86], // Periwinkle for Cross
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
// Position points in a horizontal line
|
|
31
|
+
const startX = spacing / 2
|
|
32
|
+
const centerY = spaceSize / 2
|
|
33
|
+
for (let i = 0; i < pointCount; i++) {
|
|
34
|
+
// Position: horizontal line, centered
|
|
35
|
+
pointPositions[i * 2] = startX + i * spacing
|
|
36
|
+
pointPositions[i * 2 + 1] = centerY
|
|
37
|
+
|
|
38
|
+
// Color: distinct color for each shape
|
|
39
|
+
const color = shapeColors[i] || [1.0, 1.0, 1.0] // fallback to white if undefined
|
|
40
|
+
pointColors[i * 4] = color[0] // R
|
|
41
|
+
pointColors[i * 4 + 1] = color[1] // G
|
|
42
|
+
pointColors[i * 4 + 2] = color[2] // B
|
|
43
|
+
pointColors[i * 4 + 3] = 1.0 // A
|
|
44
|
+
|
|
45
|
+
// Shape: cycle through all available shapes
|
|
46
|
+
pointShapes[i] = i as PointShape
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Create graph with minimal configuration
|
|
50
|
+
const graph = new Graph(div, {
|
|
51
|
+
spaceSize,
|
|
52
|
+
pointSize: spacing / 2,
|
|
53
|
+
enableSimulation: false,
|
|
54
|
+
scalePointsOnZoom: true,
|
|
55
|
+
renderHoveredPointRing: true,
|
|
56
|
+
hoveredPointRingColor: '#ffffff',
|
|
57
|
+
rescalePositions: false,
|
|
58
|
+
enableDrag: true,
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
// Set data
|
|
62
|
+
graph.setPointPositions(pointPositions)
|
|
63
|
+
graph.setPointColors(pointColors)
|
|
64
|
+
graph.setPointShapes(pointShapes)
|
|
65
|
+
|
|
66
|
+
graph.render()
|
|
67
|
+
|
|
68
|
+
return { div, graph }
|
|
69
|
+
}
|