@cocorof/graphier 1.4.3 → 1.4.5

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/dist/index.d.ts CHANGED
@@ -185,6 +185,13 @@ export declare interface NavigationConfig {
185
185
  * (default: cameraMode in 3D, "pan" in 2D)
186
186
  */
187
187
  keyboard?: "fly" | "orbit" | "pan" | "off";
188
+ /**
189
+ * Base drag-pan speed multiplier (default: 1 = cursor-accurate at the
190
+ * orbit-target depth). On top of this, pan automatically accelerates
191
+ * up to 3x as the camera zooms deep into the graph — otherwise
192
+ * traversing a large graph while zoomed in takes dozens of drags.
193
+ */
194
+ panSpeed?: number;
188
195
  }
189
196
 
190
197
  export declare const neon: ThemeConfig;
package/dist/index.js CHANGED
@@ -2050,7 +2050,7 @@ function createPanUpdate(camera, controls, keys) {
2050
2050
  const target = controls.target;
2051
2051
  const dist = camera.position.distanceTo(target);
2052
2052
  if (dx || dy) {
2053
- const step = Math.max(dist, 50) * PAN_SPEED;
2053
+ const step = Math.max(dist, 50) * PAN_SPEED * (controls.panSpeed || 1);
2054
2054
  _panRight.setFromMatrixColumn(camera.matrix, 0);
2055
2055
  _panUp.setFromMatrixColumn(camera.matrix, 1);
2056
2056
  _panOffset.set(0, 0, 0).addScaledVector(_panRight, dx * step).addScaledVector(_panUp, dy * step);
@@ -2229,6 +2229,7 @@ function createScene(container, backgroundColor, style, rendererConfig) {
2229
2229
  controls.zoomSpeed = 1.2;
2230
2230
  controls.minDistance = 10;
2231
2231
  controls.maxDistance = 3e4;
2232
+ controls.screenSpacePanning = true;
2232
2233
  scene.add(new THREE.AmbientLight(16777215, 0.8));
2233
2234
  const stars = createStarField(4e3, 8e3);
2234
2235
  stars.visible = style.starField;
@@ -2242,26 +2243,35 @@ function createScene(container, backgroundColor, style, rendererConfig) {
2242
2243
  controls.enableZoom = false;
2243
2244
  const flags = {
2244
2245
  is2D: false,
2245
- keyboardMode3D: cameraMode
2246
+ keyboardMode3D: cameraMode,
2247
+ rotateButton: null,
2248
+ panSpeedBase: 1
2246
2249
  };
2247
- const _wheelDir = new THREE.Vector3();
2250
+ const _wheelOffset = new THREE.Vector3();
2248
2251
  const onWheel = (e) => {
2249
2252
  if (flags.is2D) return;
2250
2253
  e.preventDefault();
2251
2254
  let delta = -e.deltaY;
2252
2255
  if (e.deltaMode === 1) delta *= 40;
2253
2256
  if (e.deltaMode === 2) delta *= 800;
2254
- camera.getWorldDirection(_wheelDir);
2255
- const dist = camera.position.distanceTo(controls.target);
2257
+ const target = controls.target;
2258
+ _wheelOffset.copy(camera.position).sub(target);
2259
+ const dist = _wheelOffset.length();
2256
2260
  const moveAmount = delta * 2e-3 * Math.max(dist * 0.1, 1);
2257
- camera.position.addScaledVector(_wheelDir, moveAmount);
2258
- controls.target.addScaledVector(_wheelDir, moveAmount);
2261
+ const newDist = Math.min(
2262
+ Math.max(dist - moveAmount, controls.minDistance),
2263
+ controls.maxDistance
2264
+ );
2265
+ _wheelOffset.setLength(newDist);
2266
+ camera.position.copy(target).add(_wheelOffset);
2259
2267
  };
2260
2268
  canvas.addEventListener("wheel", onWheel, { passive: false });
2269
+ const trackballCleanup = setupTrackballRotation(canvas, camera, controls, flags);
2261
2270
  const scrollCleanup = () => {
2262
2271
  canvas.removeEventListener("wheel", onWheel);
2263
2272
  canvas.removeEventListener("dragstart", onDragStart);
2264
2273
  canvas.removeEventListener("selectstart", onSelectStart);
2274
+ trackballCleanup();
2265
2275
  };
2266
2276
  return {
2267
2277
  scene,
@@ -2278,24 +2288,82 @@ function createScene(container, backgroundColor, style, rendererConfig) {
2278
2288
  flags
2279
2289
  };
2280
2290
  }
2281
- const MOUSE_ACTION = {
2282
- rotate: THREE.MOUSE.ROTATE,
2283
- pan: THREE.MOUSE.PAN
2284
- };
2291
+ const TRACKBALL_SPEED = 4e-3;
2292
+ function setupTrackballRotation(canvas, camera, controls, flags) {
2293
+ let rotating = false;
2294
+ let lastX = 0;
2295
+ let lastY = 0;
2296
+ const _offset2 = new THREE.Vector3();
2297
+ const _right2 = new THREE.Vector3();
2298
+ const _upAxis = new THREE.Vector3();
2299
+ const _q2 = new THREE.Quaternion();
2300
+ const _qPitch = new THREE.Quaternion();
2301
+ function onDown(e) {
2302
+ if (flags.rotateButton == null || e.button !== flags.rotateButton) return;
2303
+ rotating = true;
2304
+ lastX = e.clientX;
2305
+ lastY = e.clientY;
2306
+ try {
2307
+ canvas.setPointerCapture(e.pointerId);
2308
+ } catch {
2309
+ }
2310
+ }
2311
+ function onMove(e) {
2312
+ if (!rotating) return;
2313
+ const dx = e.clientX - lastX;
2314
+ const dy = e.clientY - lastY;
2315
+ lastX = e.clientX;
2316
+ lastY = e.clientY;
2317
+ if (!dx && !dy) return;
2318
+ const target = controls.target;
2319
+ _offset2.copy(camera.position).sub(target);
2320
+ _right2.setFromMatrixColumn(camera.matrix, 0);
2321
+ _upAxis.setFromMatrixColumn(camera.matrix, 1);
2322
+ _q2.setFromAxisAngle(_upAxis, -dx * TRACKBALL_SPEED);
2323
+ _qPitch.setFromAxisAngle(_right2, -dy * TRACKBALL_SPEED);
2324
+ _q2.multiply(_qPitch);
2325
+ _offset2.applyQuaternion(_q2);
2326
+ camera.up.applyQuaternion(_q2).normalize();
2327
+ camera.position.copy(target).add(_offset2);
2328
+ camera.lookAt(target);
2329
+ }
2330
+ function onUp(e) {
2331
+ if (!rotating) return;
2332
+ rotating = false;
2333
+ try {
2334
+ canvas.releasePointerCapture(e.pointerId);
2335
+ } catch {
2336
+ }
2337
+ }
2338
+ canvas.addEventListener("pointerdown", onDown);
2339
+ canvas.addEventListener("pointermove", onMove);
2340
+ canvas.addEventListener("pointerup", onUp);
2341
+ canvas.addEventListener("pointercancel", onUp);
2342
+ return () => {
2343
+ canvas.removeEventListener("pointerdown", onDown);
2344
+ canvas.removeEventListener("pointermove", onMove);
2345
+ canvas.removeEventListener("pointerup", onUp);
2346
+ canvas.removeEventListener("pointercancel", onUp);
2347
+ };
2348
+ }
2285
2349
  function applyNavigationMode(state, is2D, nav) {
2286
2350
  const { controls, camera, keyboard } = state;
2287
2351
  state.flags.is2D = is2D;
2288
2352
  const left = (nav == null ? void 0 : nav.leftButton) ?? (is2D ? "pan" : "rotate");
2289
2353
  const right = (nav == null ? void 0 : nav.rightButton) ?? (is2D ? "rotate" : "pan");
2354
+ const NONE = -1;
2290
2355
  controls.mouseButtons = {
2291
- LEFT: MOUSE_ACTION[left],
2356
+ LEFT: left === "pan" ? THREE.MOUSE.PAN : NONE,
2292
2357
  MIDDLE: THREE.MOUSE.DOLLY,
2293
- RIGHT: MOUSE_ACTION[right]
2358
+ RIGHT: right === "pan" ? THREE.MOUSE.PAN : NONE
2294
2359
  };
2295
- controls.enableRotate = left === "rotate" || right === "rotate";
2360
+ controls.enableRotate = false;
2361
+ state.flags.rotateButton = left === "rotate" ? 0 : right === "rotate" ? 2 : null;
2296
2362
  controls.touches = left === "pan" ? { ONE: THREE.TOUCH.PAN, TWO: THREE.TOUCH.DOLLY_PAN } : { ONE: THREE.TOUCH.ROTATE, TWO: THREE.TOUCH.DOLLY_PAN };
2297
2363
  controls.enableZoom = is2D;
2298
- controls.screenSpacePanning = is2D;
2364
+ controls.screenSpacePanning = true;
2365
+ state.flags.panSpeedBase = (nav == null ? void 0 : nav.panSpeed) ?? 1;
2366
+ controls.panSpeed = state.flags.panSpeedBase;
2299
2367
  const kb = (nav == null ? void 0 : nav.keyboard) ?? (is2D ? "pan" : state.flags.keyboardMode3D);
2300
2368
  if (kb === "off") {
2301
2369
  keyboard.setEnabled(false);
@@ -2631,6 +2699,7 @@ function animateCamera(camera, controls, targetPos, targetLookAt, duration) {
2631
2699
  function zoomToFitPositions(camera, controls, positions, duration, padding) {
2632
2700
  const count = positions.length / 3;
2633
2701
  if (count === 0) return;
2702
+ camera.up.set(0, 1, 0);
2634
2703
  let cx = 0, cy = 0, cz = 0;
2635
2704
  for (let i = 0; i < count; i++) {
2636
2705
  cx += positions[i * 3];
@@ -2875,8 +2944,8 @@ function setupInteraction(canvas, camera, controls, getNodesMesh, getNodes, call
2875
2944
  }
2876
2945
  }
2877
2946
  function onContextMenu2(e) {
2878
- if (!callbacks.onContextMenu) return;
2879
2947
  e.preventDefault();
2948
+ if (!callbacks.onContextMenu) return;
2880
2949
  const nodesMesh = getNodesMesh();
2881
2950
  if (!nodesMesh) return;
2882
2951
  const rect = canvas.getBoundingClientRect();
@@ -3140,7 +3209,29 @@ function NetworkGraph3DInner(props, ref) {
3140
3209
  rendererProp
3141
3210
  );
3142
3211
  sceneRef.current = state;
3212
+ let panBoostAt = 0;
3213
+ let graphRadius = 0;
3143
3214
  const cancelAnim = startAnimationLoop(state, () => {
3215
+ const nowMs = performance.now();
3216
+ if (nowMs - panBoostAt > 400) {
3217
+ panBoostAt = nowMs;
3218
+ const pos = dataRef.current.positions;
3219
+ if (pos) {
3220
+ let maxSq = 0;
3221
+ for (let i = 0; i < pos.length; i += 3) {
3222
+ const sq = pos[i] * pos[i] + pos[i + 1] * pos[i + 1] + pos[i + 2] * pos[i + 2];
3223
+ if (sq > maxSq) maxSq = sq;
3224
+ }
3225
+ graphRadius = Math.sqrt(maxSq);
3226
+ }
3227
+ if (graphRadius > 0) {
3228
+ const dist = state.camera.position.distanceTo(
3229
+ state.controls.target
3230
+ );
3231
+ const boost = Math.min(3, Math.max(1, graphRadius / (3 * dist)));
3232
+ state.controls.panSpeed = state.flags.panSpeedBase * boost;
3233
+ }
3234
+ }
3144
3235
  updateLabels(
3145
3236
  state.labels,
3146
3237
  dataRef.current.nodes,