@fusion.dev/helix-graph 0.201.1 → 0.203.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.
@@ -66,7 +66,7 @@ export interface NodeLook {
66
66
  /** How strongly it glows, from 0 to 1: the selected note's halo. */
67
67
  glow: number;
68
68
  /** What it is drawn as made of, where the note says. */
69
- material?: 'frosted' | 'glass' | undefined;
69
+ material?: 'frosted' | 'glass' | 'mark' | undefined;
70
70
  /**
71
71
  * The note the keys are on in the frame's list or search field, which a
72
72
  * renderer marks as it marks the note under the pointer, so a reader moving
@@ -90,7 +90,7 @@ export const ease = (at) => (at < 0.5 ? 2 * at * at : 1 - (-2 * at + 2) ** 2 / 2
90
90
  const SAMPLED = 96;
91
91
  const nameGroups = (context, groups, looks, at, avoid) => {
92
92
  context.font = `650 11px ${looks.font}`;
93
- context.letterSpacing = '0.14em';
93
+ context.letterSpacing = '0.02em';
94
94
  context.textAlign = 'center';
95
95
  context.textBaseline = 'middle';
96
96
  context.shadowColor = looks.ground;
@@ -121,7 +121,7 @@ const nameGroups = (context, groups, looks, at, avoid) => {
121
121
  if (spread < 12) {
122
122
  continue;
123
123
  }
124
- const name = group.name.toLocaleUpperCase();
124
+ const name = group.name;
125
125
  const half = context.measureText(name).width / 2 + 4;
126
126
  const boxAt = (middle) => ({
127
127
  bottom: middle + 9,
@@ -0,0 +1,87 @@
1
+ import { EdgeProgram, type EdgeProgramType } from 'sigma/rendering';
2
+ import type { EdgeDisplayData, NodeDisplayData, RenderParams } from 'sigma/types';
3
+ /** What the edge program reads off a link, beyond what Sigma gives every link. */
4
+ export interface HelixEdgeData {
5
+ /** How strongly the link glows, from 0 to 1: a link on the selected note's path. */
6
+ glow?: number;
7
+ /** Whether the link is drawn heavy, with an arrowhead, as a link the selection lights is. */
8
+ strong?: boolean;
9
+ }
10
+ declare const UNIFORMS: readonly ['u_matrix', 'u_zoomRatio', 'u_sizeRatio', 'u_correctionRatio', 'u_pixelRatio', 'u_feather', 'u_minEdgeThickness'];
11
+ /**
12
+ * Draws a link as a line that reads in its direction, faint where it leaves
13
+ * a note and full where it arrives, with a glow where the selection lights
14
+ * it: one quad a link, as Sigma's own rectangle is, widened where it glows.
15
+ */
16
+ export declare class HelixEdgeLineProgram extends EdgeProgram<(typeof UNIFORMS)[number]> {
17
+ getDefinition(): {
18
+ ATTRIBUTES: ({
19
+ name: string;
20
+ size: number;
21
+ type: 5126;
22
+ normalized?: never;
23
+ } | {
24
+ name: string;
25
+ normalized: boolean;
26
+ size: number;
27
+ type: 5121;
28
+ })[];
29
+ CONSTANT_ATTRIBUTES: {
30
+ name: string;
31
+ size: number;
32
+ type: 5126;
33
+ }[];
34
+ CONSTANT_DATA: number[][];
35
+ FRAGMENT_SHADER_SOURCE: string;
36
+ METHOD: 4;
37
+ UNIFORMS: readonly ["u_matrix", "u_zoomRatio", "u_sizeRatio", "u_correctionRatio", "u_pixelRatio", "u_feather", "u_minEdgeThickness"];
38
+ VERTEX_SHADER_SOURCE: string;
39
+ VERTICES: number;
40
+ };
41
+ processVisibleItem(edgeIndex: number, startIndex: number, sourceData: NodeDisplayData, targetData: NodeDisplayData, data: EdgeDisplayData & HelixEdgeData): void;
42
+ setUniforms(params: RenderParams, { gl, uniformLocations, }: {
43
+ gl: WebGLRenderingContext;
44
+ uniformLocations: Record<string, WebGLUniformLocation>;
45
+ }): void;
46
+ }
47
+ declare const HEAD_UNIFORMS: readonly ['u_matrix', 'u_sizeRatio', 'u_correctionRatio', 'u_minEdgeThickness', 'u_lengthToThicknessRatio', 'u_widenessToThicknessRatio'];
48
+ /**
49
+ * An arrowhead on a strong link only, at the note it arrives at: Sigma's own
50
+ * head, one triangle a link, drawn transparent where the link is not strong.
51
+ * Every link already reads in its direction by its gradient, and a head on
52
+ * each of a graph's thousands of links is a field of chevrons.
53
+ */
54
+ export declare class HelixArrowHeadProgram extends EdgeProgram<(typeof HEAD_UNIFORMS)[number]> {
55
+ getDefinition(): {
56
+ ATTRIBUTES: ({
57
+ normalized?: never;
58
+ name: string;
59
+ size: number;
60
+ type: 5126;
61
+ } | {
62
+ name: string;
63
+ normalized: boolean;
64
+ size: number;
65
+ type: 5121;
66
+ })[];
67
+ CONSTANT_ATTRIBUTES: {
68
+ name: string;
69
+ size: number;
70
+ type: 5126;
71
+ }[];
72
+ CONSTANT_DATA: number[][];
73
+ FRAGMENT_SHADER_SOURCE: string;
74
+ METHOD: 4;
75
+ UNIFORMS: readonly ["u_matrix", "u_sizeRatio", "u_correctionRatio", "u_minEdgeThickness", "u_lengthToThicknessRatio", "u_widenessToThicknessRatio"];
76
+ VERTEX_SHADER_SOURCE: string;
77
+ VERTICES: number;
78
+ };
79
+ processVisibleItem(edgeIndex: number, startIndex: number, sourceData: NodeDisplayData, targetData: NodeDisplayData, data: EdgeDisplayData & HelixEdgeData): void;
80
+ setUniforms(params: RenderParams, { gl, uniformLocations, }: {
81
+ gl: WebGLRenderingContext;
82
+ uniformLocations: Record<string, WebGLUniformLocation>;
83
+ }): void;
84
+ }
85
+ /** The line and its head, as one program a link is drawn with. */
86
+ export declare const HelixEdgeProgram: EdgeProgramType;
87
+ export {};
@@ -0,0 +1,316 @@
1
+ import { createEdgeCompoundProgram, EdgeProgram } from 'sigma/rendering';
2
+ import { floatColor } from 'sigma/utils';
3
+ const REACH = 4;
4
+ const NONE = 'rgba(0, 0, 0, 0)';
5
+ const VERTEX = `
6
+ attribute vec4 a_id;
7
+ attribute vec4 a_color;
8
+ attribute vec2 a_normal;
9
+ attribute float a_normalCoef;
10
+ attribute vec2 a_positionStart;
11
+ attribute vec2 a_positionEnd;
12
+ attribute float a_positionCoef;
13
+ attribute float a_glow;
14
+
15
+ uniform mat3 u_matrix;
16
+ uniform float u_sizeRatio;
17
+ uniform float u_zoomRatio;
18
+ uniform float u_pixelRatio;
19
+ uniform float u_correctionRatio;
20
+ uniform float u_minEdgeThickness;
21
+ uniform float u_feather;
22
+
23
+ varying vec4 v_color;
24
+ varying vec2 v_normal;
25
+ varying float v_thickness;
26
+ varying float v_feather;
27
+ varying float v_along;
28
+ varying float v_glow;
29
+ varying float v_spread;
30
+
31
+ const float bias = 255.0 / 254.0;
32
+
33
+ void main() {
34
+ float minThickness = u_minEdgeThickness;
35
+
36
+ vec2 normal = a_normal * a_normalCoef;
37
+ vec2 position = a_positionStart * (1.0 - a_positionCoef) + a_positionEnd * a_positionCoef;
38
+
39
+ float normalLength = length(normal);
40
+ vec2 unitNormal = normal / normalLength;
41
+
42
+ /* At least the minimum thickness on screen, in the WebGL referential, as
43
+ Sigma's own rectangle does it. */
44
+ float pixelsThickness = max(normalLength, minThickness * u_sizeRatio);
45
+ float webGLThickness = pixelsThickness * u_correctionRatio / u_sizeRatio;
46
+
47
+ /* The quad reaches past the line by the room its glow needs, and only
48
+ where it glows: a resting link's quad is the line's own width. */
49
+ float spread = 1.0 + a_glow * ${REACH.toFixed(1)};
50
+ gl_Position = vec4((u_matrix * vec3(position + unitNormal * webGLThickness * spread, 1)).xy, 0, 1);
51
+
52
+ v_thickness = webGLThickness / u_zoomRatio;
53
+ v_normal = unitNormal;
54
+ v_feather = u_feather * u_correctionRatio / u_zoomRatio / u_pixelRatio * 2.0;
55
+ v_along = a_positionCoef;
56
+ v_glow = a_glow;
57
+ v_spread = spread;
58
+
59
+ #ifdef PICKING_MODE
60
+ v_color = a_id;
61
+ #else
62
+ v_color = a_color;
63
+ #endif
64
+
65
+ v_color.a *= bias;
66
+ }
67
+ `;
68
+ const FRAGMENT = `
69
+ precision mediump float;
70
+
71
+ varying vec4 v_color;
72
+ varying vec2 v_normal;
73
+ varying float v_thickness;
74
+ varying float v_feather;
75
+ varying float v_along;
76
+ varying float v_glow;
77
+ varying float v_spread;
78
+
79
+ void main(void) {
80
+ #ifdef PICKING_MODE
81
+ gl_FragColor = v_color;
82
+ #else
83
+ /* Across the quad, from the line's middle out: the normal interpolates
84
+ through zero at the middle, so its length is the distance in half-widths
85
+ of the quad, and the quad is the line widened by its spread. */
86
+ float dist = length(v_normal) * v_spread * v_thickness;
87
+ float line = 1.0 - smoothstep(v_thickness - v_feather, v_thickness, dist);
88
+
89
+ /* The glow, outside the line: a soft light falling away from its edge, in
90
+ the link's own color, as a note's halo falls away from its rim. */
91
+ float past = max(dist - v_thickness, 0.0) / max(v_thickness * ${REACH.toFixed(1)}, 0.0001);
92
+ float halo = v_glow * (1.0 - line) * exp(-past * 3.2) * (1.0 - smoothstep(0.7, 1.0, past)) * 0.55;
93
+
94
+ /* Faint where the link leaves and full where it arrives, so the direction
95
+ reads along every link, and the one lit by the selection as a path. */
96
+ float along = mix(0.4, 1.0, smoothstep(0.0, 1.0, v_along));
97
+
98
+ float alpha = (line + halo) * along * v_color.a;
99
+ /* Premultiplied, as Sigma blends. */
100
+ gl_FragColor = vec4(v_color.rgb * alpha, alpha);
101
+ #endif
102
+ }
103
+ `;
104
+ const { FLOAT, UNSIGNED_BYTE } = WebGLRenderingContext;
105
+ const UNIFORMS = [
106
+ 'u_matrix',
107
+ 'u_zoomRatio',
108
+ 'u_sizeRatio',
109
+ 'u_correctionRatio',
110
+ 'u_pixelRatio',
111
+ 'u_feather',
112
+ 'u_minEdgeThickness',
113
+ ];
114
+ export class HelixEdgeLineProgram extends EdgeProgram {
115
+ getDefinition() {
116
+ return {
117
+ ATTRIBUTES: [
118
+ { name: 'a_positionStart', size: 2, type: FLOAT },
119
+ { name: 'a_positionEnd', size: 2, type: FLOAT },
120
+ { name: 'a_normal', size: 2, type: FLOAT },
121
+ { name: 'a_color', normalized: true, size: 4, type: UNSIGNED_BYTE },
122
+ { name: 'a_id', normalized: true, size: 4, type: UNSIGNED_BYTE },
123
+ { name: 'a_glow', size: 1, type: FLOAT },
124
+ ],
125
+ CONSTANT_ATTRIBUTES: [
126
+ { name: 'a_positionCoef', size: 1, type: FLOAT },
127
+ { name: 'a_normalCoef', size: 1, type: FLOAT },
128
+ ],
129
+ CONSTANT_DATA: [
130
+ [0, 1],
131
+ [0, -1],
132
+ [1, 1],
133
+ [1, 1],
134
+ [0, -1],
135
+ [1, -1],
136
+ ],
137
+ FRAGMENT_SHADER_SOURCE: FRAGMENT,
138
+ METHOD: WebGLRenderingContext.TRIANGLES,
139
+ UNIFORMS,
140
+ VERTEX_SHADER_SOURCE: VERTEX,
141
+ VERTICES: 6,
142
+ };
143
+ }
144
+ processVisibleItem(edgeIndex, startIndex, sourceData, targetData, data) {
145
+ const thickness = data.size || 1;
146
+ const dx = targetData.x - sourceData.x;
147
+ const dy = targetData.y - sourceData.y;
148
+ let len = dx * dx + dy * dy;
149
+ let n1 = 0;
150
+ let n2 = 0;
151
+ if (len) {
152
+ len = 1 / Math.sqrt(len);
153
+ n1 = -dy * len * thickness;
154
+ n2 = dx * len * thickness;
155
+ }
156
+ const array = this.array;
157
+ let at = startIndex;
158
+ array[at++] = sourceData.x;
159
+ array[at++] = sourceData.y;
160
+ array[at++] = targetData.x;
161
+ array[at++] = targetData.y;
162
+ array[at++] = n1;
163
+ array[at++] = n2;
164
+ array[at++] = floatColor(data.color);
165
+ array[at++] = edgeIndex;
166
+ array[at] = data.glow ?? 0;
167
+ }
168
+ setUniforms(params, { gl, uniformLocations, }) {
169
+ gl.uniformMatrix3fv(uniformLocations.u_matrix, false, params.matrix);
170
+ gl.uniform1f(uniformLocations.u_zoomRatio, params.zoomRatio);
171
+ gl.uniform1f(uniformLocations.u_sizeRatio, params.sizeRatio);
172
+ gl.uniform1f(uniformLocations.u_correctionRatio, params.correctionRatio);
173
+ gl.uniform1f(uniformLocations.u_pixelRatio, params.pixelRatio);
174
+ gl.uniform1f(uniformLocations.u_feather, params.antiAliasingFeather);
175
+ gl.uniform1f(uniformLocations.u_minEdgeThickness, params.minEdgeThickness);
176
+ }
177
+ }
178
+ const HEAD_VERTEX = `
179
+ attribute vec2 a_position;
180
+ attribute vec2 a_normal;
181
+ attribute float a_radius;
182
+ attribute vec3 a_barycentric;
183
+ attribute vec4 a_id;
184
+ attribute vec4 a_color;
185
+
186
+ uniform mat3 u_matrix;
187
+ uniform float u_sizeRatio;
188
+ uniform float u_correctionRatio;
189
+ uniform float u_minEdgeThickness;
190
+ uniform float u_lengthToThicknessRatio;
191
+ uniform float u_widenessToThicknessRatio;
192
+
193
+ varying vec4 v_color;
194
+
195
+ const float bias = 255.0 / 254.0;
196
+
197
+ void main() {
198
+ float minThickness = u_minEdgeThickness;
199
+
200
+ float normalLength = length(a_normal);
201
+ vec2 unitNormal = a_normal / normalLength;
202
+
203
+ /* As Sigma's own arrowhead: the thickness on screen, the note's radius,
204
+ and the head's length and width from the thickness. */
205
+ float pixelsThickness = max(normalLength / u_sizeRatio, minThickness);
206
+ float webGLThickness = pixelsThickness * u_correctionRatio;
207
+ float webGLNodeRadius = a_radius * 2.0 * u_correctionRatio / u_sizeRatio;
208
+ float webGLArrowHeadLength = webGLThickness * u_lengthToThicknessRatio * 2.0;
209
+ float webGLArrowHeadThickness = webGLThickness * u_widenessToThicknessRatio;
210
+
211
+ float da = a_barycentric.x;
212
+ float db = a_barycentric.y;
213
+ float dc = a_barycentric.z;
214
+
215
+ vec2 delta = vec2(
216
+ da * (webGLNodeRadius * unitNormal.y)
217
+ + db * ((webGLNodeRadius + webGLArrowHeadLength) * unitNormal.y + webGLArrowHeadThickness * unitNormal.x)
218
+ + dc * ((webGLNodeRadius + webGLArrowHeadLength) * unitNormal.y - webGLArrowHeadThickness * unitNormal.x),
219
+
220
+ da * (-webGLNodeRadius * unitNormal.x)
221
+ + db * (-(webGLNodeRadius + webGLArrowHeadLength) * unitNormal.x + webGLArrowHeadThickness * unitNormal.y)
222
+ + dc * (-(webGLNodeRadius + webGLArrowHeadLength) * unitNormal.x - webGLArrowHeadThickness * unitNormal.y)
223
+ );
224
+
225
+ gl_Position = vec4((u_matrix * vec3(a_position + delta, 1)).xy, 0, 1);
226
+
227
+ #ifdef PICKING_MODE
228
+ v_color = a_id;
229
+ #else
230
+ v_color = a_color;
231
+ #endif
232
+
233
+ v_color.a *= bias;
234
+ }
235
+ `;
236
+ const HEAD_FRAGMENT = `
237
+ precision mediump float;
238
+
239
+ varying vec4 v_color;
240
+
241
+ void main(void) {
242
+ #ifdef PICKING_MODE
243
+ gl_FragColor = v_color;
244
+ #else
245
+ /* Premultiplied, as Sigma blends. */
246
+ gl_FragColor = vec4(v_color.rgb * v_color.a, v_color.a);
247
+ #endif
248
+ }
249
+ `;
250
+ const HEAD_UNIFORMS = [
251
+ 'u_matrix',
252
+ 'u_sizeRatio',
253
+ 'u_correctionRatio',
254
+ 'u_minEdgeThickness',
255
+ 'u_lengthToThicknessRatio',
256
+ 'u_widenessToThicknessRatio',
257
+ ];
258
+ const HEAD = { length: 2.5, wideness: 2 };
259
+ export class HelixArrowHeadProgram extends EdgeProgram {
260
+ getDefinition() {
261
+ return {
262
+ ATTRIBUTES: [
263
+ { name: 'a_position', size: 2, type: FLOAT },
264
+ { name: 'a_normal', size: 2, type: FLOAT },
265
+ { name: 'a_radius', size: 1, type: FLOAT },
266
+ { name: 'a_color', normalized: true, size: 4, type: UNSIGNED_BYTE },
267
+ { name: 'a_id', normalized: true, size: 4, type: UNSIGNED_BYTE },
268
+ ],
269
+ CONSTANT_ATTRIBUTES: [{ name: 'a_barycentric', size: 3, type: FLOAT }],
270
+ CONSTANT_DATA: [
271
+ [1, 0, 0],
272
+ [0, 1, 0],
273
+ [0, 0, 1],
274
+ ],
275
+ FRAGMENT_SHADER_SOURCE: HEAD_FRAGMENT,
276
+ METHOD: WebGLRenderingContext.TRIANGLES,
277
+ UNIFORMS: HEAD_UNIFORMS,
278
+ VERTEX_SHADER_SOURCE: HEAD_VERTEX,
279
+ VERTICES: 3,
280
+ };
281
+ }
282
+ processVisibleItem(edgeIndex, startIndex, sourceData, targetData, data) {
283
+ const thickness = data.size || 1;
284
+ const dx = targetData.x - sourceData.x;
285
+ const dy = targetData.y - sourceData.y;
286
+ let len = dx * dx + dy * dy;
287
+ let n1 = 0;
288
+ let n2 = 0;
289
+ if (len) {
290
+ len = 1 / Math.sqrt(len);
291
+ n1 = -dy * len * thickness;
292
+ n2 = dx * len * thickness;
293
+ }
294
+ const array = this.array;
295
+ let at = startIndex;
296
+ array[at++] = targetData.x;
297
+ array[at++] = targetData.y;
298
+ array[at++] = -n1;
299
+ array[at++] = -n2;
300
+ array[at++] = targetData.size || 1;
301
+ array[at++] = floatColor(data.strong ? data.color : NONE);
302
+ array[at] = edgeIndex;
303
+ }
304
+ setUniforms(params, { gl, uniformLocations, }) {
305
+ gl.uniformMatrix3fv(uniformLocations.u_matrix, false, params.matrix);
306
+ gl.uniform1f(uniformLocations.u_sizeRatio, params.sizeRatio);
307
+ gl.uniform1f(uniformLocations.u_correctionRatio, params.correctionRatio);
308
+ gl.uniform1f(uniformLocations.u_minEdgeThickness, params.minEdgeThickness);
309
+ gl.uniform1f(uniformLocations.u_lengthToThicknessRatio, HEAD.length);
310
+ gl.uniform1f(uniformLocations.u_widenessToThicknessRatio, HEAD.wideness);
311
+ }
312
+ }
313
+ export const HelixEdgeProgram = createEdgeCompoundProgram([
314
+ HelixEdgeLineProgram,
315
+ HelixArrowHeadProgram,
316
+ ]);
@@ -1 +1,2 @@
1
+ export { type HelixEdgeData, HelixEdgeProgram } from './edge-program.js';
1
2
  export { type SigmaCamera, type SigmaDriverOptions, type SigmaGraph, type SigmaLayout, type SigmaRenderer, sigmaDriver, } from './sigma-driver.js';
@@ -1 +1,2 @@
1
+ export { HelixEdgeProgram } from './edge-program.js';
1
2
  export { sigmaDriver, } from './sigma-driver.js';
@@ -18,6 +18,7 @@ export declare const MATERIALS: {
18
18
  readonly flat: 0;
19
19
  readonly frosted: 2;
20
20
  readonly glass: 1;
21
+ readonly mark: 3;
21
22
  };
22
23
  /** What the node program reads off a note, beyond what Sigma gives every note. */
23
24
  export interface HelixNodeData {
@@ -2,8 +2,8 @@ import { NodeProgram } from 'sigma/rendering';
2
2
  import { floatColor } from 'sigma/utils';
3
3
  export const SHAPES = { circle: 0, diamond: 2, ring: 3, square: 1 };
4
4
  export const EMPHASES = { outlined: 1, solid: 0, subtle: 2 };
5
- export const MATERIALS = { flat: 0, frosted: 2, glass: 1 };
6
- const REACH = 2.6;
5
+ export const MATERIALS = { flat: 0, frosted: 2, glass: 1, mark: 3 };
6
+ const REACH = 3.4;
7
7
  const VERTEX = `
8
8
  attribute vec4 a_id;
9
9
  attribute vec4 a_color;
@@ -104,6 +104,11 @@ void main(void) {
104
104
  color = mix(color, vec3(1.0), highlight * mix(0.25, 0.6, drawn));
105
105
  color = mix(color, vec3(1.0), rim * 0.22 * drawn);
106
106
  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. */
111
+ alpha = 1.0;
107
112
  } else if (v_material > 1.5) {
108
113
  /* Frosted: a milky body, a soft fall of light, and no sharp highlight. */
109
114
  color = mix(color * mix(0.86, 1.06, lit), vec3(1.0), 0.16);
@@ -123,8 +128,18 @@ void main(void) {
123
128
  soft light falling away beyond it, both in the note's own color. */
124
129
  float reach = ${(REACH - 1).toFixed(2)};
125
130
  float ring = 1.0 - smoothstep(0.0, 0.07 + aa, abs(d - 0.32));
126
- float falloff = exp(-d * 2.4) * (1.0 - smoothstep(reach * 0.8, reach, d));
127
- float halo = v_glow * step(0.0, d) * clamp(ring * 0.95 + falloff * 0.55, 0.0, 1.0) * v_color.a;
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);
128
143
  /* Premultiplied, as Sigma blends: the body over its glow. */
129
144
  gl_FragColor = vec4(color * body + v_color.rgb * halo * (1.0 - body), body + halo * (1.0 - body));
130
145
  #endif
@@ -92,7 +92,7 @@ export interface SigmaRenderer<G extends SigmaGraph = SigmaGraph> {
92
92
  };
93
93
  }
94
94
  /** The settings the driver sets, and puts back as they were when it stops. */
95
- type SigmaSetting = 'defaultDrawNodeHover' | 'defaultDrawNodeLabel' | 'edgeReducer' | 'enableCameraPanning' | 'enableCameraZooming' | 'labelColor' | 'labelFont' | 'labelGridCellSize' | 'labelRenderedSizeThreshold' | 'labelSize' | 'minEdgeThickness' | 'nodeProgramClasses' | 'nodeReducer';
95
+ type SigmaSetting = 'defaultDrawNodeHover' | 'defaultDrawNodeLabel' | 'defaultEdgeType' | 'edgeProgramClasses' | 'edgeReducer' | 'enableCameraPanning' | 'enableCameraZooming' | 'labelColor' | 'labelFont' | 'labelGridCellSize' | 'labelRenderedSizeThreshold' | 'labelSize' | 'minEdgeThickness' | 'nodeProgramClasses' | 'nodeReducer';
96
96
  /** A layout that runs over a graphology graph until it is stopped, as ForceAtlas2's worker does. */
97
97
  export interface SigmaLayout {
98
98
  kill(): void;
@@ -125,8 +125,9 @@ export interface SigmaDriverOptions<G extends SigmaGraph = SigmaGraph> {
125
125
  emphasis?: NodeLook['emphasis'];
126
126
  /**
127
127
  * What a note is drawn as made of where it does not say: `flat` by default,
128
- * `glass`, a shaded sphere with a highlight and a lit rim, or `frosted`, a
129
- * milky body with a soft fall of light.
128
+ * `glass`, a shaded sphere with a highlight and a lit rim; `frosted`, a
129
+ * milky body with a soft fall of light; or `mark`, a flat dot of its color
130
+ * with a faint ring and a soft glow around it, as a marker on a table is.
130
131
  */
131
132
  material?: NodeLook['material'] | 'flat';
132
133
  /** A note's shape where it does not say: `circle` by default, `square`, `diamond` or `ring`. */
@@ -1,10 +1,13 @@
1
1
  import { isRtl } from '@fusion.dev/helix-core';
2
2
  import { pixelsOf } from '../connect-graph/sizes.js';
3
3
  import { chromeOver, clearOf, ease, gesturesHeld, groupsOver, nameStart, signalsOver, } from '../overlay/overlay.js';
4
+ import { HelixEdgeProgram } from './edge-program.js';
4
5
  import { EMPHASES, HelixNodeProgram, MATERIALS, SHAPES } from './node-program.js';
5
6
  const SETTINGS = [
6
7
  'defaultDrawNodeHover',
7
8
  'defaultDrawNodeLabel',
9
+ 'defaultEdgeType',
10
+ 'edgeProgramClasses',
8
11
  'edgeReducer',
9
12
  'enableCameraPanning',
10
13
  'enableCameraZooming',
@@ -88,6 +91,9 @@ export const sigmaDriver = (sigma, options = {}) => {
88
91
  const before = new Map(SETTINGS.map((name) => [name, sigma.getSetting(name)]));
89
92
  const programs = before.get('nodeProgramClasses');
90
93
  sigma.setSetting('nodeProgramClasses', { ...programs, helix: HelixNodeProgram });
94
+ const edgePrograms = before.get('edgeProgramClasses');
95
+ sigma.setSetting('edgeProgramClasses', { ...edgePrograms, helix: HelixEdgeProgram });
96
+ sigma.setSetting('defaultEdgeType', 'helix');
91
97
  const sized = typeof ResizeObserver === 'function'
92
98
  ? new ResizeObserver(() => {
93
99
  sigma.resize(true);
@@ -276,12 +282,16 @@ export const sigmaDriver = (sigma, options = {}) => {
276
282
  const look = looks.link(String(data.from), String(data.to));
277
283
  const near = hovered ?? looks.keyed;
278
284
  const lit = near !== undefined && (data.from === near || data.to === near);
285
+ const strong = look.raised || lit;
279
286
  return {
280
287
  ...data,
281
288
  color: lit ? looks.node(near).color : look.color,
289
+ glow: strong ? 1 : 0,
282
290
  hidden: look.hidden,
283
- size: look.raised || lit ? 1.6 : 0.8,
284
- zIndex: look.raised || lit ? 1 : 0,
291
+ size: strong ? 1.3 : 0.8,
292
+ strong,
293
+ type: 'helix',
294
+ zIndex: strong ? 1 : 0,
285
295
  };
286
296
  });
287
297
  sigma.refresh();
@@ -174,7 +174,7 @@ export const graphSnapshot = (nodes, links, options = {}) => {
174
174
  if (points.length < 2 || spread < 12) {
175
175
  continue;
176
176
  }
177
- const name = group.toLocaleUpperCase();
177
+ const name = group;
178
178
  const half = (widthOf(name, 11) * 1.14) / 2 + 4;
179
179
  const boxAt = (y) => ({
180
180
  bottom: y + 9,
@@ -192,7 +192,7 @@ export const graphSnapshot = (nodes, links, options = {}) => {
192
192
  const tinted = members.find((node) => node.tone === undefined) ?? members[0];
193
193
  const color = mix(tinted ? own(tinted) : token('foreground-muted'), token('foreground'), 60);
194
194
  const fade = selected !== undefined && ownGroup !== group ? 0.3 : 0.92;
195
- regions.push(`<text dominant-baseline="middle" fill-opacity="${fade}" font-size="11" font-weight="650" letter-spacing="0.14em" style="fill:${color};stroke:${ground}" text-anchor="middle" x="${short(middle.x)}" y="${short(y)}">${escaped(name)}</text>`);
195
+ regions.push(`<text dominant-baseline="middle" fill-opacity="${fade}" font-size="11" font-weight="650" letter-spacing="0.02em" style="fill:${color};stroke:${ground}" text-anchor="middle" x="${short(middle.x)}" y="${short(y)}">${escaped(name)}</text>`);
196
196
  }
197
197
  }
198
198
  return (`<div aria-hidden="true" data-helix-graph-snapshot style="inset:0;overflow:hidden;pointer-events:none;position:absolute">` +
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@fusion.dev/helix-graph",
3
3
  "description": "Connects the Helix Graph frame to a graph renderer: Sigma.js, cosmos.gl, three.js, or a driver of your own.",
4
- "version": "0.201.1",
4
+ "version": "0.203.0",
5
5
  "license": "MIT",
6
6
  "author": "Ross Sheppard <ross.sheppard@fusion.dev>",
7
7
  "repository": {
@@ -59,12 +59,12 @@
59
59
  },
60
60
  "bugs": "https://github.com/project-fusion/fusion/issues",
61
61
  "dependencies": {
62
- "@fusion.dev/helix-core": "0.201.1"
62
+ "@fusion.dev/helix-core": "0.203.0"
63
63
  },
64
64
  "devDependencies": {
65
65
  "@cosmos.gl/graph": "^3.4.2",
66
- "@fusion.dev/helix-headless": "0.201.1",
67
- "@fusion.dev/helix-testing": "0.201.1",
66
+ "@fusion.dev/helix-headless": "0.203.0",
67
+ "@fusion.dev/helix-testing": "0.203.0",
68
68
  "@types/bun": "^1.4.2",
69
69
  "@types/three": "^0.186.0",
70
70
  "d3-force-3d": "^3.0.6",