@cocorof/graphier 1.4.0 → 1.4.2
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 +3 -1
- package/dist/index.cjs +90 -27
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +25 -0
- package/dist/index.js +90 -27
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -39,12 +39,14 @@ const ref = useRef<NetworkGraph3DRef>(null);
|
|
|
39
39
|
<GraphMinimap graphRef={ref} width={200} height={140} />
|
|
40
40
|
```
|
|
41
41
|
|
|
42
|
-
- `layout.dimensions: 2` — simulation runs in 2D (z locked to 0)
|
|
42
|
+
- `layout.dimensions: 2` — flat Obsidian-style plane: simulation runs in 2D (z locked to 0). Left-drag pans, right-drag tilts/orbits the world, wheel/pinch zooms, arrows/WASD pan and z/x zoom (v1.4.1).
|
|
43
|
+
- `enableNodeDrag={false}` — left-drag always reaches the camera even over nodes; essential for dense graphs (v1.4.1).
|
|
43
44
|
- `layout.clusterBy: "type" | "group"` + `clusterStrength` — pulls same-key nodes toward a shared centroid so categories form visible clusters.
|
|
44
45
|
- `linkVisibility={(link) => bool}` — reheat-free per-edge filter (e.g. hide a link type).
|
|
45
46
|
- `visibleNodeIds` — client-side filter; hidden nodes/edges/labels vanish via per-instance scale + collapsed segments. Positions are preserved: toggling filters never reheats the simulation.
|
|
46
47
|
- `hoverHighlight` / `hoverHighlightHops` — Obsidian-style neighborhood emphasis on hover (selection wins while active).
|
|
47
48
|
- `theme="paper"` — light background preset; edge blending switches to normal automatically (additive lines vanish on white) and highlight/dim directions invert.
|
|
49
|
+
- `renderer.navigation` — remap pointer/keyboard navigation per consumer, e.g. `{ leftButton: "pan", rightButton: "rotate", keyboard: "pan" }` for pan-first 3D graphs (v1.4.2).
|
|
48
50
|
- `GraphMinimap` — 2D-canvas overview (no second WebGL context): draws all visible nodes + the camera viewport rectangle, click/drag to pan. Powered by `ref.getGraphSnapshot()` / `ref.getViewportRect()` / `ref.panTo(x, y)`.
|
|
49
51
|
|
|
50
52
|
## Install
|
package/dist/index.cjs
CHANGED
|
@@ -2049,6 +2049,45 @@ const _right = new THREE__namespace.Vector3();
|
|
|
2049
2049
|
const _q = new THREE__namespace.Quaternion();
|
|
2050
2050
|
const _dir = new THREE__namespace.Vector3();
|
|
2051
2051
|
const _worldUp = new THREE__namespace.Vector3(0, 1, 0);
|
|
2052
|
+
const PAN_SPEED = 0.02;
|
|
2053
|
+
const PAN_ZOOM_FACTOR = 0.02;
|
|
2054
|
+
function createPanUpdate(camera, controls, keys) {
|
|
2055
|
+
const _panRight = new THREE__namespace.Vector3();
|
|
2056
|
+
const _panUp = new THREE__namespace.Vector3();
|
|
2057
|
+
const _panOffset = new THREE__namespace.Vector3();
|
|
2058
|
+
return function update() {
|
|
2059
|
+
let dx = 0;
|
|
2060
|
+
let dy = 0;
|
|
2061
|
+
if (keys["ArrowLeft"] || keys["a"] || keys["A"]) dx -= 1;
|
|
2062
|
+
if (keys["ArrowRight"] || keys["d"] || keys["D"]) dx += 1;
|
|
2063
|
+
if (keys["ArrowUp"] || keys["w"] || keys["W"]) dy += 1;
|
|
2064
|
+
if (keys["ArrowDown"] || keys["s"] || keys["S"]) dy -= 1;
|
|
2065
|
+
const zoomIn = keys["z"] || keys["Z"] || keys["+"] || keys["="];
|
|
2066
|
+
const zoomOut = keys["x"] || keys["X"] || keys["-"] || keys["_"];
|
|
2067
|
+
if (!dx && !dy && !zoomIn && !zoomOut) return false;
|
|
2068
|
+
const target = controls.target;
|
|
2069
|
+
const dist = camera.position.distanceTo(target);
|
|
2070
|
+
if (dx || dy) {
|
|
2071
|
+
const step = Math.max(dist, 50) * PAN_SPEED;
|
|
2072
|
+
_panRight.setFromMatrixColumn(camera.matrix, 0);
|
|
2073
|
+
_panUp.setFromMatrixColumn(camera.matrix, 1);
|
|
2074
|
+
_panOffset.set(0, 0, 0).addScaledVector(_panRight, dx * step).addScaledVector(_panUp, dy * step);
|
|
2075
|
+
camera.position.add(_panOffset);
|
|
2076
|
+
target.add(_panOffset);
|
|
2077
|
+
}
|
|
2078
|
+
if (zoomIn || zoomOut) {
|
|
2079
|
+
const factor = zoomIn ? -PAN_ZOOM_FACTOR : PAN_ZOOM_FACTOR;
|
|
2080
|
+
_panOffset.copy(camera.position).sub(target);
|
|
2081
|
+
const newLen = Math.max(
|
|
2082
|
+
controls.minDistance,
|
|
2083
|
+
Math.min(controls.maxDistance, _panOffset.length() * (1 + factor))
|
|
2084
|
+
);
|
|
2085
|
+
_panOffset.setLength(newLen);
|
|
2086
|
+
camera.position.copy(target).add(_panOffset);
|
|
2087
|
+
}
|
|
2088
|
+
return true;
|
|
2089
|
+
};
|
|
2090
|
+
}
|
|
2052
2091
|
function createOrbitUpdate(camera, controls, keys) {
|
|
2053
2092
|
return function update() {
|
|
2054
2093
|
let zoom = 0;
|
|
@@ -2148,8 +2187,13 @@ function setupKeyboardControls(camera, controls, container, mode = "fly", flySpe
|
|
|
2148
2187
|
container.addEventListener("keyup", onKeyUp);
|
|
2149
2188
|
container.addEventListener("blur", onBlur);
|
|
2150
2189
|
let enabled = true;
|
|
2151
|
-
|
|
2152
|
-
const
|
|
2190
|
+
let currentMode = mode;
|
|
2191
|
+
const updaters = {
|
|
2192
|
+
fly: createFlyUpdate(camera, controls, keys, state),
|
|
2193
|
+
orbit: createOrbitUpdate(camera, controls, keys),
|
|
2194
|
+
pan: createPanUpdate(camera, controls, keys)
|
|
2195
|
+
};
|
|
2196
|
+
const updateFn = () => enabled ? updaters[currentMode]() : false;
|
|
2153
2197
|
function cleanup() {
|
|
2154
2198
|
container.removeEventListener("keydown", onKeyDown2);
|
|
2155
2199
|
container.removeEventListener("keyup", onKeyUp);
|
|
@@ -2162,7 +2206,11 @@ function setupKeyboardControls(camera, controls, container, mode = "fly", flySpe
|
|
|
2162
2206
|
enabled = on;
|
|
2163
2207
|
if (!on) for (const k of Object.keys(keys)) keys[k] = false;
|
|
2164
2208
|
}
|
|
2165
|
-
|
|
2209
|
+
function setMode(m2) {
|
|
2210
|
+
currentMode = m2;
|
|
2211
|
+
for (const k of Object.keys(keys)) keys[k] = false;
|
|
2212
|
+
}
|
|
2213
|
+
return { update: updateFn, cleanup, setFlySpeed, setEnabled, setMode };
|
|
2166
2214
|
}
|
|
2167
2215
|
function createScene(container, backgroundColor, style, rendererConfig) {
|
|
2168
2216
|
const scene = new THREE__namespace.Scene();
|
|
@@ -2202,7 +2250,10 @@ function createScene(container, backgroundColor, style, rendererConfig) {
|
|
|
2202
2250
|
const cameraMode = (rendererConfig == null ? void 0 : rendererConfig.cameraMode) ?? "fly";
|
|
2203
2251
|
const keyboard = setupKeyboardControls(camera, controls, container, cameraMode, style.flySpeed ?? 1);
|
|
2204
2252
|
controls.enableZoom = false;
|
|
2205
|
-
const flags = {
|
|
2253
|
+
const flags = {
|
|
2254
|
+
is2D: false,
|
|
2255
|
+
keyboardMode3D: cameraMode
|
|
2256
|
+
};
|
|
2206
2257
|
const _wheelDir = new THREE__namespace.Vector3();
|
|
2207
2258
|
const onWheel = (e) => {
|
|
2208
2259
|
if (flags.is2D) return;
|
|
@@ -2233,33 +2284,38 @@ function createScene(container, backgroundColor, style, rendererConfig) {
|
|
|
2233
2284
|
flags
|
|
2234
2285
|
};
|
|
2235
2286
|
}
|
|
2236
|
-
|
|
2287
|
+
const MOUSE_ACTION = {
|
|
2288
|
+
rotate: THREE__namespace.MOUSE.ROTATE,
|
|
2289
|
+
pan: THREE__namespace.MOUSE.PAN
|
|
2290
|
+
};
|
|
2291
|
+
function applyNavigationMode(state, is2D, nav) {
|
|
2237
2292
|
const { controls, camera, keyboard } = state;
|
|
2238
2293
|
state.flags.is2D = is2D;
|
|
2239
|
-
|
|
2294
|
+
const left = (nav == null ? void 0 : nav.leftButton) ?? (is2D ? "pan" : "rotate");
|
|
2295
|
+
const right = (nav == null ? void 0 : nav.rightButton) ?? (is2D ? "rotate" : "pan");
|
|
2296
|
+
controls.mouseButtons = {
|
|
2297
|
+
LEFT: MOUSE_ACTION[left],
|
|
2298
|
+
MIDDLE: THREE__namespace.MOUSE.DOLLY,
|
|
2299
|
+
RIGHT: MOUSE_ACTION[right]
|
|
2300
|
+
};
|
|
2301
|
+
controls.enableRotate = left === "rotate" || right === "rotate";
|
|
2302
|
+
controls.touches = left === "pan" ? { ONE: THREE__namespace.TOUCH.PAN, TWO: THREE__namespace.TOUCH.DOLLY_PAN } : { ONE: THREE__namespace.TOUCH.ROTATE, TWO: THREE__namespace.TOUCH.DOLLY_PAN };
|
|
2240
2303
|
controls.enableZoom = is2D;
|
|
2241
2304
|
controls.screenSpacePanning = is2D;
|
|
2242
|
-
keyboard
|
|
2305
|
+
const kb = (nav == null ? void 0 : nav.keyboard) ?? (is2D ? "pan" : state.flags.keyboardMode3D);
|
|
2306
|
+
if (kb === "off") {
|
|
2307
|
+
keyboard.setEnabled(false);
|
|
2308
|
+
} else {
|
|
2309
|
+
keyboard.setEnabled(true);
|
|
2310
|
+
keyboard.setMode(kb);
|
|
2311
|
+
}
|
|
2243
2312
|
if (is2D) {
|
|
2244
|
-
controls.mouseButtons = {
|
|
2245
|
-
LEFT: THREE__namespace.MOUSE.PAN,
|
|
2246
|
-
MIDDLE: THREE__namespace.MOUSE.DOLLY,
|
|
2247
|
-
RIGHT: THREE__namespace.MOUSE.PAN
|
|
2248
|
-
};
|
|
2249
|
-
controls.touches = { ONE: THREE__namespace.TOUCH.PAN, TWO: THREE__namespace.TOUCH.DOLLY_PAN };
|
|
2250
2313
|
const t = controls.target;
|
|
2251
2314
|
const dist = Math.max(camera.position.distanceTo(t), 50);
|
|
2252
2315
|
t.z = 0;
|
|
2253
2316
|
camera.up.set(0, 1, 0);
|
|
2254
2317
|
camera.position.set(t.x, t.y, dist);
|
|
2255
2318
|
camera.lookAt(t);
|
|
2256
|
-
} else {
|
|
2257
|
-
controls.mouseButtons = {
|
|
2258
|
-
LEFT: THREE__namespace.MOUSE.ROTATE,
|
|
2259
|
-
MIDDLE: THREE__namespace.MOUSE.DOLLY,
|
|
2260
|
-
RIGHT: THREE__namespace.MOUSE.PAN
|
|
2261
|
-
};
|
|
2262
|
-
controls.touches = { ONE: THREE__namespace.TOUCH.ROTATE, TWO: THREE__namespace.TOUCH.DOLLY_PAN };
|
|
2263
2319
|
}
|
|
2264
2320
|
controls.update();
|
|
2265
2321
|
}
|
|
@@ -2622,11 +2678,12 @@ function setupInteraction(canvas, camera, controls, getNodesMesh, getNodes, call
|
|
|
2622
2678
|
const dragPlane = new THREE__namespace.Plane();
|
|
2623
2679
|
const dragIntersect = new THREE__namespace.Vector3();
|
|
2624
2680
|
function onPointerDown2(e) {
|
|
2681
|
+
var _a;
|
|
2625
2682
|
mouseDownPos = { x: e.clientX, y: e.clientY };
|
|
2626
2683
|
if (container && document.activeElement !== container) {
|
|
2627
2684
|
container.focus({ preventScroll: true });
|
|
2628
2685
|
}
|
|
2629
|
-
if (callbacks.onNodeDrag) {
|
|
2686
|
+
if (callbacks.onNodeDrag && (((_a = options == null ? void 0 : options.getNodeDragEnabled) == null ? void 0 : _a.call(options)) ?? true)) {
|
|
2630
2687
|
const nodesMesh = getNodesMesh();
|
|
2631
2688
|
if (nodesMesh) {
|
|
2632
2689
|
const rect = canvas.getBoundingClientRect();
|
|
@@ -2938,6 +2995,7 @@ function NetworkGraph3DInner(props, ref) {
|
|
|
2938
2995
|
nodeValueAccessor,
|
|
2939
2996
|
visibleNodeIds,
|
|
2940
2997
|
linkVisibility,
|
|
2998
|
+
enableNodeDrag = true,
|
|
2941
2999
|
clickToFocus = true,
|
|
2942
3000
|
hoverHighlight = false,
|
|
2943
3001
|
hoverHighlightHops = 1
|
|
@@ -2978,6 +3036,7 @@ function NetworkGraph3DInner(props, ref) {
|
|
|
2978
3036
|
const labelFormatterRef = react.useRef(labelFormatter);
|
|
2979
3037
|
const highlightSetRef = react.useRef(null);
|
|
2980
3038
|
const clickToFocusRef = react.useRef(clickToFocus);
|
|
3039
|
+
const enableNodeDragRef = react.useRef(enableNodeDrag);
|
|
2981
3040
|
const hoverHighlightRef = react.useRef(hoverHighlight);
|
|
2982
3041
|
const hoverHighlightHopsRef = react.useRef(hoverHighlightHops);
|
|
2983
3042
|
const adjacencyMapRef = react.useRef(/* @__PURE__ */ new Map());
|
|
@@ -2999,6 +3058,7 @@ function NetworkGraph3DInner(props, ref) {
|
|
|
2999
3058
|
selectedNodeIdRef.current = selectedNodeId;
|
|
3000
3059
|
labelFormatterRef.current = labelFormatter;
|
|
3001
3060
|
clickToFocusRef.current = clickToFocus;
|
|
3061
|
+
enableNodeDragRef.current = enableNodeDrag;
|
|
3002
3062
|
hoverHighlightRef.current = hoverHighlight;
|
|
3003
3063
|
hoverHighlightHopsRef.current = hoverHighlightHops;
|
|
3004
3064
|
const is2D = (layoutProp == null ? void 0 : layoutProp.dimensions) === 2;
|
|
@@ -3181,7 +3241,8 @@ function NetworkGraph3DInner(props, ref) {
|
|
|
3181
3241
|
},
|
|
3182
3242
|
{
|
|
3183
3243
|
getClickToFocus: () => clickToFocusRef.current,
|
|
3184
|
-
getIs2D: () => is2DRef.current
|
|
3244
|
+
getIs2D: () => is2DRef.current,
|
|
3245
|
+
getNodeDragEnabled: () => enableNodeDragRef.current
|
|
3185
3246
|
}
|
|
3186
3247
|
);
|
|
3187
3248
|
return () => {
|
|
@@ -3461,10 +3522,11 @@ function NetworkGraph3DInner(props, ref) {
|
|
|
3461
3522
|
);
|
|
3462
3523
|
}
|
|
3463
3524
|
}, [visibleSet, linkVisibility, mergedData]);
|
|
3525
|
+
const navKey = JSON.stringify((rendererProp == null ? void 0 : rendererProp.navigation) ?? null);
|
|
3464
3526
|
react.useEffect(() => {
|
|
3465
3527
|
const s = sceneRef.current;
|
|
3466
|
-
if (s)
|
|
3467
|
-
}, [is2D]);
|
|
3528
|
+
if (s) applyNavigationMode(s, is2D, rendererProp == null ? void 0 : rendererProp.navigation);
|
|
3529
|
+
}, [is2D, navKey]);
|
|
3468
3530
|
react.useEffect(() => {
|
|
3469
3531
|
const gObj = graphObjRef.current;
|
|
3470
3532
|
const sceneState = sceneRef.current;
|
|
@@ -3716,11 +3778,12 @@ function NetworkGraph3DInner(props, ref) {
|
|
|
3716
3778
|
const s = sceneRef.current;
|
|
3717
3779
|
if (!s) return null;
|
|
3718
3780
|
const cam = s.camera;
|
|
3719
|
-
const
|
|
3781
|
+
const t = s.controls.target;
|
|
3782
|
+
const dist = Math.max(cam.position.distanceTo(t), 1);
|
|
3720
3783
|
const halfH = Math.tan(cam.fov * Math.PI / 360) * dist;
|
|
3721
3784
|
return {
|
|
3722
|
-
cx:
|
|
3723
|
-
cy:
|
|
3785
|
+
cx: t.x,
|
|
3786
|
+
cy: t.y,
|
|
3724
3787
|
halfW: halfH * cam.aspect,
|
|
3725
3788
|
halfH
|
|
3726
3789
|
};
|