diff --git a/docs/Centering.mdx b/docs/Centering.mdx
index ee67ae0b..56cbcd6a 100644
--- a/docs/Centering.mdx
+++ b/docs/Centering.mdx
@@ -4,7 +4,7 @@ import { GraphCanvas } from '../src';
# Centering
-Reagraph supports the ability to dynamically center nodes using the `centerGraph` method from the `GraphCanvasRef`. This method allows you to programmatically center the camera view around specific nodes or the entire graph.
+Reagraph supports the ability to dynamically center nodes using the `centerGraph` and `fitNodesInView` methods from the `GraphCanvasRef`. These methods allows you to programmatically center the camera view around specific nodes or the entire graph.
### Usage
First, you need to get a reference to the `GraphRef`:
@@ -17,20 +17,30 @@ return (
)
```
-Then, you can use the `centerGraph` method to center all nodes within view of the camera:
+Then, you can use the `fitNodesInView` method to center all nodes within view of the camera:
```js
-graphRef.current?.centerGraph();
+graphRef.current?.fitNodesInView();
```
-If you want to center the view around specific nodes, you can pass an array of node ids to the `centerGraph` method:
+If you want to fit the view around specific nodes, you can pass an array of node ids to the `fitNodesInView` method:
```jsx
+graphRef.current?.fitNodesInView(['node1', 'node2']);
+```
+
+If you want to center the camera on a given set of nodes without adjusting the zoom, you can use the `centerGraph` method:
+
+```jsx
+// Center the camera position based on all nodes in the graph
+graphRef.current?.centerGraph();
+
+// Center the camera position based on specific nodes
graphRef.current?.centerGraph(['node1', 'node2']);
```
### Examples
-In this example, clicking the "Center Graph" button will center the camera around all the nodes in the graph:
+In this example, clicking the "Fit View" button will fit the view around all the nodes in the graph:
```jsx
import React, { useRef } from 'react';
import { GraphCanvas } from 'reagraph';
@@ -38,21 +48,21 @@ import { GraphCanvas } from 'reagraph';
const MyComponent = () => {
const graphRef = useRef(null);
- const centerGraph = () => {
- graphRef.current?.centerGraph();
+ const fitView = () => {
+ graphRef.current?.fitNodesInView();
};
return (
-
+
);
};
```
-Here's a more advanced example that centers the camera on the whole graph whenever new nodes are added:
+Here's a more advanced example that fits the view on the whole graph whenever new nodes are added:
```jsx
import React, { useRef, useEffect } from 'react';
import { GraphCanvas } from 'reagraph';
@@ -61,7 +71,7 @@ const MyComponent = ({ nodes }) => {
const graphRef = useRef(null);
useEffect(() => {
- graphRef.current?.centerGraph();
+ graphRef.current?.fitNodesInView();
}, [nodes]);
return ;
diff --git a/docs/demos/Basic.story.tsx b/docs/demos/Basic.story.tsx
index 004c1e88..b2aec0cb 100644
--- a/docs/demos/Basic.story.tsx
+++ b/docs/demos/Basic.story.tsx
@@ -105,7 +105,7 @@ export const LiveUpdates = () => {
const [edges, setEdges] = useState(simpleEdges);
useEffect(() => {
- ref.current?.centerGraph();
+ ref.current?.fitNodesInView();
}, [nodes]);
return (
diff --git a/docs/demos/Controls.story.tsx b/docs/demos/Controls.story.tsx
index 08dc93d8..21af49c6 100644
--- a/docs/demos/Controls.story.tsx
+++ b/docs/demos/Controls.story.tsx
@@ -15,6 +15,7 @@ export const All = () => {
+
diff --git a/src/CameraControls/useCenterGraph.ts b/src/CameraControls/useCenterGraph.ts
index 5a8328c9..2b42df29 100644
--- a/src/CameraControls/useCenterGraph.ts
+++ b/src/CameraControls/useCenterGraph.ts
@@ -16,6 +16,11 @@ export interface CenterNodesParams {
centerOnlyIfNodesNotInView?: boolean;
}
+export interface FitNodesParams {
+ animated?: boolean;
+ fitOnlyIfNodesNotInView?: boolean;
+}
+
export interface CenterGraphInput {
/**
* Whether the animate the transition or not.
@@ -54,10 +59,10 @@ export interface CenterGraphOutput {
/**
* Centers the graph on a specific node or list of nodes.
*
- * @param ids - An array of node IDs to center the graph on. If this parameter is omitted,
+ * @param nodeIds - An array of node IDs to center the graph on. If this parameter is omitted,
* the graph will be centered on all nodes.
*
- * @param centerOnlyIfNodesNotInView - A boolean flag that determines whether the graph should
+ * @param opts.centerOnlyIfNodesNotInView - A boolean flag that determines whether the graph should
* only be centered if the nodes specified by `ids` are not currently in view. If this
* parameter is `true`, the graph will only be re-centered if one or more of the nodes
* specified by `ids` are not currently in view. If this parameter is
@@ -66,6 +71,21 @@ export interface CenterGraphOutput {
*/
centerNodesById: (nodeIds: string[], opts?: CenterNodesParams) => void;
+ /**
+ * Fit all the given nodes into view of the camera.
+ *
+ * @param nodeIds - An array of node IDs to fit the view on. If this parameter is omitted,
+ * the view will fit to all nodes.
+ *
+ * @param opts.fitOnlyIfNodesNotInView - A boolean flag that determines whether the view should
+ * only be fit if the nodes specified by `ids` are not currently in view. If this
+ * parameter is `true`, the view will only be fit if one or more of the nodes
+ * specified by `ids` are not currently visible in the viewport. If this parameter is
+ * `false` or omitted, the view will be fit regardless of whether the nodes
+ * are currently in view.
+ */
+ fitNodesInViewById: (nodeIds: string[], opts?: FitNodesParams) => void;
+
/**
* Whether the graph is centered or not.
*/
@@ -99,8 +119,34 @@ export const useCenterGraph = ({
nodes?.some(node => !isNodeInView(camera, node.position)))
) {
// Centers the graph based on the central most node
- const { minX, maxX, minY, maxY, minZ, maxZ, x, y, z } =
- getLayoutCenter(nodes);
+ const { x, y, z } = getLayoutCenter(nodes);
+
+ await controls.setTarget(x, y, z, animated);
+
+ if (!isCentered) {
+ setIsCentered(true);
+ }
+
+ invalidate();
+ }
+ },
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ [invalidate, controls, nodes]
+ );
+
+ const fitNodesInView = useCallback(
+ async (
+ nodes,
+ opts: FitNodesParams = { animated: true, fitOnlyIfNodesNotInView: false }
+ ) => {
+ const { fitOnlyIfNodesNotInView } = opts;
+
+ if (
+ !fitOnlyIfNodesNotInView ||
+ (fitOnlyIfNodesNotInView &&
+ nodes?.some(node => !isNodeInView(camera, node.position)))
+ ) {
+ const { minX, maxX, minY, maxY, minZ, maxZ } = getLayoutCenter(nodes);
if (!layoutType.includes('3d')) {
// fitToBox will auto rotate to the closest axis including the z axis,
@@ -115,12 +161,14 @@ export const useCenterGraph = ({
void controls?.rotate(horizontalRotation, verticalRotation, true);
}
+ void controls?.zoomTo(1, opts?.animated);
+
await controls?.fitToBox(
new Box3(
new Vector3(minX, minY, minZ),
new Vector3(maxX, maxY, maxZ)
),
- animated,
+ opts?.animated,
{
cover: false,
paddingLeft: PADDING,
@@ -129,21 +177,13 @@ export const useCenterGraph = ({
paddingTop: PADDING
}
);
- await controls.setTarget(x, y, z, animated);
-
- if (!isCentered) {
- setIsCentered(true);
- }
-
- invalidate();
}
},
- // eslint-disable-next-line react-hooks/exhaustive-deps
- [invalidate, controls, nodes]
+ [camera, controls, layoutType]
);
- const centerNodesById = useCallback(
- (nodeIds: string[], opts: CenterNodesParams) => {
+ const getNodesById = useCallback(
+ (nodeIds: string[]) => {
let mappedNodes: InternalGraphNode[] | null = null;
if (nodeIds?.length) {
@@ -162,12 +202,30 @@ export const useCenterGraph = ({
}, []);
}
+ return mappedNodes;
+ },
+ [nodes]
+ );
+
+ const centerNodesById = useCallback(
+ (nodeIds: string[], opts: CenterNodesParams) => {
+ const mappedNodes = getNodesById(nodeIds);
+
centerNodes(mappedNodes || nodes, {
animated,
centerOnlyIfNodesNotInView: opts?.centerOnlyIfNodesNotInView
});
},
- [animated, centerNodes, nodes]
+ [animated, centerNodes, getNodesById, nodes]
+ );
+
+ const fitNodesInViewById = useCallback(
+ async (nodeIds: string[], opts: FitNodesParams) => {
+ const mappedNodes = getNodesById(nodeIds);
+
+ await fitNodesInView(mappedNodes || nodes, { animated, ...opts });
+ },
+ [animated, fitNodesInView, getNodesById, nodes]
);
useLayoutEffect(() => {
@@ -177,13 +235,14 @@ export const useCenterGraph = ({
if (!mounted.current) {
// Center the graph once nodes are loaded on mount
await centerNodes(nodes, { animated: false });
+ await fitNodesInView(nodes, { animated: false });
mounted.current = true;
}
}
}
load();
- }, [controls, centerNodes, nodes, animated, camera]);
+ }, [controls, centerNodes, nodes, animated, camera, fitNodesInView]);
useHotkeys([
{
@@ -195,5 +254,5 @@ export const useCenterGraph = ({
}
]);
- return { centerNodes, centerNodesById, isCentered };
+ return { centerNodes, centerNodesById, fitNodesInViewById, isCentered };
};
diff --git a/src/GraphCanvas.tsx b/src/GraphCanvas.tsx
index afd5614f..e452d2c9 100644
--- a/src/GraphCanvas.tsx
+++ b/src/GraphCanvas.tsx
@@ -134,6 +134,8 @@ export const GraphCanvas: FC }> =
useImperativeHandle(ref, () => ({
centerGraph: (nodeIds, opts) =>
rendererRef.current?.centerGraph(nodeIds, opts),
+ fitNodesInView: (nodeIds, opts) =>
+ rendererRef.current?.fitNodesInView(nodeIds, opts),
zoomIn: () => controlsRef.current?.zoomIn(),
zoomOut: () => controlsRef.current?.zoomOut(),
panLeft: () => controlsRef.current?.panLeft(),
diff --git a/src/GraphScene.tsx b/src/GraphScene.tsx
index 845ec118..9051bd6c 100644
--- a/src/GraphScene.tsx
+++ b/src/GraphScene.tsx
@@ -30,7 +30,11 @@ import {
Edges,
Node
} from './symbols';
-import { CenterNodesParams, useCenterGraph } from './CameraControls';
+import {
+ CenterNodesParams,
+ FitNodesParams,
+ useCenterGraph
+} from './CameraControls';
import { LabelVisibilityType } from './utils';
import { useStore } from './store';
import Graph from 'graphology';
@@ -266,7 +270,7 @@ export interface GraphSceneRef {
* @param nodeIds - An array of node IDs to center the graph on. If this parameter is omitted,
* the graph will be centered on all nodes.
*
- * @param centerOnlyIfNodesNotInView - A boolean flag that determines whether the graph should
+ * @param opts.centerOnlyIfNodesNotInView - A boolean flag that determines whether the graph should
* only be centered if the nodes specified by `ids` are not currently in view. If this
* parameter is `true`, the graph will only be re-centered if one or more of the nodes
* specified by `ids` are not currently in view. If this parameter is
@@ -275,6 +279,21 @@ export interface GraphSceneRef {
*/
centerGraph: (nodeIds?: string[], opts?: CenterNodesParams) => void;
+ /**
+ * Fit all the given nodes into view of the camera.
+ *
+ * @param nodeIds - An array of node IDs to fit the view on. If this parameter is omitted,
+ * the view will fit to all nodes.
+ *
+ * @param opts.fitOnlyIfNodesNotInView - A boolean flag that determines whether the view should
+ * only be fit if the nodes specified by `ids` are not currently in view. If this
+ * parameter is `true`, the view will only be fit if one or more of the nodes
+ * specified by `ids` are not currently visible in the viewport. If this parameter is
+ * `false` or omitted, the view will be fit regardless of whether the nodes
+ * are currently in view.
+ */
+ fitNodesInView: (nodeIds?: string[], opts?: FitNodesParams) => void;
+
/**
* Calls render scene on the graph. this is useful when you want to manually render the graph
* for things like screenshots.
@@ -338,21 +357,23 @@ export const GraphScene: FC }> =
const clusters = useStore(state => [...state.clusters.values()]);
// Center the graph on the nodes
- const { centerNodesById, isCentered } = useCenterGraph({
- animated,
- disabled,
- layoutType
- });
+ const { centerNodesById, fitNodesInViewById, isCentered } =
+ useCenterGraph({
+ animated,
+ disabled,
+ layoutType
+ });
// Let's expose some helper methods
useImperativeHandle(
ref,
() => ({
centerGraph: centerNodesById,
+ fitNodesInView: fitNodesInViewById,
graph,
renderScene: () => gl.render(scene, camera)
}),
- [centerNodesById, graph, gl, scene, camera]
+ [centerNodesById, fitNodesInViewById, graph, gl, scene, camera]
);
const nodeComponents = useMemo(
diff --git a/src/selection/useSelection.ts b/src/selection/useSelection.ts
index bb66de67..d87313d0 100644
--- a/src/selection/useSelection.ts
+++ b/src/selection/useSelection.ts
@@ -267,8 +267,8 @@ export const useSelection = ({
pathSelectionType
);
- ref.current?.centerGraph([data.id, ...adjacents], {
- centerOnlyIfNodesNotInView: true
+ ref.current.fitNodesInView([data.id, ...adjacents], {
+ fitOnlyIfNodesNotInView: true
});
}
},
@@ -352,9 +352,7 @@ export const useSelection = ({
throw new Error('No ref found for the graph canvas.');
}
- ref.current?.centerGraph([], {
- centerOnlyIfNodesNotInView: true
- });
+ ref.current.fitNodesInView([], { fitOnlyIfNodesNotInView: true });
}
}
},