@fusion.dev/helix-graph 0.126.0 → 0.127.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/README.md +70 -38
- package/dist/connect-graph/arrange.d.ts +4 -2
- package/dist/connect-graph/arrange.js +5 -22
- package/dist/connect-graph/connect-graph.d.ts +18 -6
- package/dist/connect-graph/connect-graph.js +76 -32
- package/dist/connect-graph/diff.d.ts +7 -0
- package/dist/connect-graph/looks.d.ts +8 -0
- package/dist/connect-graph/looks.js +6 -3
- package/dist/connect-graph/seed.d.ts +9 -0
- package/dist/connect-graph/seed.js +21 -15
- package/dist/connect-graph/stencil.d.ts +3 -1
- package/dist/connect-graph/stencil.js +32 -10
- package/dist/cosmos/cosmos-driver.d.ts +5 -4
- package/dist/cosmos/cosmos-driver.js +122 -39
- package/dist/index.d.ts +4 -3
- package/dist/index.js +2 -2
- package/dist/model/index.d.ts +1 -0
- package/dist/model/index.js +1 -0
- package/dist/{shapes → model}/model.d.ts +4 -3
- package/dist/{shapes → model}/model.js +10 -5
- package/dist/overlay/overlay.d.ts +14 -8
- package/dist/overlay/overlay.js +64 -62
- package/dist/shapes/flat.d.ts +1 -1
- package/dist/shapes/flat.js +9 -5
- package/dist/shapes/index.d.ts +3 -4
- package/dist/shapes/index.js +3 -4
- package/dist/{connect-graph → shapes}/shape-field.d.ts +7 -3
- package/dist/{connect-graph → shapes}/shape-field.js +4 -2
- package/dist/shapes/solids.d.ts +3 -3
- package/dist/shapes/solids.js +25 -9
- package/dist/{three → shapes}/surface.d.ts +1 -1
- package/dist/shapes/triangles.d.ts +2 -2
- package/dist/shapes/triangles.js +49 -30
- package/dist/sigma/sigma-driver.d.ts +33 -17
- package/dist/sigma/sigma-driver.js +69 -17
- package/dist/three/three-driver.d.ts +12 -3
- package/dist/three/three-driver.js +376 -201
- package/package.json +9 -5
- /package/dist/{three → shapes}/surface.js +0 -0
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
import type { GraphDriver } from '../connect-graph/connect-graph.js';
|
|
2
2
|
import type { NodeLook } from '../connect-graph/looks.js';
|
|
3
|
-
/**
|
|
3
|
+
/**
|
|
4
|
+
* A graphology graph, as far as the driver calls it. Each member is
|
|
5
|
+
* graphology's own, as its documentation describes it.
|
|
6
|
+
*/
|
|
4
7
|
export interface SigmaGraph {
|
|
5
8
|
addEdge(source: string, target: string, attributes?: Record<string, unknown>): unknown;
|
|
6
9
|
addNode(node: string, attributes?: Record<string, unknown>): unknown;
|
|
7
10
|
clear(): void;
|
|
8
11
|
dropEdge(source: string, target: string): void;
|
|
9
12
|
dropNode(node: string): void;
|
|
13
|
+
edges(node: string): string[];
|
|
10
14
|
forEachNode(callback: (node: string, attributes: Record<string, unknown>) => void): void;
|
|
11
15
|
getNodeAttributes(node: string): Record<string, unknown>;
|
|
12
16
|
hasEdge(source: string, target: string): boolean;
|
|
@@ -15,7 +19,7 @@ export interface SigmaGraph {
|
|
|
15
19
|
readonly order: number;
|
|
16
20
|
setNodeAttribute(node: string, name: string, value: unknown): unknown;
|
|
17
21
|
}
|
|
18
|
-
/** Sigma's camera, as far as the driver calls it. */
|
|
22
|
+
/** Sigma's camera, as far as the driver calls it. Each member is Sigma's own. */
|
|
19
23
|
export interface SigmaCamera {
|
|
20
24
|
animate(state: {
|
|
21
25
|
ratio?: number;
|
|
@@ -36,15 +40,18 @@ export interface SigmaCamera {
|
|
|
36
40
|
y?: number;
|
|
37
41
|
}): unknown;
|
|
38
42
|
}
|
|
39
|
-
/**
|
|
40
|
-
|
|
43
|
+
/**
|
|
44
|
+
* A Sigma renderer, as far as the driver calls it, over a graph of type `G`.
|
|
45
|
+
* Each member is Sigma's own, as its documentation describes it.
|
|
46
|
+
*/
|
|
47
|
+
export interface SigmaRenderer<G extends SigmaGraph = SigmaGraph> {
|
|
41
48
|
getCamera(): SigmaCamera;
|
|
42
49
|
getContainer(): HTMLElement;
|
|
43
50
|
getDimensions(): {
|
|
44
51
|
height: number;
|
|
45
52
|
width: number;
|
|
46
53
|
};
|
|
47
|
-
getGraph():
|
|
54
|
+
getGraph(): G;
|
|
48
55
|
framedGraphToViewport(point: {
|
|
49
56
|
x: number;
|
|
50
57
|
y: number;
|
|
@@ -61,16 +68,21 @@ export interface SigmaRenderer {
|
|
|
61
68
|
off(type: 'clickNode' | 'enterNode' | 'leaveNode', listener: (payload: {
|
|
62
69
|
node: string;
|
|
63
70
|
}) => void): unknown;
|
|
64
|
-
off(type: 'afterRender' | 'beforeRender', listener: () => void): unknown;
|
|
71
|
+
off(type: 'afterRender' | 'beforeRender' | 'clickStage', listener: () => void): unknown;
|
|
65
72
|
on(type: 'clickNode' | 'enterNode' | 'leaveNode', listener: (payload: {
|
|
66
73
|
node: string;
|
|
67
74
|
}) => void): unknown;
|
|
68
|
-
on(type: 'afterRender' | 'beforeRender', listener: () => void): unknown;
|
|
69
|
-
getSetting(name:
|
|
70
|
-
refresh(
|
|
75
|
+
on(type: 'afterRender' | 'beforeRender' | 'clickStage', listener: () => void): unknown;
|
|
76
|
+
getSetting(name: SigmaSetting): unknown;
|
|
77
|
+
refresh(options?: {
|
|
78
|
+
partialGraph?: {
|
|
79
|
+
edges?: string[];
|
|
80
|
+
nodes?: string[];
|
|
81
|
+
};
|
|
82
|
+
}): unknown;
|
|
71
83
|
resize(force?: boolean): unknown;
|
|
72
84
|
scaleSize(size?: number): number;
|
|
73
|
-
setSetting(name:
|
|
85
|
+
setSetting(name: SigmaSetting, value: unknown): unknown;
|
|
74
86
|
viewportToFramedGraph(point: {
|
|
75
87
|
x: number;
|
|
76
88
|
y: number;
|
|
@@ -79,14 +91,16 @@ export interface SigmaRenderer {
|
|
|
79
91
|
y: number;
|
|
80
92
|
};
|
|
81
93
|
}
|
|
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';
|
|
82
96
|
/** A layout that runs over a graphology graph until it is stopped, as ForceAtlas2's worker does. */
|
|
83
97
|
export interface SigmaLayout {
|
|
84
98
|
kill(): void;
|
|
85
99
|
start(): void;
|
|
86
100
|
stop(): void;
|
|
87
101
|
}
|
|
88
|
-
/** How the Sigma driver behaves where the page does not say
|
|
89
|
-
export interface SigmaDriverOptions {
|
|
102
|
+
/** How the Sigma driver behaves where the page does not say, over a graph of type `G`. */
|
|
103
|
+
export interface SigmaDriverOptions<G extends SigmaGraph = SigmaGraph> {
|
|
90
104
|
/**
|
|
91
105
|
* Makes a layout for the graph, which the driver runs where notes have no
|
|
92
106
|
* position and stops once they have settled. ForceAtlas2's worker is the
|
|
@@ -103,18 +117,18 @@ export interface SigmaDriverOptions {
|
|
|
103
117
|
*
|
|
104
118
|
* Without one, notes stay where the connector started them.
|
|
105
119
|
*/
|
|
106
|
-
layout?: ((graph:
|
|
120
|
+
layout?: ((graph: G) => SigmaLayout) | undefined;
|
|
107
121
|
/**
|
|
108
122
|
* How heavily a note is drawn where it does not say: `solid`, filled, by
|
|
109
123
|
* default; `outlined`, a ring; `subtle`, a tinted fill with a ring.
|
|
110
124
|
*/
|
|
111
125
|
emphasis?: NodeLook['emphasis'];
|
|
112
126
|
/**
|
|
113
|
-
* What a note is drawn as made of where it does not say: flat by default,
|
|
127
|
+
* What a note is drawn as made of where it does not say: `flat` by default,
|
|
114
128
|
* `glass`, a shaded sphere with a highlight and a lit rim, or `frosted`, a
|
|
115
129
|
* milky body with a soft fall of light.
|
|
116
130
|
*/
|
|
117
|
-
material?: NodeLook['material'];
|
|
131
|
+
material?: NodeLook['material'] | 'flat';
|
|
118
132
|
/** A note's shape where it does not say: `circle` by default, `square`, `diamond` or `ring`. */
|
|
119
133
|
shape?: NodeLook['shape'];
|
|
120
134
|
/**
|
|
@@ -142,6 +156,8 @@ export interface SigmaDriverOptions {
|
|
|
142
156
|
*
|
|
143
157
|
* **Neither Sigma nor graphology is a dependency.** The driver types the
|
|
144
158
|
* renderer by the calls it makes on it, so a page brings whichever version it
|
|
145
|
-
* has, and a spec holds those shapes to the real classes.
|
|
159
|
+
* has, and a spec holds those shapes to the real classes. The renderer is the
|
|
160
|
+
* page's: after `disconnect()`, `sigma.kill()` frees it.
|
|
146
161
|
*/
|
|
147
|
-
export declare const sigmaDriver: (sigma: SigmaRenderer
|
|
162
|
+
export declare const sigmaDriver: <G extends SigmaGraph>(sigma: SigmaRenderer<G>, options?: SigmaDriverOptions<G>) => GraphDriver;
|
|
163
|
+
export {};
|
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
import { ease, gesturesHeld, groupsOver, signalsOver } from '../overlay/overlay.js';
|
|
2
2
|
import { EMPHASES, HelixNodeProgram, MATERIALS, SHAPES } from './node-program.js';
|
|
3
|
+
const SETTINGS = [
|
|
4
|
+
'defaultDrawNodeHover',
|
|
5
|
+
'defaultDrawNodeLabel',
|
|
6
|
+
'edgeReducer',
|
|
7
|
+
'enableCameraPanning',
|
|
8
|
+
'enableCameraZooming',
|
|
9
|
+
'labelColor',
|
|
10
|
+
'labelFont',
|
|
11
|
+
'labelGridCellSize',
|
|
12
|
+
'labelRenderedSizeThreshold',
|
|
13
|
+
'labelSize',
|
|
14
|
+
'minEdgeThickness',
|
|
15
|
+
'nodeProgramClasses',
|
|
16
|
+
'nodeReducer',
|
|
17
|
+
];
|
|
3
18
|
const STEP = 250;
|
|
4
19
|
const ZOOM = 1.5;
|
|
5
20
|
const SAMPLE = 250;
|
|
@@ -65,7 +80,8 @@ export const sigmaDriver = (sigma, options = {}) => {
|
|
|
65
80
|
const graph = sigma.getGraph();
|
|
66
81
|
const container = sigma.getContainer();
|
|
67
82
|
const camera = sigma.getCamera();
|
|
68
|
-
const
|
|
83
|
+
const before = new Map(SETTINGS.map((name) => [name, sigma.getSetting(name)]));
|
|
84
|
+
const programs = before.get('nodeProgramClasses');
|
|
69
85
|
sigma.setSetting('nodeProgramClasses', { ...programs, helix: HelixNodeProgram });
|
|
70
86
|
const sized = typeof ResizeObserver === 'function'
|
|
71
87
|
? new ResizeObserver(() => {
|
|
@@ -75,13 +91,22 @@ export const sigmaDriver = (sigma, options = {}) => {
|
|
|
75
91
|
: undefined;
|
|
76
92
|
sized?.observe(container);
|
|
77
93
|
let hovered;
|
|
94
|
+
const cursor = container.style.cursor;
|
|
95
|
+
const relight = (from, to) => {
|
|
96
|
+
const edges = [from, to].flatMap((id) => id !== undefined && graph.hasNode(id) ? graph.edges(id) : []);
|
|
97
|
+
sigma.refresh({ partialGraph: { edges } });
|
|
98
|
+
};
|
|
78
99
|
const enter = ({ node }) => {
|
|
100
|
+
const was = hovered;
|
|
79
101
|
hovered = node;
|
|
80
|
-
|
|
102
|
+
container.style.cursor = 'pointer';
|
|
103
|
+
relight(was, node);
|
|
81
104
|
};
|
|
82
105
|
const leave = () => {
|
|
106
|
+
const was = hovered;
|
|
83
107
|
hovered = undefined;
|
|
84
|
-
|
|
108
|
+
container.style.cursor = cursor;
|
|
109
|
+
relight(was, undefined);
|
|
85
110
|
};
|
|
86
111
|
sigma.on('enterNode', enter);
|
|
87
112
|
sigma.on('leaveNode', leave);
|
|
@@ -111,22 +136,31 @@ export const sigmaDriver = (sigma, options = {}) => {
|
|
|
111
136
|
return { radius: sigma.scaleSize(data.size), x: at.x, y: at.y };
|
|
112
137
|
};
|
|
113
138
|
const signals = signalsOver(container, onScreen);
|
|
114
|
-
const groups = groupsOver(container, onScreen);
|
|
139
|
+
const groups = groupsOver(container, onScreen, () => taken);
|
|
115
140
|
sigma.on('afterRender', groups.draw);
|
|
116
|
-
|
|
141
|
+
let gliding = 0;
|
|
142
|
+
const glide = (sizes, moves) => {
|
|
143
|
+
cancelAnimationFrame(gliding);
|
|
117
144
|
const started = performance.now();
|
|
118
|
-
const step = (
|
|
119
|
-
const along = Math.min((now - started) / GROW, 1);
|
|
145
|
+
const step = () => {
|
|
146
|
+
const along = Math.min((performance.now() - started) / GROW, 1);
|
|
147
|
+
const eased = ease(along);
|
|
120
148
|
for (const [id, size] of sizes) {
|
|
121
149
|
if (graph.hasNode(id)) {
|
|
122
|
-
graph.setNodeAttribute(id, 'size', size *
|
|
150
|
+
graph.setNodeAttribute(id, 'size', size * eased);
|
|
123
151
|
}
|
|
124
152
|
}
|
|
125
|
-
|
|
126
|
-
|
|
153
|
+
for (const [id, { from, to }] of moves) {
|
|
154
|
+
if (graph.hasNode(id)) {
|
|
155
|
+
graph.mergeNodeAttributes(id, {
|
|
156
|
+
x: from.x + (to.x - from.x) * eased,
|
|
157
|
+
y: from.y + (to.y - from.y) * eased,
|
|
158
|
+
});
|
|
159
|
+
}
|
|
127
160
|
}
|
|
161
|
+
gliding = along < 1 ? requestAnimationFrame(step) : 0;
|
|
128
162
|
};
|
|
129
|
-
requestAnimationFrame(step);
|
|
163
|
+
gliding = requestAnimationFrame(step);
|
|
130
164
|
};
|
|
131
165
|
return {
|
|
132
166
|
center: (id, animate) => {
|
|
@@ -176,7 +210,7 @@ export const sigmaDriver = (sigma, options = {}) => {
|
|
|
176
210
|
return;
|
|
177
211
|
}
|
|
178
212
|
const state = {
|
|
179
|
-
ratio: Math.min(Math.max(Math.max(high.x - low.x, high.y - low.y) * 2.4, 0.
|
|
213
|
+
ratio: Math.min(Math.max(Math.max(high.x - low.x, high.y - low.y) * 2.4, 0.25), 1),
|
|
180
214
|
x: (low.x + high.x) / 2,
|
|
181
215
|
y: (low.y + high.y) / 2,
|
|
182
216
|
};
|
|
@@ -189,6 +223,8 @@ export const sigmaDriver = (sigma, options = {}) => {
|
|
|
189
223
|
},
|
|
190
224
|
gestures: (next) => {
|
|
191
225
|
held.set(next);
|
|
226
|
+
sigma.setSetting('enableCameraZooming', next !== 'off');
|
|
227
|
+
sigma.setSetting('enableCameraPanning', next !== 'off');
|
|
192
228
|
},
|
|
193
229
|
groups: (next) => {
|
|
194
230
|
if (options.groupLabels !== false) {
|
|
@@ -245,9 +281,12 @@ export const sigmaDriver = (sigma, options = {}) => {
|
|
|
245
281
|
},
|
|
246
282
|
onPress: (press) => {
|
|
247
283
|
const listener = ({ node }) => press(node);
|
|
284
|
+
const away = () => press(undefined);
|
|
248
285
|
sigma.on('clickNode', listener);
|
|
286
|
+
sigma.on('clickStage', away);
|
|
249
287
|
return () => {
|
|
250
288
|
sigma.off('clickNode', listener);
|
|
289
|
+
sigma.off('clickStage', away);
|
|
251
290
|
};
|
|
252
291
|
},
|
|
253
292
|
panBy: (x, y, animate) => {
|
|
@@ -270,7 +309,11 @@ export const sigmaDriver = (sigma, options = {}) => {
|
|
|
270
309
|
signals.stop();
|
|
271
310
|
sigma.off('enterNode', enter);
|
|
272
311
|
sigma.off('leaveNode', leave);
|
|
273
|
-
|
|
312
|
+
cancelAnimationFrame(gliding);
|
|
313
|
+
container.style.cursor = cursor;
|
|
314
|
+
for (const [name, value] of before) {
|
|
315
|
+
sigma.setSetting(name, value);
|
|
316
|
+
}
|
|
274
317
|
sized?.disconnect();
|
|
275
318
|
held.stop();
|
|
276
319
|
},
|
|
@@ -298,12 +341,18 @@ export const sigmaDriver = (sigma, options = {}) => {
|
|
|
298
341
|
growing.set(node.id, size);
|
|
299
342
|
}
|
|
300
343
|
}
|
|
344
|
+
const moves = new Map();
|
|
301
345
|
for (const node of changes.changeNodes) {
|
|
346
|
+
const was = graph.getNodeAttributes(node.id);
|
|
347
|
+
const from = { x: Number(was.x), y: Number(was.y) };
|
|
348
|
+
const moved = from.x !== node.x || from.y !== node.y;
|
|
349
|
+
if (animate && moved && Number.isFinite(from.x) && Number.isFinite(from.y)) {
|
|
350
|
+
moves.set(node.id, { from, to: { x: node.x, y: node.y } });
|
|
351
|
+
}
|
|
302
352
|
graph.mergeNodeAttributes(node.id, {
|
|
303
353
|
label: node.label,
|
|
304
354
|
size: pixels(node.size),
|
|
305
|
-
x: node.x,
|
|
306
|
-
y: node.y,
|
|
355
|
+
...(moves.has(node.id) ? {} : { x: node.x, y: node.y }),
|
|
307
356
|
});
|
|
308
357
|
}
|
|
309
358
|
for (const link of changes.addLinks) {
|
|
@@ -318,8 +367,8 @@ export const sigmaDriver = (sigma, options = {}) => {
|
|
|
318
367
|
});
|
|
319
368
|
}
|
|
320
369
|
}
|
|
321
|
-
if (growing.size > 0) {
|
|
322
|
-
|
|
370
|
+
if (growing.size > 0 || moves.size > 0) {
|
|
371
|
+
glide(growing, moves);
|
|
323
372
|
}
|
|
324
373
|
},
|
|
325
374
|
zoomBy: (direction, animate) => {
|
|
@@ -394,6 +443,9 @@ export const sigmaDriver = (sigma, options = {}) => {
|
|
|
394
443
|
clearInterval(timer);
|
|
395
444
|
layout.stop();
|
|
396
445
|
layout.kill();
|
|
446
|
+
graph.forEachNode((node) => {
|
|
447
|
+
graph.setNodeAttribute(node, 'fixed', false);
|
|
448
|
+
});
|
|
397
449
|
};
|
|
398
450
|
},
|
|
399
451
|
}
|
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
import type { GraphDriver } from '../connect-graph/connect-graph.js';
|
|
2
|
-
import type
|
|
2
|
+
import { type NodeLook } from '../connect-graph/looks.js';
|
|
3
3
|
/** How the three.js driver behaves where the page does not say. */
|
|
4
4
|
export interface ThreeDriverOptions {
|
|
5
|
+
/**
|
|
6
|
+
* How many pixels the drawing has for each of the page's. By default half
|
|
7
|
+
* as many again as the screen has, at least two and at most three, which
|
|
8
|
+
* smooths every edge. Fewer is faster on a slow GPU.
|
|
9
|
+
*/
|
|
10
|
+
density?: number | undefined;
|
|
5
11
|
/**
|
|
6
12
|
* Whether the graph turns slowly while nobody is handling it. On by
|
|
7
13
|
* default, and never for a reader who asks for less motion.
|
|
@@ -11,7 +17,7 @@ export interface ThreeDriverOptions {
|
|
|
11
17
|
groupLabels?: boolean | undefined;
|
|
12
18
|
/** The longest a layout runs, in milliseconds, settled or not. 10 seconds by default. */
|
|
13
19
|
layoutLimit?: number | undefined;
|
|
14
|
-
/** A note's shape where it does not say: `circle` by default, `square` or `diamond`. */
|
|
20
|
+
/** A note's shape where it does not say: `circle` by default, `square` or `diamond`. A `ring` is drawn as a circle. */
|
|
15
21
|
shape?: NodeLook['shape'];
|
|
16
22
|
}
|
|
17
23
|
/**
|
|
@@ -27,6 +33,9 @@ export interface ThreeDriverOptions {
|
|
|
27
33
|
* place; with a solid `stencil`, the notes are drawn in its shape and none
|
|
28
34
|
* runs. Dragging turns the graph, the wheel and a pinch bring it nearer, and
|
|
29
35
|
* it turns slowly by itself while nobody is handling it, unless the reader
|
|
30
|
-
* asks for less motion.
|
|
36
|
+
* asks for less motion. It draws only while something moves, and not at all
|
|
37
|
+
* while the box is out of view or off the page.
|
|
38
|
+
*
|
|
39
|
+
* The renderer is the driver's own, and `disconnect()` frees it.
|
|
31
40
|
*/
|
|
32
41
|
export declare const threeDriver: (container: HTMLElement, options?: ThreeDriverOptions) => GraphDriver;
|