@cocorof/graphier 1.1.1 → 1.2.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/dist/index.d.ts CHANGED
@@ -102,6 +102,13 @@ export declare interface LayoutConfig {
102
102
  velocityDecay?: number;
103
103
  /** Convergence threshold — simulation stops when alpha drops below this (default: 0.005) */
104
104
  settledThreshold?: number;
105
+ /**
106
+ * Spread multiplier — scales charge strength and link distance to control
107
+ * how far apart nodes spread. 1.0 = default, 2.0 = twice as spread out.
108
+ * Automatically scaled by node count when set to "auto".
109
+ * (default: "auto")
110
+ */
111
+ spreadFactor?: "auto" | number;
105
112
  }
106
113
 
107
114
  /** Layout settled callback */
@@ -192,6 +199,8 @@ export declare interface NetworkGraph3DRef {
192
199
  getCamera(): THREE.PerspectiveCamera | null;
193
200
  /** Capture a screenshot as a PNG data URL string (synchronous, matches original) */
194
201
  captureScreenshot(): string | null;
202
+ /** Re-run force layout from current positions (useful after changing spreadFactor) */
203
+ reheatLayout(): void;
195
204
  }
196
205
 
197
206
  export declare function NodeDetailPanel(props: NodeDetailPanelProps): JSX_2.Element;
@@ -286,6 +295,11 @@ export declare interface StyleConfig {
286
295
  maxLabels?: number;
287
296
  /** Edge line width multiplier (default: 1.0). Note: WebGL limits linewidth to 1 on most platforms. */
288
297
  edgeWidthScale?: number;
298
+ /**
299
+ * Fly camera thrust speed multiplier (default: 1.0).
300
+ * 0.5 = half speed, 2.0 = double speed.
301
+ */
302
+ flySpeed?: number;
289
303
  }
290
304
 
291
305
  export declare interface SubgraphOptions {
package/dist/index.js CHANGED
@@ -16,7 +16,8 @@ const DEFAULT_STYLE = {
16
16
  labelThreshold: 0.8,
17
17
  showLabels: true,
18
18
  maxLabels: 150,
19
- edgeWidthScale: 1
19
+ edgeWidthScale: 1,
20
+ flySpeed: 1
20
21
  };
21
22
  const DEFAULT_LAYOUT = {
22
23
  type: "force-3d",
@@ -24,7 +25,8 @@ const DEFAULT_LAYOUT = {
24
25
  linkDistance: "auto",
25
26
  alphaDecay: "auto",
26
27
  velocityDecay: 0.4,
27
- settledThreshold: 5e-3
28
+ settledThreshold: 5e-3,
29
+ spreadFactor: "auto"
28
30
  };
29
31
  function hexToRgb(hex) {
30
32
  const c2 = hex.replace("#", "");
@@ -170,17 +172,26 @@ function resolveTheme(theme) {
170
172
  }
171
173
  return { nodeColor, nodeColorBright, linkColor, backgroundColor };
172
174
  }
175
+ function autoSpreadFactor(n) {
176
+ if (n <= 500) return 1;
177
+ if (n <= 2e3) return 1 + (n - 500) / 1500 * 0.3;
178
+ if (n <= 1e4) return 1.3 + (n - 2e3) / 8e3 * 0.5;
179
+ return Math.min(2.5, 1.8 + (n - 1e4) / 4e4 * 0.7);
180
+ }
173
181
  function resolveLayoutParams(nodeCount, config) {
174
182
  const n = nodeCount;
175
- const charge = (config == null ? void 0 : config.charge) !== "auto" && (config == null ? void 0 : config.charge) != null ? config.charge : n > 5e4 ? -80 : n > 1e4 ? -120 : -200;
176
- const distanceMax = n > 5e4 ? 1500 : n > 1e4 ? 2e3 : 3e3;
183
+ const spread = (config == null ? void 0 : config.spreadFactor) !== "auto" && (config == null ? void 0 : config.spreadFactor) != null ? config.spreadFactor : autoSpreadFactor(n);
184
+ const baseCharge = (config == null ? void 0 : config.charge) !== "auto" && (config == null ? void 0 : config.charge) != null ? config.charge : n > 5e4 ? -80 : n > 1e4 ? -120 : -200;
185
+ const charge = baseCharge * spread;
186
+ const distanceMax = (n > 5e4 ? 1500 : n > 1e4 ? 2e3 : 3e3) * spread;
177
187
  const theta = n > 2e4 ? 1.5 : n > 5e3 ? 1.2 : 0.9;
178
- const linkDistance = (config == null ? void 0 : config.linkDistance) !== "auto" && (config == null ? void 0 : config.linkDistance) != null ? config.linkDistance : n > 5e4 ? 120 : n > 1e4 ? 180 : 250;
188
+ const baseLinkDistance = (config == null ? void 0 : config.linkDistance) !== "auto" && (config == null ? void 0 : config.linkDistance) != null ? config.linkDistance : n > 5e4 ? 120 : n > 1e4 ? 180 : 250;
189
+ const linkDistance = baseLinkDistance * spread;
179
190
  const alphaDecay = (config == null ? void 0 : config.alphaDecay) !== "auto" && (config == null ? void 0 : config.alphaDecay) != null ? config.alphaDecay : n > 5e4 ? 0.04 : n > 1e4 ? 0.03 : 0.02;
180
191
  const velocityDecay = (config == null ? void 0 : config.velocityDecay) ?? 0.4;
181
192
  const settledThreshold = (config == null ? void 0 : config.settledThreshold) ?? 5e-3;
182
193
  const postEvery = n > 5e4 ? 5 : n > 1e4 ? 3 : 2;
183
- const initialRadius2 = 500 + Math.min(n, 1e4) * 0.1;
194
+ const initialRadius2 = (500 + Math.min(n, 1e4) * 0.1) * spread;
184
195
  return {
185
196
  charge,
186
197
  distanceMax,
@@ -1957,6 +1968,7 @@ const _offset = new THREE.Vector3();
1957
1968
  const _right = new THREE.Vector3();
1958
1969
  const _q = new THREE.Quaternion();
1959
1970
  const _dir = new THREE.Vector3();
1971
+ const _worldUp = new THREE.Vector3(0, 1, 0);
1960
1972
  function clamp(v, min, max) {
1961
1973
  return v < min ? min : v > max ? max : v;
1962
1974
  }
@@ -2007,18 +2019,19 @@ function createOrbitUpdate(camera, controls, keys) {
2007
2019
  return true;
2008
2020
  };
2009
2021
  }
2010
- function createFlyUpdate(camera, controls, keys) {
2022
+ function createFlyUpdate(camera, controls, keys, state) {
2011
2023
  const vel = { thrust: 0, yaw: 0, pitch: 0 };
2012
2024
  return function update() {
2013
- if (keys["z"] || keys["Z"]) vel.thrust += FLY_ACCEL;
2014
- if (keys["x"] || keys["X"]) vel.thrust -= FLY_ACCEL;
2025
+ const speedMul = state.flySpeed;
2026
+ if (keys["z"] || keys["Z"]) vel.thrust += FLY_ACCEL * speedMul;
2027
+ if (keys["x"] || keys["X"]) vel.thrust -= FLY_ACCEL * speedMul;
2015
2028
  if (keys["a"] || keys["A"] || keys["ArrowLeft"]) vel.yaw += FLY_TURN_RATE;
2016
2029
  if (keys["d"] || keys["D"] || keys["ArrowRight"]) vel.yaw -= FLY_TURN_RATE;
2017
2030
  if (keys["w"] || keys["W"] || keys["ArrowUp"]) vel.pitch += FLY_TURN_RATE;
2018
2031
  if (keys["s"] || keys["S"] || keys["ArrowDown"]) vel.pitch -= FLY_TURN_RATE;
2019
2032
  const lookDist = camera.position.distanceTo(controls.target);
2020
2033
  const distScale = Math.max(0.3, lookDist * 3e-3);
2021
- vel.thrust = clamp(vel.thrust, -FLY_MAX_THRUST * distScale, FLY_MAX_THRUST * distScale);
2034
+ vel.thrust = clamp(vel.thrust, -FLY_MAX_THRUST * distScale * speedMul, FLY_MAX_THRUST * distScale * speedMul);
2022
2035
  vel.yaw = clamp(vel.yaw, -FLY_TURN_RATE * 2, FLY_TURN_RATE * 2);
2023
2036
  vel.pitch = clamp(vel.pitch, -FLY_TURN_RATE * 2, FLY_TURN_RATE * 2);
2024
2037
  vel.thrust *= FLY_FRICTION;
@@ -2030,13 +2043,17 @@ function createFlyUpdate(camera, controls, keys) {
2030
2043
  const hasMotion = vel.thrust !== 0 || vel.yaw !== 0 || vel.pitch !== 0;
2031
2044
  if (!hasMotion) return false;
2032
2045
  if (vel.yaw !== 0) {
2033
- _q.setFromAxisAngle(camera.up, vel.yaw);
2046
+ const upDot = camera.up.dot(_worldUp);
2047
+ const yawSign = upDot < 0 ? -1 : 1;
2048
+ _q.setFromAxisAngle(_worldUp, vel.yaw * yawSign);
2034
2049
  camera.quaternion.premultiply(_q);
2050
+ camera.up.applyQuaternion(_q).normalize();
2035
2051
  }
2036
2052
  if (vel.pitch !== 0) {
2037
2053
  _right.setFromMatrixColumn(camera.matrixWorld, 0).normalize();
2038
2054
  _q.setFromAxisAngle(_right, vel.pitch);
2039
2055
  camera.quaternion.premultiply(_q);
2056
+ camera.up.applyQuaternion(_q).normalize();
2040
2057
  }
2041
2058
  if (vel.thrust !== 0) {
2042
2059
  camera.getWorldDirection(_dir);
@@ -2047,8 +2064,9 @@ function createFlyUpdate(camera, controls, keys) {
2047
2064
  return true;
2048
2065
  };
2049
2066
  }
2050
- function setupKeyboardControls(camera, controls, container, mode = "fly") {
2067
+ function setupKeyboardControls(camera, controls, container, mode = "fly", flySpeed = 1) {
2051
2068
  const keys = {};
2069
+ const state = { flySpeed };
2052
2070
  function onKeyDown2(e) {
2053
2071
  var _a;
2054
2072
  const tag = (_a = e.target) == null ? void 0 : _a.tagName;
@@ -2064,13 +2082,16 @@ function setupKeyboardControls(camera, controls, container, mode = "fly") {
2064
2082
  container.addEventListener("keydown", onKeyDown2);
2065
2083
  container.addEventListener("keyup", onKeyUp);
2066
2084
  container.addEventListener("blur", onBlur);
2067
- const updateFn = mode === "fly" ? createFlyUpdate(camera, controls, keys) : createOrbitUpdate(camera, controls, keys);
2085
+ const updateFn = mode === "fly" ? createFlyUpdate(camera, controls, keys, state) : createOrbitUpdate(camera, controls, keys);
2068
2086
  function cleanup() {
2069
2087
  container.removeEventListener("keydown", onKeyDown2);
2070
2088
  container.removeEventListener("keyup", onKeyUp);
2071
2089
  container.removeEventListener("blur", onBlur);
2072
2090
  }
2073
- return { update: updateFn, cleanup };
2091
+ function setFlySpeed(speed) {
2092
+ state.flySpeed = speed;
2093
+ }
2094
+ return { update: updateFn, cleanup, setFlySpeed };
2074
2095
  }
2075
2096
  function createScene(container, backgroundColor, style, rendererConfig) {
2076
2097
  const scene = new THREE.Scene();
@@ -2108,7 +2129,7 @@ function createScene(container, backgroundColor, style, rendererConfig) {
2108
2129
  const labels = createLabelSystem(style.maxLabels);
2109
2130
  scene.add(labels.group);
2110
2131
  const cameraMode = (rendererConfig == null ? void 0 : rendererConfig.cameraMode) ?? "fly";
2111
- const keyboard = setupKeyboardControls(camera, controls, container, cameraMode);
2132
+ const keyboard = setupKeyboardControls(camera, controls, container, cameraMode, style.flySpeed ?? 1);
2112
2133
  return {
2113
2134
  scene,
2114
2135
  camera,
@@ -3264,6 +3285,11 @@ function NetworkGraph3DInner(props, ref) {
3264
3285
  }, 30);
3265
3286
  return () => clearInterval(id);
3266
3287
  }, [resolvedStyle.autoOrbit]);
3288
+ useEffect(() => {
3289
+ const s = sceneRef.current;
3290
+ if (!s) return;
3291
+ s.keyboard.setFlySpeed(resolvedStyle.flySpeed);
3292
+ }, [resolvedStyle.flySpeed]);
3267
3293
  useImperativeHandle(ref, () => ({
3268
3294
  cameraPosition(pos, lookAt, duration = 1e3) {
3269
3295
  const s = sceneRef.current;
@@ -3341,6 +3367,16 @@ function NetworkGraph3DInner(props, ref) {
3341
3367
  if (!s) return null;
3342
3368
  s.renderer.render(s.scene, s.camera);
3343
3369
  return s.renderer.domElement.toDataURL("image/png");
3370
+ },
3371
+ reheatLayout() {
3372
+ const gObj = graphObjRef.current;
3373
+ if (gObj == null ? void 0 : gObj.worker) {
3374
+ gObj.worker.postMessage({ type: "stop" });
3375
+ gObj.worker.terminate();
3376
+ gObj.worker = null;
3377
+ }
3378
+ dataRef.current.settled = false;
3379
+ setAppendedData((prev) => prev ? { ...prev } : { nodes: [], links: [] });
3344
3380
  }
3345
3381
  }));
3346
3382
  return /* @__PURE__ */ jsx(