@fusion.dev/helix-graph 0.185.0 → 0.186.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/README.md +61 -7
- package/dist/isometric/geometry.d.ts +136 -0
- package/dist/isometric/geometry.js +280 -0
- package/dist/isometric/index.d.ts +2 -0
- package/dist/isometric/index.js +2 -0
- package/dist/isometric/isometric-driver.d.ts +75 -0
- package/dist/isometric/isometric-driver.js +591 -0
- package/dist/three/budget.d.ts +49 -0
- package/dist/three/budget.js +43 -0
- package/dist/three/three-driver.d.ts +16 -9
- package/dist/three/three-driver.js +46 -14
- package/package.json +8 -4
|
@@ -3,14 +3,18 @@ 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
5
|
/**
|
|
6
|
-
* How many pixels the drawing has for each of the page's. By default
|
|
7
|
-
* as many again as the screen has, at
|
|
8
|
-
* smooths every edge
|
|
6
|
+
* How many pixels the drawing has for each of the page's. By default, on
|
|
7
|
+
* a screen worked with a mouse, half as many again as the screen has, at
|
|
8
|
+
* least two and at most three, which smooths every edge; on a touch
|
|
9
|
+
* screen, as many as the screen has, at most two. Fewer is faster on a
|
|
10
|
+
* slow GPU.
|
|
9
11
|
*/
|
|
10
12
|
density?: number | undefined;
|
|
11
13
|
/**
|
|
12
|
-
* Whether the graph turns slowly
|
|
13
|
-
* default, and never for a reader who asks for
|
|
14
|
+
* Whether the graph turns slowly for its first five seconds, until the
|
|
15
|
+
* reader handles it. On by default, and never for a reader who asks for
|
|
16
|
+
* less motion. It stops for good after five seconds, since motion that
|
|
17
|
+
* starts by itself and runs longer needs a way to pause it.
|
|
14
18
|
*/
|
|
15
19
|
autoRotate?: boolean | undefined;
|
|
16
20
|
/** Whether each group's name is drawn over its notes. On by default. */
|
|
@@ -32,10 +36,13 @@ export interface ThreeDriverOptions {
|
|
|
32
36
|
* **A layout runs in three dimensions**, with d3-force-3d, where notes have no
|
|
33
37
|
* place; with a solid `stencil`, the notes are drawn in its shape and none
|
|
34
38
|
* runs. Dragging turns the graph, the wheel and a pinch bring it nearer, and
|
|
35
|
-
* it turns slowly by itself
|
|
36
|
-
* asks for less motion. It draws only while
|
|
37
|
-
* while the box is out of view or off the
|
|
39
|
+
* it turns slowly by itself for its first five seconds, until the reader
|
|
40
|
+
* handles it, unless the reader asks for less motion. It draws only while
|
|
41
|
+
* something moves, and not at all while the box is out of view or off the
|
|
42
|
+
* page.
|
|
38
43
|
*
|
|
39
|
-
* The renderer is the driver's own, and `disconnect()` frees it.
|
|
44
|
+
* The renderer is the driver's own, and `disconnect()` frees it. Every
|
|
45
|
+
* three.js graph on a page shares six WebGL contexts: past six, one far off
|
|
46
|
+
* screen gives its context back and takes it again as it comes near.
|
|
40
47
|
*/
|
|
41
48
|
export declare const threeDriver: (container: HTMLElement, options?: ThreeDriverOptions) => GraphDriver;
|
|
@@ -9,6 +9,7 @@ import { linkKey } from '../connect-graph/diff.js';
|
|
|
9
9
|
import { rgbOf } from '../connect-graph/looks.js';
|
|
10
10
|
import { ease, gesturesHeld, groupsOver, isLight, namesOver, signalsOver, } from '../overlay/overlay.js';
|
|
11
11
|
import { surfaceOf } from '../shapes/surface.js';
|
|
12
|
+
import { pageBudget } from './budget.js';
|
|
12
13
|
const SHAPES = { circle: 0, diamond: 2, ring: 0, square: 1 };
|
|
13
14
|
const REACH = 100;
|
|
14
15
|
const STEP = 250;
|
|
@@ -16,7 +17,7 @@ const MORPH = 1400;
|
|
|
16
17
|
const ZOOM = 1.5;
|
|
17
18
|
const SAMPLE = 96;
|
|
18
19
|
const LINKS = { dark: { close: 0.16, far: 0.025 }, light: { close: 0.36, far: 0.07 } };
|
|
19
|
-
const
|
|
20
|
+
const TURNING = 5000;
|
|
20
21
|
const WASH = { dark: 0.14, full: 1.5, light: 0.18, notes: 6, reach: 3.5 };
|
|
21
22
|
const BLOOM = { dark: 0.28, light: 0 };
|
|
22
23
|
const LIGHT = new Vector3(-0.4, 0.8, 0.45).normalize();
|
|
@@ -292,7 +293,9 @@ const depthOf = (id) => {
|
|
|
292
293
|
export const threeDriver = (container, options = {}) => {
|
|
293
294
|
const still = () => window.matchMedia?.('(prefers-reduced-motion: reduce)').matches ?? false;
|
|
294
295
|
const renderer = new WebGLRenderer({ alpha: true, antialias: true });
|
|
295
|
-
|
|
296
|
+
const screen = window.devicePixelRatio || 1;
|
|
297
|
+
const touch = window.matchMedia?.('(pointer: coarse)').matches ?? false;
|
|
298
|
+
renderer.setPixelRatio(options.density ?? (touch ? Math.min(screen, 2) : Math.min(Math.max(screen * 1.5, 2), 3)));
|
|
296
299
|
renderer.setClearColor(0x000000, 0);
|
|
297
300
|
const canvas = renderer.domElement;
|
|
298
301
|
canvas.style.cssText = 'position:absolute;inset:0;width:100%;height:100%;display:block';
|
|
@@ -317,7 +320,8 @@ export const threeDriver = (container, options = {}) => {
|
|
|
317
320
|
let near = new Set();
|
|
318
321
|
let stopped = false;
|
|
319
322
|
let moved = false;
|
|
320
|
-
|
|
323
|
+
const turnUntil = performance.now() + TURNING;
|
|
324
|
+
let touched = false;
|
|
321
325
|
let scale = 1;
|
|
322
326
|
let positions = new Float32Array();
|
|
323
327
|
const uniforms = {
|
|
@@ -647,6 +651,7 @@ export const threeDriver = (container, options = {}) => {
|
|
|
647
651
|
return;
|
|
648
652
|
}
|
|
649
653
|
glide = { duration, from, fromDistance, start: performance.now(), to, toDistance: distance };
|
|
654
|
+
invalidate();
|
|
650
655
|
};
|
|
651
656
|
const sphereOf = (spots) => {
|
|
652
657
|
const center = new Vector3();
|
|
@@ -977,18 +982,20 @@ export const threeDriver = (container, options = {}) => {
|
|
|
977
982
|
const shade = LINKS[dark ? 'dark' : 'light'];
|
|
978
983
|
quiet.material.opacity = shade.far + (shade.close - shade.far) * closeness;
|
|
979
984
|
};
|
|
985
|
+
let visible = true;
|
|
986
|
+
let pending = 0;
|
|
980
987
|
let dirty = true;
|
|
981
988
|
const invalidate = () => {
|
|
982
989
|
dirty = true;
|
|
990
|
+
if (!pending && !stopped && visible && container.isConnected) {
|
|
991
|
+
pending = requestAnimationFrame(tick);
|
|
992
|
+
}
|
|
983
993
|
};
|
|
984
|
-
let visible = true;
|
|
985
|
-
let pending = 0;
|
|
986
994
|
const tick = (now) => {
|
|
987
995
|
pending = 0;
|
|
988
996
|
if (stopped || !visible || !container.isConnected) {
|
|
989
997
|
return;
|
|
990
998
|
}
|
|
991
|
-
pending = requestAnimationFrame(tick);
|
|
992
999
|
const moving = glide !== undefined || morph !== undefined || layout !== undefined;
|
|
993
1000
|
if (glide) {
|
|
994
1001
|
const through = Math.min((now - glide.start) / glide.duration, 1);
|
|
@@ -1042,14 +1049,18 @@ export const threeDriver = (container, options = {}) => {
|
|
|
1042
1049
|
controls.autoRotate =
|
|
1043
1050
|
!still() &&
|
|
1044
1051
|
options.autoRotate !== false &&
|
|
1052
|
+
!touched &&
|
|
1045
1053
|
hovered === undefined &&
|
|
1046
1054
|
selected === undefined &&
|
|
1047
|
-
now
|
|
1048
|
-
controls.enableDamping = !still();
|
|
1055
|
+
now < turnUntil;
|
|
1056
|
+
controls.enableDamping = !still() && (touched || now < turnUntil);
|
|
1049
1057
|
const turned = controls.update();
|
|
1050
1058
|
if (!(moving || turned || dirty)) {
|
|
1051
1059
|
return;
|
|
1052
1060
|
}
|
|
1061
|
+
if (!pending) {
|
|
1062
|
+
pending = requestAnimationFrame(tick);
|
|
1063
|
+
}
|
|
1053
1064
|
dirty = false;
|
|
1054
1065
|
cue();
|
|
1055
1066
|
if (solids.cube.mesh || solids.octahedron.mesh) {
|
|
@@ -1061,9 +1072,6 @@ export const threeDriver = (container, options = {}) => {
|
|
|
1061
1072
|
};
|
|
1062
1073
|
const wake = () => {
|
|
1063
1074
|
invalidate();
|
|
1064
|
-
if (!pending && !stopped && visible && container.isConnected) {
|
|
1065
|
-
pending = requestAnimationFrame(tick);
|
|
1066
|
-
}
|
|
1067
1075
|
};
|
|
1068
1076
|
const seen = typeof IntersectionObserver === 'function'
|
|
1069
1077
|
? new IntersectionObserver((entries) => {
|
|
@@ -1083,6 +1091,26 @@ export const threeDriver = (container, options = {}) => {
|
|
|
1083
1091
|
invalidate();
|
|
1084
1092
|
};
|
|
1085
1093
|
let resizing = 0;
|
|
1094
|
+
const restored = () => {
|
|
1095
|
+
invalidate();
|
|
1096
|
+
};
|
|
1097
|
+
canvas.addEventListener('webglcontextrestored', restored);
|
|
1098
|
+
const holder = {
|
|
1099
|
+
release: () => renderer.forceContextLoss(),
|
|
1100
|
+
restore: () => renderer.forceContextRestore(),
|
|
1101
|
+
};
|
|
1102
|
+
pageBudget.join(holder);
|
|
1103
|
+
const nearby = typeof IntersectionObserver === 'function'
|
|
1104
|
+
? new IntersectionObserver((entries) => {
|
|
1105
|
+
if (entries.at(-1)?.isIntersecting ?? true) {
|
|
1106
|
+
pageBudget.near(holder);
|
|
1107
|
+
}
|
|
1108
|
+
else {
|
|
1109
|
+
pageBudget.far(holder, performance.now());
|
|
1110
|
+
}
|
|
1111
|
+
}, { rootMargin: '50%' })
|
|
1112
|
+
: undefined;
|
|
1113
|
+
nearby?.observe(container);
|
|
1086
1114
|
const sized = new ResizeObserver(() => {
|
|
1087
1115
|
cancelAnimationFrame(resizing);
|
|
1088
1116
|
resizing = requestAnimationFrame(resize);
|
|
@@ -1092,12 +1120,12 @@ export const threeDriver = (container, options = {}) => {
|
|
|
1092
1120
|
pending = requestAnimationFrame(tick);
|
|
1093
1121
|
controls.addEventListener('start', () => {
|
|
1094
1122
|
moved = true;
|
|
1095
|
-
|
|
1123
|
+
touched = true;
|
|
1096
1124
|
glide = undefined;
|
|
1097
1125
|
wake();
|
|
1098
1126
|
});
|
|
1099
|
-
controls.addEventListener('
|
|
1100
|
-
|
|
1127
|
+
controls.addEventListener('change', () => {
|
|
1128
|
+
invalidate();
|
|
1101
1129
|
});
|
|
1102
1130
|
const frontOf = (ray) => {
|
|
1103
1131
|
const frame = shapeFrame;
|
|
@@ -1270,6 +1298,7 @@ export const threeDriver = (container, options = {}) => {
|
|
|
1270
1298
|
const step = (now) => {
|
|
1271
1299
|
const through = Math.min((now - start) / STEP, 1);
|
|
1272
1300
|
camera.position.copy(from.clone().lerp(to, ease(through)));
|
|
1301
|
+
invalidate();
|
|
1273
1302
|
if (through < 1 && !stopped) {
|
|
1274
1303
|
requestAnimationFrame(step);
|
|
1275
1304
|
}
|
|
@@ -1350,6 +1379,9 @@ export const threeDriver = (container, options = {}) => {
|
|
|
1350
1379
|
},
|
|
1351
1380
|
stop: () => {
|
|
1352
1381
|
stopped = true;
|
|
1382
|
+
canvas.removeEventListener('webglcontextrestored', restored);
|
|
1383
|
+
nearby?.disconnect();
|
|
1384
|
+
pageBudget.leave(holder);
|
|
1353
1385
|
cancelAnimationFrame(pending);
|
|
1354
1386
|
cancelAnimationFrame(hovering);
|
|
1355
1387
|
cancelAnimationFrame(resizing);
|
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.
|
|
4
|
+
"version": "0.186.1",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Ross Sheppard <ross.sheppard@fusion.dev>",
|
|
7
7
|
"repository": {
|
|
@@ -21,6 +21,10 @@
|
|
|
21
21
|
"types": "./dist/cosmos/index.d.ts",
|
|
22
22
|
"default": "./dist/cosmos/index.js"
|
|
23
23
|
},
|
|
24
|
+
"./isometric": {
|
|
25
|
+
"types": "./dist/isometric/index.d.ts",
|
|
26
|
+
"default": "./dist/isometric/index.js"
|
|
27
|
+
},
|
|
24
28
|
"./model": {
|
|
25
29
|
"types": "./dist/model/index.d.ts",
|
|
26
30
|
"default": "./dist/model/index.js"
|
|
@@ -55,12 +59,12 @@
|
|
|
55
59
|
},
|
|
56
60
|
"bugs": "https://github.com/project-fusion/fusion/issues",
|
|
57
61
|
"dependencies": {
|
|
58
|
-
"@fusion.dev/helix-core": "0.
|
|
62
|
+
"@fusion.dev/helix-core": "0.186.1"
|
|
59
63
|
},
|
|
60
64
|
"devDependencies": {
|
|
61
65
|
"@cosmos.gl/graph": "^3.4.2",
|
|
62
|
-
"@fusion.dev/helix-headless": "0.
|
|
63
|
-
"@fusion.dev/helix-testing": "0.
|
|
66
|
+
"@fusion.dev/helix-headless": "0.186.1",
|
|
67
|
+
"@fusion.dev/helix-testing": "0.186.1",
|
|
64
68
|
"@types/bun": "^1.4.2",
|
|
65
69
|
"@types/three": "^0.186.0",
|
|
66
70
|
"d3-force-3d": "^3.0.6",
|