Merge branch 'main' of github.com:wbkd/react-flow

This commit is contained in:
moklick
2021-12-02 22:28:35 +01:00
3 changed files with 46 additions and 20 deletions
+20 -2
View File
@@ -29,7 +29,7 @@ const UseZoomPanHelperFlow = () => {
const [elements, setElements] = useState<Elements>(initialElements); const [elements, setElements] = useState<Elements>(initialElements);
const onElementsRemove = (elementsToRemove: Elements) => setElements((els) => removeElements(elementsToRemove, els)); const onElementsRemove = (elementsToRemove: Elements) => setElements((els) => removeElements(elementsToRemove, els));
const onConnect = (params: Connection | Edge) => setElements((els) => addEdge(params, els)); const onConnect = (params: Connection | Edge) => setElements((els) => addEdge(params, els));
const { project } = useZoomPanHelper(); const { project, setCenter, zoomIn, zoomOut } = useZoomPanHelper();
const onPaneClick = useCallback( const onPaneClick = useCallback(
(evt) => { (evt) => {
@@ -48,8 +48,26 @@ const UseZoomPanHelperFlow = () => {
[project] [project]
); );
const onElementClick = useCallback(
(_, element) => {
const { x, y } = element.position;
setCenter(x, y, 1);
},
[setCenter]
);
return ( return (
<ReactFlow elements={elements} onElementsRemove={onElementsRemove} onConnect={onConnect} onPaneClick={onPaneClick}> <ReactFlow
elements={elements}
onElementClick={onElementClick}
onElementsRemove={onElementsRemove}
onConnect={onConnect}
onPaneClick={onPaneClick}
>
<div style={{ position: 'absolute', left: 0, top: 0, zIndex: 100 }}>
<button onClick={() => zoomIn(1200)}>zoomIn</button>
<button onClick={() => zoomOut(0)}>zoomOut</button>
</div>
<Background /> <Background />
<MiniMap /> <MiniMap />
</ReactFlow> </ReactFlow>
+19 -11
View File
@@ -1,5 +1,6 @@
import { useMemo } from 'react'; import { useMemo } from 'react';
import { zoomIdentity } from 'd3-zoom'; import { zoomIdentity } from 'd3-zoom';
import { Selection as D3Selection } from 'd3';
import { useStoreState, useStore } from '../store/hooks'; import { useStoreState, useStore } from '../store/hooks';
import { getRectOfNodes, pointToRendererPoint, getTransformForBounds } from '../utils/graph'; import { getRectOfNodes, pointToRendererPoint, getTransformForBounds } from '../utils/graph';
@@ -19,6 +20,10 @@ const initialZoomPanHelper: ZoomPanHelperFunctions = {
initialized: false, initialized: false,
}; };
const getTransition = (selection: D3Selection<Element, unknown, null, undefined>, duration: number = 0) => {
return selection.transition().duration(duration);
};
const useZoomPanHelper = (): ZoomPanHelperFunctions => { const useZoomPanHelper = (): ZoomPanHelperFunctions => {
const store = useStore(); const store = useStore();
const d3Zoom = useStoreState((s) => s.d3Zoom); const d3Zoom = useStoreState((s) => s.d3Zoom);
@@ -27,15 +32,19 @@ const useZoomPanHelper = (): ZoomPanHelperFunctions => {
const zoomPanHelperFunctions = useMemo<ZoomPanHelperFunctions>(() => { const zoomPanHelperFunctions = useMemo<ZoomPanHelperFunctions>(() => {
if (d3Selection && d3Zoom) { if (d3Selection && d3Zoom) {
return { return {
zoomIn: () => d3Zoom.scaleBy(d3Selection, 1.2), zoomIn: (duration?: number) => d3Zoom.scaleBy(getTransition(d3Selection, duration), 1.2),
zoomOut: () => d3Zoom.scaleBy(d3Selection, 1 / 1.2), zoomOut: (duration?: number) => d3Zoom.scaleBy(getTransition(d3Selection, duration), 1 / 1.2),
zoomTo: (zoomLevel: number) => d3Zoom.scaleTo(d3Selection, zoomLevel), zoomTo: (zoomLevel: number, duration?: number) =>
d3Zoom.scaleTo(getTransition(d3Selection, duration), zoomLevel),
transform: (transform: FlowTransform) => { transform: (transform: FlowTransform) => {
const nextTransform = zoomIdentity.translate(transform.x, transform.y).scale(transform.zoom); const nextTransform = zoomIdentity.translate(transform.x, transform.y).scale(transform.zoom);
d3Zoom.transform(d3Selection, nextTransform); d3Zoom.transform(getTransition(d3Selection), nextTransform);
}, },
fitView: (options: FitViewParams = { padding: DEFAULT_PADDING, includeHiddenNodes: false }) => { fitView: (
options: FitViewParams = { padding: DEFAULT_PADDING, includeHiddenNodes: false },
duration?: number
) => {
const { nodes, width, height, minZoom, maxZoom } = store.getState(); const { nodes, width, height, minZoom, maxZoom } = store.getState();
if (!nodes.length) { if (!nodes.length) {
@@ -53,24 +62,23 @@ const useZoomPanHelper = (): ZoomPanHelperFunctions => {
); );
const transform = zoomIdentity.translate(x, y).scale(zoom); const transform = zoomIdentity.translate(x, y).scale(zoom);
d3Zoom.transform(d3Selection, transform); d3Zoom.transform(getTransition(d3Selection, duration), transform);
}, },
setCenter: (x: number, y: number, zoom?: number) => { setCenter: (x: number, y: number, zoom?: number, duration?: number) => {
const { width, height, maxZoom } = store.getState(); const { width, height, maxZoom } = store.getState();
const nextZoom = typeof zoom !== 'undefined' ? zoom : maxZoom; const nextZoom = typeof zoom !== 'undefined' ? zoom : maxZoom;
const centerX = width / 2 - x * nextZoom; const centerX = width / 2 - x * nextZoom;
const centerY = height / 2 - y * nextZoom; const centerY = height / 2 - y * nextZoom;
const transform = zoomIdentity.translate(centerX, centerY).scale(nextZoom); const transform = zoomIdentity.translate(centerX, centerY).scale(nextZoom);
d3Zoom.transform(getTransition(d3Selection, duration), transform);
d3Zoom.transform(d3Selection, transform);
}, },
fitBounds: (bounds: Rect, padding = DEFAULT_PADDING) => { fitBounds: (bounds: Rect, padding = DEFAULT_PADDING, duration?: number) => {
const { width, height, minZoom, maxZoom } = store.getState(); const { width, height, minZoom, maxZoom } = store.getState();
const [x, y, zoom] = getTransformForBounds(bounds, width, height, minZoom, maxZoom, padding); const [x, y, zoom] = getTransformForBounds(bounds, width, height, minZoom, maxZoom, padding);
const transform = zoomIdentity.translate(x, y).scale(zoom); const transform = zoomIdentity.translate(x, y).scale(zoom);
d3Zoom.transform(d3Selection, transform); d3Zoom.transform(getTransition(d3Selection, duration), transform);
}, },
project: (position: XYPosition) => { project: (position: XYPosition) => {
const { transform, snapToGrid, snapGrid } = store.getState(); const { transform, snapToGrid, snapGrid } = store.getState();
+7 -7
View File
@@ -262,7 +262,7 @@ export type FlowExportObject<T = any> = {
zoom: number; zoom: number;
}; };
export type FitViewFunc = (fitViewOptions?: FitViewParams) => void; export type FitViewFunc = (fitViewOptions?: FitViewParams, duration?: number) => void;
export type ProjectFunc = (position: XYPosition) => XYPosition; export type ProjectFunc = (position: XYPosition) => XYPosition;
export type ToObjectFunc<T = any> = () => FlowExportObject<T>; export type ToObjectFunc<T = any> = () => FlowExportObject<T>;
@@ -372,13 +372,13 @@ export enum PanOnScrollMode {
} }
export interface ZoomPanHelperFunctions { export interface ZoomPanHelperFunctions {
zoomIn: () => void; zoomIn: (duration?: number) => void;
zoomOut: () => void; zoomOut: (duration?: number) => void;
zoomTo: (zoomLevel: number) => void; zoomTo: (zoomLevel: number, duration?: number) => void;
transform: (transform: FlowTransform) => void; transform: (transform: FlowTransform, duration?: number) => void;
fitView: FitViewFunc; fitView: FitViewFunc;
setCenter: (x: number, y: number, zoom?: number) => void; setCenter: (x: number, y: number, zoom?: number, duration?: number) => void;
fitBounds: (bounds: Rect, padding?: number) => void; fitBounds: (bounds: Rect, padding?: number, duration?: number) => void;
project: (position: XYPosition) => XYPosition; project: (position: XYPosition) => XYPosition;
initialized: boolean; initialized: boolean;
} }