@fusion.dev/helix-graph 0.214.0 → 0.215.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.
@@ -1,115 +1,79 @@
1
- import { NodeProgram } from 'sigma/rendering';
2
- import { floatColor } from 'sigma/utils';
3
- export const SHAPES = { circle: 0, diamond: 2, ring: 3, square: 1 };
1
+ export const SHAPES = {
2
+ circle: 'helixCircle',
3
+ diamond: 'helixDiamond',
4
+ ring: 'helixCircle',
5
+ square: 'helixSquare',
6
+ };
4
7
  export const EMPHASES = { outlined: 1, solid: 0, subtle: 2 };
5
8
  export const MATERIALS = { flat: 0, frosted: 2, glass: 1, mark: 3 };
6
- const REACH = 3.4;
7
- const VERTEX = `
8
- attribute vec4 a_id;
9
- attribute vec4 a_color;
10
- attribute vec2 a_position;
11
- attribute float a_size;
12
- attribute float a_shape;
13
- attribute float a_emphasis;
14
- attribute float a_material;
15
- attribute float a_glow;
16
- attribute float a_angle;
17
-
18
- uniform mat3 u_matrix;
19
- uniform float u_sizeRatio;
20
- uniform float u_correctionRatio;
21
-
22
- varying vec4 v_color;
23
- varying vec2 v_diffVector;
24
- varying float v_radius;
25
- varying float v_shape;
26
- varying float v_emphasis;
27
- varying float v_material;
28
- varying float v_glow;
29
-
30
- const float bias = 255.0 / 254.0;
31
-
32
- void main() {
33
- float size = a_size * u_correctionRatio / u_sizeRatio * 4.0;
34
- vec2 diffVector = size * ${REACH.toFixed(2)} * vec2(cos(a_angle), sin(a_angle));
35
- gl_Position = vec4((u_matrix * vec3(a_position + diffVector, 1)).xy, 0, 1);
36
- v_diffVector = diffVector;
37
- v_radius = size / 2.0;
38
- v_shape = a_shape;
39
- v_emphasis = a_emphasis;
40
- v_material = a_material;
41
- v_glow = a_glow;
42
- #ifdef PICKING_MODE
43
- v_color = a_id;
44
- #else
45
- v_color = a_color;
46
- #endif
47
- v_color.a *= bias;
9
+ export const DIAMOND = 0.86 / Math.SQRT1_2;
10
+ const circle = {
11
+ glsl: `
12
+ float sdf_helixCircle(vec2 uv, float size) {
13
+ return length(uv) - size;
48
14
  }
49
- `;
50
- const FRAGMENT = `
51
- precision highp float;
52
-
53
- varying vec4 v_color;
54
- varying vec2 v_diffVector;
55
- varying float v_radius;
56
- varying float v_shape;
57
- varying float v_emphasis;
58
- varying float v_material;
59
- varying float v_glow;
60
-
61
- uniform float u_correctionRatio;
62
-
63
- /* How far a point is outside the note's shape, in radii: below zero inside. */
64
- float outside(vec2 p, float shape) {
65
- if (shape > 0.5 && shape < 1.5) {
66
- vec2 q = abs(p) - vec2(0.7);
67
- return length(max(q, 0.0)) + min(max(q.x, q.y), 0.0) - 0.18;
68
- }
69
- if (shape > 1.5 && shape < 2.5) {
70
- return (abs(p.x) + abs(p.y)) * 0.7071 - 0.86;
71
- }
72
- return length(p) - 1.0;
15
+ `,
16
+ name: 'helixCircle',
17
+ uniforms: [],
18
+ };
19
+ const square = {
20
+ glsl: `
21
+ float sdf_helixSquare(vec2 uv, float size) {
22
+ vec2 q = abs(uv) - vec2(0.7 * size);
23
+ return length(max(q, 0.0)) + min(max(q.x, q.y), 0.0) - 0.18 * size;
73
24
  }
25
+ `,
26
+ inradiusFactor: 0.88 / (0.7 * Math.SQRT2 + 0.18),
27
+ name: 'helixSquare',
28
+ uniforms: [],
29
+ };
30
+ const diamond = {
31
+ glsl: `
32
+ float sdf_helixDiamond(vec2 uv, float size) {
33
+ return (abs(uv.x) + abs(uv.y)) * 0.7071 - 0.7071 * size;
34
+ }
35
+ `,
36
+ inradiusFactor: Math.SQRT1_2,
37
+ name: 'helixDiamond',
38
+ uniforms: [],
39
+ };
40
+ const { FLOAT, UNSIGNED_BYTE } = WebGL2RenderingContext;
41
+ const look = {
42
+ attributes: [
43
+ { name: 'helixColor', normalized: true, size: 4, source: 'color', type: UNSIGNED_BYTE },
44
+ { name: 'helixEmphasis', size: 1, source: 'emphasisCode', type: FLOAT },
45
+ { name: 'helixMaterial', size: 1, source: 'materialCode', type: FLOAT },
46
+ ],
47
+ glsl: `
48
+ vec4 layer_helix(vec4 v_helixColor, float v_helixEmphasis, float v_helixMaterial) {
49
+ vec2 p = context.uv;
50
+ float d = context.sdf;
51
+ float aa = context.aaWidth;
74
52
 
75
- void main(void) {
76
- vec2 p = v_diffVector / v_radius;
77
- float d = outside(p, v_shape);
78
-
79
- #ifdef PICKING_MODE
80
- if (d > 0.0) discard;
81
- gl_FragColor = v_color;
82
- #else
83
- float aa = u_correctionRatio * 2.0 / v_radius;
84
- float inside = 1.0 - smoothstep(-aa, 0.0, d);
85
-
86
- /* A ring is a circle drawn outlined, whatever its emphasis says. */
87
- float emphasis = v_shape > 2.5 ? 1.0 : v_emphasis;
88
53
  float edge = smoothstep(-0.32 - aa, -0.32, d);
89
- float fill = emphasis > 1.5 ? mix(0.3, 1.0, edge) : emphasis > 0.5 ? edge : 1.0;
54
+ float fill = v_helixEmphasis > 1.5 ? mix(0.3, 1.0, edge) : v_helixEmphasis > 0.5 ? edge : 1.0;
90
55
 
91
- vec3 color = v_color.rgb;
56
+ vec3 color = v_helixColor.rgb;
92
57
  float alpha = 1.0;
93
58
  /* How large the note is drawn, in pixels: a highlight that suits a hub is
94
59
  a speck of noise on a leaf, so the detail grows in with the size. */
95
- float drawn = smoothstep(4.0, 14.0, v_radius / u_correctionRatio);
60
+ float drawn = smoothstep(4.0, 14.0, 1.0 / context.pixelToUV);
96
61
  /* Lit from above on the left, as every surface here is. */
97
62
  float lit = clamp(0.5 + 0.5 * dot(normalize(vec2(-0.45, 0.9)), p), 0.0, 1.0);
98
63
  /* A fine rim a shade darker, so notes that touch stay two. */
99
64
  float rim = smoothstep(-0.2 - aa, -0.2, d);
100
- if (v_material > 0.5 && v_material < 1.5) {
65
+ if (v_helixMaterial > 0.5 && v_helixMaterial < 1.5) {
101
66
  /* Glass: shaded like a sphere, a highlight, and light at its rim. */
102
67
  color *= mix(0.72, 1.12, lit);
103
68
  float highlight = exp(-pow(length(p - vec2(-0.32, 0.38)) / 0.26, 2.0));
104
69
  color = mix(color, vec3(1.0), highlight * mix(0.25, 0.6, drawn));
105
70
  color = mix(color, vec3(1.0), rim * 0.22 * drawn);
106
71
  alpha = 0.94;
107
- } else if (v_material > 2.5) {
108
- /* Mark: a flat dot of its color, as a marker on a table is. No fall of
109
- light and no highlight; what says it is a mark is the ring and the glow
110
- drawn around it below. */
72
+ } else if (v_helixMaterial > 2.5) {
73
+ /* Mark: a flat dot of its color, as a marker on a table is. What says it
74
+ is a mark is the band and the light its backdrop draws around it. */
111
75
  alpha = 1.0;
112
- } else if (v_material > 1.5) {
76
+ } else if (v_helixMaterial > 1.5) {
113
77
  /* Frosted: a milky body, a soft fall of light, and no sharp highlight. */
114
78
  color = mix(color * mix(0.86, 1.06, lit), vec3(1.0), 0.16);
115
79
  color = mix(color, vec3(1.0), rim * 0.14);
@@ -123,68 +87,14 @@ void main(void) {
123
87
  color = mix(color, color * 0.72, rim * 0.6);
124
88
  }
125
89
 
126
- float body = inside * fill * alpha * v_color.a;
127
- /* The glow, outside the shape: a crisp ring just clear of the edge and a
128
- soft light falling away beyond it, both in the note's own color. */
129
- float reach = ${(REACH - 1).toFixed(2)};
130
- float ring = 1.0 - smoothstep(0.0, 0.07 + aa, abs(d - 0.32));
131
- /* The light falls away more slowly and further than it did, so the
132
- selected note sits in a bloom rather than wearing a ring: the halo the
133
- aura gives the stars its pointer gathers. */
134
- float falloff = exp(-d * 1.7) * (1.0 - smoothstep(reach * 0.8, reach, d));
135
- float halo = v_glow * step(0.0, d) * clamp(ring * 0.8 + falloff * 0.85, 0.0, 1.0) * v_color.a;
136
- /* A mark's own halo, selected or not: a band of its color at a fifth just
137
- outside the dot, and a soft glow beyond, the way a marker on a table
138
- carries a ring of its color and a light around it. */
139
- float band = (1.0 - smoothstep(0.34, 0.42, d)) * 0.22;
140
- float light = exp(-d * 2.0) * (1.0 - smoothstep(reach * 0.8, reach, d)) * 0.3;
141
- float mark = v_material > 2.5 ? step(0.0, d) * (band + light) * v_color.a : 0.0;
142
- halo = clamp(halo + mark, 0.0, 1.0);
143
- /* Premultiplied, as Sigma blends: the body over its glow. */
144
- gl_FragColor = vec4(color * body + v_color.rgb * halo * (1.0 - body), body + halo * (1.0 - body));
145
- #endif
146
- }
147
- `;
148
- const { FLOAT, UNSIGNED_BYTE } = WebGLRenderingContext;
149
- const UNIFORMS = ['u_sizeRatio', 'u_correctionRatio', 'u_matrix'];
150
- export class HelixNodeProgram extends NodeProgram {
151
- getDefinition() {
152
- return {
153
- ATTRIBUTES: [
154
- { name: 'a_position', size: 2, type: FLOAT },
155
- { name: 'a_size', size: 1, type: FLOAT },
156
- { name: 'a_color', normalized: true, size: 4, type: UNSIGNED_BYTE },
157
- { name: 'a_id', normalized: true, size: 4, type: UNSIGNED_BYTE },
158
- { name: 'a_shape', size: 1, type: FLOAT },
159
- { name: 'a_emphasis', size: 1, type: FLOAT },
160
- { name: 'a_material', size: 1, type: FLOAT },
161
- { name: 'a_glow', size: 1, type: FLOAT },
162
- ],
163
- CONSTANT_ATTRIBUTES: [{ name: 'a_angle', size: 1, type: FLOAT }],
164
- CONSTANT_DATA: [[0], [(2 * Math.PI) / 3], [(4 * Math.PI) / 3]],
165
- FRAGMENT_SHADER_SOURCE: FRAGMENT,
166
- METHOD: WebGLRenderingContext.TRIANGLES,
167
- UNIFORMS,
168
- VERTEX_SHADER_SOURCE: VERTEX,
169
- VERTICES: 3,
170
- };
171
- }
172
- processVisibleItem(nodeIndex, startIndex, data) {
173
- const array = this.array;
174
- let at = startIndex;
175
- array[at++] = data.x;
176
- array[at++] = data.y;
177
- array[at++] = data.size;
178
- array[at++] = floatColor(data.color);
179
- array[at++] = nodeIndex;
180
- array[at++] = data.shapeCode ?? SHAPES.circle;
181
- array[at++] = data.emphasisCode ?? EMPHASES.solid;
182
- array[at++] = data.materialCode ?? MATERIALS.flat;
183
- array[at] = data.glow ?? 0;
184
- }
185
- setUniforms(params, { gl, uniformLocations, }) {
186
- gl.uniform1f(uniformLocations.u_correctionRatio, params.correctionRatio);
187
- gl.uniform1f(uniformLocations.u_sizeRatio, params.sizeRatio);
188
- gl.uniformMatrix3fv(uniformLocations.u_matrix, false, params.matrix);
189
- }
90
+ return vec4(color, fill * alpha * v_helixColor.a);
190
91
  }
92
+ `,
93
+ name: 'helix',
94
+ uniforms: [],
95
+ };
96
+ export const nodePrimitives = () => ({
97
+ backdrop: {},
98
+ layers: [look],
99
+ shapes: [circle, square, diamond],
100
+ });
@@ -1,5 +1,6 @@
1
1
  import type { GraphDriver } from '../connect-graph/connect-graph.js';
2
- import type { NodeLook } from '../connect-graph/looks.js';
2
+ import { type GraphLooks, type NodeLook } from '../connect-graph/looks.js';
3
+ import { type Named, type OnScreen } from '../overlay/overlay.js';
3
4
  /**
4
5
  * A graphology graph, as far as the driver calls it. Each member is
5
6
  * graphology's own, as its documentation describes it.
@@ -34,6 +35,8 @@ export interface SigmaCamera {
34
35
  x: number;
35
36
  y: number;
36
37
  };
38
+ off(type: 'updated', listener: () => void): unknown;
39
+ on(type: 'updated', listener: () => void): unknown;
37
40
  setState(state: {
38
41
  ratio?: number;
39
42
  x?: number;
@@ -60,19 +63,23 @@ export interface SigmaRenderer<G extends SigmaGraph = SigmaGraph> {
60
63
  y: number;
61
64
  };
62
65
  getNodeDisplayData(node: string): {
63
- hidden?: boolean;
64
66
  size: number;
67
+ visibility?: string;
65
68
  x: number;
66
69
  y: number;
67
70
  } | undefined;
71
+ /** The primitives the renderer was made with, which say whether `sigmaOptions()` made it. */
72
+ getPrimitives(): {
73
+ nodes?: object | undefined;
74
+ };
68
75
  off(type: 'clickNode' | 'enterNode' | 'leaveNode', listener: (payload: {
69
76
  node: string;
70
77
  }) => void): unknown;
71
- off(type: 'afterRender' | 'beforeRender' | 'clickStage', listener: () => void): unknown;
78
+ off(type: 'afterRender' | 'clickStage', listener: () => void): unknown;
72
79
  on(type: 'clickNode' | 'enterNode' | 'leaveNode', listener: (payload: {
73
80
  node: string;
74
81
  }) => void): unknown;
75
- on(type: 'afterRender' | 'beforeRender' | 'clickStage', listener: () => void): unknown;
82
+ on(type: 'afterRender' | 'clickStage', listener: () => void): unknown;
76
83
  getSetting(name: SigmaSetting): unknown;
77
84
  refresh(options?: {
78
85
  partialGraph?: {
@@ -82,7 +89,7 @@ export interface SigmaRenderer<G extends SigmaGraph = SigmaGraph> {
82
89
  }): unknown;
83
90
  resize(force?: boolean): unknown;
84
91
  scaleSize(size?: number): number;
85
- setSetting(name: SigmaSetting, value: unknown): unknown;
92
+ setSetting(name: SigmaSetting, value: boolean): unknown;
86
93
  viewportToFramedGraph(point: {
87
94
  x: number;
88
95
  y: number;
@@ -92,7 +99,7 @@ export interface SigmaRenderer<G extends SigmaGraph = SigmaGraph> {
92
99
  };
93
100
  }
94
101
  /** The settings the driver sets, and puts back as they were when it stops. */
95
- type SigmaSetting = 'defaultDrawNodeHover' | 'defaultDrawNodeLabel' | 'defaultEdgeType' | 'edgeProgramClasses' | 'edgeReducer' | 'enableCameraPanning' | 'enableCameraZooming' | 'labelColor' | 'labelFont' | 'labelGridCellSize' | 'labelRenderedSizeThreshold' | 'labelSize' | 'minEdgeThickness' | 'nodeProgramClasses' | 'nodeReducer';
102
+ type SigmaSetting = 'enableCameraPanning' | 'enableCameraZooming';
96
103
  /** A layout that runs over a graphology graph until it is stopped, as ForceAtlas2's worker does. */
97
104
  export interface SigmaLayout {
98
105
  kill(): void;
@@ -146,15 +153,28 @@ export interface SigmaDriverOptions<G extends SigmaGraph = SigmaGraph> {
146
153
  /** The longest a layout runs, in milliseconds, settled or not. 10 seconds by default, about where 10,000 notes settle. */
147
154
  layoutLimit?: number | undefined;
148
155
  }
156
+ /**
157
+ * The notes to name this frame, in the order the overlay makes room for them:
158
+ * the ones that must be named first, the selected note, the one under the
159
+ * pointer and the one the keys are on, then the larger notes, as Sigma's grid
160
+ * kept the larger note's name. Named as `labels` says: the selected note's
161
+ * neighbors where there is room, and the rest only as the page asked.
162
+ */
163
+ export declare const namedOf: (graph: SigmaGraph, looks: GraphLooks, labels: 'all' | 'hover' | 'hubs', hovered: string | undefined, onScreen: OnScreen) => Named[];
149
164
  /**
150
165
  * Drives Sigma.js for a Graph frame. Make the renderer with an empty
151
- * graphology graph and pass it here, and `connectGraph` fills it:
166
+ * graphology graph and `sigmaOptions()`, pass it here, and `connectGraph`
167
+ * fills it:
152
168
  *
153
169
  * ```js
154
- * const sigma = new Sigma(new Graph(), container)
170
+ * const sigma = new Sigma(new Graph(), container, sigmaOptions())
155
171
  * connectGraph(frame, sigmaDriver(sigma, { layout }))
156
172
  * ```
157
173
  *
174
+ * **`sigmaOptions()` is what draws the notes and links as Helix draws them.**
175
+ * Sigma 4 takes its programs and reducers only when it is made, so the driver
176
+ * cannot add them to a renderer made without them, and refuses one.
177
+ *
158
178
  * **Neither Sigma nor graphology is a dependency.** The driver types the
159
179
  * renderer by the calls it makes on it, so a page brings whichever version it
160
180
  * has, and a spec holds those shapes to the real classes. The renderer is the