feat(options): add minZoom, maxZoom, defaultZoom closes #273
This commit is contained in:
@@ -85,6 +85,9 @@ const BasicFlow = () => <ReactFlow elements={elements} />;
|
|||||||
- `onlyRenderVisibleNodes`: default: `true`
|
- `onlyRenderVisibleNodes`: default: `true`
|
||||||
- `isInteractive`: default: `true`. If the graph is not interactive you can't drag any nodes
|
- `isInteractive`: default: `true`. If the graph is not interactive you can't drag any nodes
|
||||||
- `selectNodesOnDrag`: default: `true`
|
- `selectNodesOnDrag`: default: `true`
|
||||||
|
- `minZoom`: default: `0.5`
|
||||||
|
- `maxZoom`: default: `2`
|
||||||
|
- `defaultZoom`: default: `1`
|
||||||
|
|
||||||
## React Flow Instance
|
## React Flow Instance
|
||||||
|
|
||||||
|
|||||||
@@ -127,8 +127,8 @@ describe('Basic Graph Rendering', () => {
|
|||||||
|
|
||||||
cy.window().then((win) => {
|
cy.window().then((win) => {
|
||||||
cy.get('.react-flow__zoompane')
|
cy.get('.react-flow__zoompane')
|
||||||
.trigger('mousedown', { which: 1, view: win })
|
.trigger('mousedown', 'topLeft', { which: 1, view: win })
|
||||||
.trigger('mousemove', newPosition)
|
.trigger('mousemove', 'bottomLeft')
|
||||||
.trigger('mouseup', { force: true, view: win })
|
.trigger('mouseup', { force: true, view: win })
|
||||||
.then(() => {
|
.then(() => {
|
||||||
const styleAfterDrag = Cypress.$('.react-flow__nodes').css('transform');
|
const styleAfterDrag = Cypress.$('.react-flow__nodes').css('transform');
|
||||||
|
|||||||
@@ -48,6 +48,9 @@ const BasicFlow = () => {
|
|||||||
onConnect={onConnect}
|
onConnect={onConnect}
|
||||||
onNodeDragStop={onNodeDragStop}
|
onNodeDragStop={onNodeDragStop}
|
||||||
className="react-flow-basic-example"
|
className="react-flow-basic-example"
|
||||||
|
defaultZoom={1.5}
|
||||||
|
minZoom={0.2}
|
||||||
|
maxZoom={4}
|
||||||
>
|
>
|
||||||
<Background variant="lines" />
|
<Background variant="lines" />
|
||||||
|
|
||||||
|
|||||||
@@ -43,6 +43,9 @@ export interface GraphViewProps {
|
|||||||
onlyRenderVisibleNodes: boolean;
|
onlyRenderVisibleNodes: boolean;
|
||||||
isInteractive: boolean;
|
isInteractive: boolean;
|
||||||
selectNodesOnDrag: boolean;
|
selectNodesOnDrag: boolean;
|
||||||
|
minZoom: number;
|
||||||
|
maxZoom: number;
|
||||||
|
defaultZoom: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const GraphView = memo(
|
const GraphView = memo(
|
||||||
@@ -66,6 +69,9 @@ const GraphView = memo(
|
|||||||
onlyRenderVisibleNodes,
|
onlyRenderVisibleNodes,
|
||||||
isInteractive,
|
isInteractive,
|
||||||
selectNodesOnDrag,
|
selectNodesOnDrag,
|
||||||
|
minZoom,
|
||||||
|
maxZoom,
|
||||||
|
defaultZoom,
|
||||||
}: GraphViewProps) => {
|
}: GraphViewProps) => {
|
||||||
const zoomPane = useRef<HTMLDivElement>(null);
|
const zoomPane = useRef<HTMLDivElement>(null);
|
||||||
const rendererNode = useRef<HTMLDivElement>(null);
|
const rendererNode = useRef<HTMLDivElement>(null);
|
||||||
@@ -79,6 +85,9 @@ const GraphView = memo(
|
|||||||
const setOnConnect = useStoreActions((a) => a.setOnConnect);
|
const setOnConnect = useStoreActions((a) => a.setOnConnect);
|
||||||
const setSnapGrid = useStoreActions((actions) => actions.setSnapGrid);
|
const setSnapGrid = useStoreActions((actions) => actions.setSnapGrid);
|
||||||
const setInteractive = useStoreActions((actions) => actions.setInteractive);
|
const setInteractive = useStoreActions((actions) => actions.setInteractive);
|
||||||
|
const updateTransform = useStoreActions((actions) => actions.updateTransform);
|
||||||
|
const setMinMaxZoom = useStoreActions((actions) => actions.setMinMaxZoom);
|
||||||
|
|
||||||
const selectionKeyPressed = useKeyPress(selectionKeyCode);
|
const selectionKeyPressed = useKeyPress(selectionKeyCode);
|
||||||
const rendererClasses = classnames('react-flow__renderer', { 'is-interactive': isInteractive });
|
const rendererClasses = classnames('react-flow__renderer', { 'is-interactive': isInteractive });
|
||||||
|
|
||||||
@@ -106,6 +115,10 @@ const GraphView = memo(
|
|||||||
setOnConnect(onConnect);
|
setOnConnect(onConnect);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (defaultZoom !== 1) {
|
||||||
|
updateTransform({ x: 0, y: 0, k: defaultZoom });
|
||||||
|
}
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
window.onresize = null;
|
window.onresize = null;
|
||||||
};
|
};
|
||||||
@@ -132,6 +145,10 @@ const GraphView = memo(
|
|||||||
setInteractive(isInteractive);
|
setInteractive(isInteractive);
|
||||||
}, [isInteractive]);
|
}, [isInteractive]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setMinMaxZoom({ minZoom, maxZoom });
|
||||||
|
}, [minZoom, maxZoom]);
|
||||||
|
|
||||||
useGlobalKeyHandler({ onElementsRemove, deleteKeyCode });
|
useGlobalKeyHandler({ onElementsRemove, deleteKeyCode });
|
||||||
useElementUpdater(elements);
|
useElementUpdater(elements);
|
||||||
|
|
||||||
|
|||||||
@@ -54,6 +54,9 @@ export interface ReactFlowProps extends Omit<HTMLAttributes<HTMLDivElement>, 'on
|
|||||||
onlyRenderVisibleNodes: boolean;
|
onlyRenderVisibleNodes: boolean;
|
||||||
isInteractive: boolean;
|
isInteractive: boolean;
|
||||||
selectNodesOnDrag: boolean;
|
selectNodesOnDrag: boolean;
|
||||||
|
minZoom: number;
|
||||||
|
maxZoom: number;
|
||||||
|
defaultZoom: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ReactFlow = ({
|
const ReactFlow = ({
|
||||||
@@ -80,6 +83,9 @@ const ReactFlow = ({
|
|||||||
onlyRenderVisibleNodes,
|
onlyRenderVisibleNodes,
|
||||||
isInteractive,
|
isInteractive,
|
||||||
selectNodesOnDrag,
|
selectNodesOnDrag,
|
||||||
|
minZoom,
|
||||||
|
maxZoom,
|
||||||
|
defaultZoom,
|
||||||
}: ReactFlowProps) => {
|
}: ReactFlowProps) => {
|
||||||
const nodeTypesParsed = useMemo(() => createNodeTypes(nodeTypes), []);
|
const nodeTypesParsed = useMemo(() => createNodeTypes(nodeTypes), []);
|
||||||
const edgeTypesParsed = useMemo(() => createEdgeTypes(edgeTypes), []);
|
const edgeTypesParsed = useMemo(() => createEdgeTypes(edgeTypes), []);
|
||||||
@@ -107,6 +113,9 @@ const ReactFlow = ({
|
|||||||
onlyRenderVisibleNodes={onlyRenderVisibleNodes}
|
onlyRenderVisibleNodes={onlyRenderVisibleNodes}
|
||||||
isInteractive={isInteractive}
|
isInteractive={isInteractive}
|
||||||
selectNodesOnDrag={selectNodesOnDrag}
|
selectNodesOnDrag={selectNodesOnDrag}
|
||||||
|
minZoom={minZoom}
|
||||||
|
maxZoom={maxZoom}
|
||||||
|
defaultZoom={defaultZoom}
|
||||||
/>
|
/>
|
||||||
{onSelectionChange && <SelectionListener onSelectionChange={onSelectionChange} />}
|
{onSelectionChange && <SelectionListener onSelectionChange={onSelectionChange} />}
|
||||||
{children}
|
{children}
|
||||||
@@ -136,6 +145,9 @@ ReactFlow.defaultProps = {
|
|||||||
onlyRenderVisibleNodes: true,
|
onlyRenderVisibleNodes: true,
|
||||||
isInteractive: true,
|
isInteractive: true,
|
||||||
selectNodesOnDrag: true,
|
selectNodesOnDrag: true,
|
||||||
|
minZoom: 0.5,
|
||||||
|
maxZoom: 2,
|
||||||
|
defaultZoom: 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ReactFlow;
|
export default ReactFlow;
|
||||||
|
|||||||
+11
-10
@@ -10,10 +10,6 @@ interface UseD3ZoomParams {
|
|||||||
onMove?: () => void;
|
onMove?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const d3ZoomInstance = zoom()
|
|
||||||
.scaleExtent([0.5, 2])
|
|
||||||
.filter(() => !event.button);
|
|
||||||
|
|
||||||
export default ({ zoomPane, onMove, selectionKeyPressed }: UseD3ZoomParams): void => {
|
export default ({ zoomPane, onMove, selectionKeyPressed }: UseD3ZoomParams): void => {
|
||||||
const transform = useStoreState((s) => s.transform);
|
const transform = useStoreState((s) => s.transform);
|
||||||
const d3Selection = useStoreState((s) => s.d3Selection);
|
const d3Selection = useStoreState((s) => s.d3Selection);
|
||||||
@@ -24,16 +20,21 @@ export default ({ zoomPane, onMove, selectionKeyPressed }: UseD3ZoomParams): voi
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (zoomPane.current) {
|
if (zoomPane.current) {
|
||||||
const selection = select(zoomPane.current).call(d3ZoomInstance);
|
const nextD3ZoomInstance = zoom();
|
||||||
initD3({ zoom: d3ZoomInstance, selection });
|
const selection = select(zoomPane.current).call(nextD3ZoomInstance);
|
||||||
|
initD3({ zoom: nextD3ZoomInstance, selection });
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (!d3Zoom) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (selectionKeyPressed) {
|
if (selectionKeyPressed) {
|
||||||
d3ZoomInstance.on('zoom', null);
|
d3Zoom.on('zoom', null);
|
||||||
} else {
|
} else {
|
||||||
d3ZoomInstance.on('zoom', () => {
|
d3Zoom.on('zoom', () => {
|
||||||
if (event.sourceEvent && event.sourceEvent.target !== zoomPane.current) {
|
if (event.sourceEvent && event.sourceEvent.target !== zoomPane.current) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -53,7 +54,7 @@ export default ({ zoomPane, onMove, selectionKeyPressed }: UseD3ZoomParams): voi
|
|||||||
}
|
}
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
d3ZoomInstance.on('zoom', null);
|
d3Zoom.on('zoom', null);
|
||||||
};
|
};
|
||||||
}, [selectionKeyPressed]);
|
}, [selectionKeyPressed, d3Zoom]);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -47,6 +47,11 @@ type D3Init = {
|
|||||||
selection: D3Selection<Element, unknown, null, undefined>;
|
selection: D3Selection<Element, unknown, null, undefined>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type SetMinMaxZoom = {
|
||||||
|
minZoom: number;
|
||||||
|
maxZoom: number;
|
||||||
|
};
|
||||||
|
|
||||||
type SetSnapGrid = {
|
type SetSnapGrid = {
|
||||||
snapToGrid: boolean;
|
snapToGrid: boolean;
|
||||||
snapGrid: [number, number];
|
snapGrid: [number, number];
|
||||||
@@ -64,6 +69,8 @@ export interface StoreModel {
|
|||||||
d3Zoom: ZoomBehavior<Element, unknown> | null;
|
d3Zoom: ZoomBehavior<Element, unknown> | null;
|
||||||
d3Selection: D3Selection<Element, unknown, null, undefined> | null;
|
d3Selection: D3Selection<Element, unknown, null, undefined> | null;
|
||||||
d3Initialised: boolean;
|
d3Initialised: boolean;
|
||||||
|
minZoom: number;
|
||||||
|
maxZoom: number;
|
||||||
|
|
||||||
nodesSelectionActive: boolean;
|
nodesSelectionActive: boolean;
|
||||||
selectionActive: boolean;
|
selectionActive: boolean;
|
||||||
@@ -104,6 +111,8 @@ export interface StoreModel {
|
|||||||
|
|
||||||
initD3: Action<StoreModel, D3Init>;
|
initD3: Action<StoreModel, D3Init>;
|
||||||
|
|
||||||
|
setMinMaxZoom: Action<StoreModel, SetMinMaxZoom>;
|
||||||
|
|
||||||
setSnapGrid: Action<StoreModel, SetSnapGrid>;
|
setSnapGrid: Action<StoreModel, SetSnapGrid>;
|
||||||
|
|
||||||
setConnectionPosition: Action<StoreModel, XYPosition>;
|
setConnectionPosition: Action<StoreModel, XYPosition>;
|
||||||
@@ -129,6 +138,8 @@ const storeModel: StoreModel = {
|
|||||||
d3Zoom: null,
|
d3Zoom: null,
|
||||||
d3Selection: null,
|
d3Selection: null,
|
||||||
d3Initialised: false,
|
d3Initialised: false,
|
||||||
|
minZoom: 0.5,
|
||||||
|
maxZoom: 2,
|
||||||
|
|
||||||
nodesSelectionActive: false,
|
nodesSelectionActive: false,
|
||||||
selectionActive: false,
|
selectionActive: false,
|
||||||
@@ -326,10 +337,22 @@ const storeModel: StoreModel = {
|
|||||||
|
|
||||||
initD3: action((state, { zoom, selection }) => {
|
initD3: action((state, { zoom, selection }) => {
|
||||||
state.d3Zoom = zoom;
|
state.d3Zoom = zoom;
|
||||||
|
|
||||||
|
state.d3Zoom.scaleExtent([state.minZoom, state.maxZoom]);
|
||||||
|
|
||||||
state.d3Selection = selection;
|
state.d3Selection = selection;
|
||||||
state.d3Initialised = true;
|
state.d3Initialised = true;
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
setMinMaxZoom: action((state, { minZoom, maxZoom }) => {
|
||||||
|
state.minZoom = minZoom;
|
||||||
|
state.maxZoom = maxZoom;
|
||||||
|
|
||||||
|
if (state.d3Zoom) {
|
||||||
|
state.d3Zoom.scaleExtent([minZoom, maxZoom]);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
setConnectionPosition: action((state, position) => {
|
setConnectionPosition: action((state, position) => {
|
||||||
state.connectionPosition = position;
|
state.connectionPosition = position;
|
||||||
}),
|
}),
|
||||||
|
|||||||
Reference in New Issue
Block a user