From 83362c187cffb01c7a4c04596a06ce6b2c263289 Mon Sep 17 00:00:00 2001 From: Mahdi Esbati Date: Sun, 21 Nov 2021 13:24:03 +0330 Subject: [PATCH 1/3] implement smooth transitions for useZoomPanHelper using d3 --- src/hooks/useZoomPanHelper.ts | 30 +++++++++++++++++++----------- src/types/index.ts | 14 +++++++------- 2 files changed, 26 insertions(+), 18 deletions(-) diff --git a/src/hooks/useZoomPanHelper.ts b/src/hooks/useZoomPanHelper.ts index f5b91403..1529474d 100644 --- a/src/hooks/useZoomPanHelper.ts +++ b/src/hooks/useZoomPanHelper.ts @@ -1,5 +1,6 @@ import { useMemo } from 'react'; import { zoomIdentity } from 'd3-zoom'; +import { Selection as D3Selection } from 'd3'; import { useStoreState, useStore } from '../store/hooks'; import { getRectOfNodes, pointToRendererPoint, getTransformForBounds } from '../utils/graph'; @@ -19,6 +20,10 @@ const initialZoomPanHelper: ZoomPanHelperFunctions = { initialized: false, }; +const getTransition = (selection: D3Selection, duration: number = 300) => { + return selection.transition().duration(duration); +}; + const useZoomPanHelper = (): ZoomPanHelperFunctions => { const store = useStore(); const d3Zoom = useStoreState((s) => s.d3Zoom); @@ -27,15 +32,19 @@ const useZoomPanHelper = (): ZoomPanHelperFunctions => { const zoomPanHelperFunctions = useMemo(() => { if (d3Selection && d3Zoom) { return { - zoomIn: () => d3Zoom.scaleBy(d3Selection, 1.2), - zoomOut: () => d3Zoom.scaleBy(d3Selection, 1 / 1.2), - zoomTo: (zoomLevel: number) => d3Zoom.scaleTo(d3Selection, zoomLevel), + zoomIn: (duration?: number) => d3Zoom.scaleBy(getTransition(d3Selection, duration), 1.2), + zoomOut: (duration?: number) => d3Zoom.scaleBy(getTransition(d3Selection, duration), 1 / 1.2), + zoomTo: (zoomLevel: number, duration?: number) => + d3Zoom.scaleTo(getTransition(d3Selection, duration), zoomLevel), transform: (transform: FlowTransform) => { 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(); if (!nodes.length) { @@ -53,24 +62,23 @@ const useZoomPanHelper = (): ZoomPanHelperFunctions => { ); 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 nextZoom = typeof zoom !== 'undefined' ? zoom : maxZoom; const centerX = width / 2 - x * nextZoom; const centerY = height / 2 - y * nextZoom; const transform = zoomIdentity.translate(centerX, centerY).scale(nextZoom); - - d3Zoom.transform(d3Selection, transform); + d3Zoom.transform(getTransition(d3Selection, duration), transform); }, - fitBounds: (bounds: Rect, padding = DEFAULT_PADDING) => { + fitBounds: (bounds: Rect, padding = DEFAULT_PADDING, duration?: number) => { const { width, height, minZoom, maxZoom } = store.getState(); const [x, y, zoom] = getTransformForBounds(bounds, width, height, minZoom, maxZoom, padding); const transform = zoomIdentity.translate(x, y).scale(zoom); - d3Zoom.transform(d3Selection, transform); + d3Zoom.transform(getTransition(d3Selection, duration), transform); }, project: (position: XYPosition) => { const { transform, snapToGrid, snapGrid } = store.getState(); diff --git a/src/types/index.ts b/src/types/index.ts index 8bb678ea..c74573fa 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -262,7 +262,7 @@ export type FlowExportObject = { zoom: number; }; -export type FitViewFunc = (fitViewOptions?: FitViewParams) => void; +export type FitViewFunc = (fitViewOptions?: FitViewParams, duration?: number) => void; export type ProjectFunc = (position: XYPosition) => XYPosition; export type ToObjectFunc = () => FlowExportObject; @@ -372,13 +372,13 @@ export enum PanOnScrollMode { } export interface ZoomPanHelperFunctions { - zoomIn: () => void; - zoomOut: () => void; - zoomTo: (zoomLevel: number) => void; - transform: (transform: FlowTransform) => void; + zoomIn: (duration?: number) => void; + zoomOut: (duration?: number) => void; + zoomTo: (zoomLevel: number, duration?: number) => void; + transform: (transform: FlowTransform, duration?: number) => void; fitView: FitViewFunc; - setCenter: (x: number, y: number, zoom?: number) => void; - fitBounds: (bounds: Rect, padding?: number) => void; + setCenter: (x: number, y: number, zoom?: number, duration?: number) => void; + fitBounds: (bounds: Rect, padding?: number, duration?: number) => void; project: (position: XYPosition) => XYPosition; initialized: boolean; } From 41612a0ba9bc9227881cb493d13050e91484bca5 Mon Sep 17 00:00:00 2001 From: Mahdi Esbati Date: Sun, 21 Nov 2021 13:26:08 +0330 Subject: [PATCH 2/3] add the corresponding examples for d3 smooth transitions --- example/src/UseZoomPanHelper/index.tsx | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/example/src/UseZoomPanHelper/index.tsx b/example/src/UseZoomPanHelper/index.tsx index 94ff30f8..a362eb06 100644 --- a/example/src/UseZoomPanHelper/index.tsx +++ b/example/src/UseZoomPanHelper/index.tsx @@ -29,7 +29,7 @@ const UseZoomPanHelperFlow = () => { const [elements, setElements] = useState(initialElements); const onElementsRemove = (elementsToRemove: Elements) => setElements((els) => removeElements(elementsToRemove, els)); const onConnect = (params: Connection | Edge) => setElements((els) => addEdge(params, els)); - const { project } = useZoomPanHelper(); + const { project, setCenter, zoomIn, zoomOut } = useZoomPanHelper(); const onPaneClick = useCallback( (evt) => { @@ -48,8 +48,26 @@ const UseZoomPanHelperFlow = () => { [project] ); + const onElementClick = useCallback( + (_, element) => { + const { x, y } = element.position; + setCenter(x, y, 1); + }, + [setCenter] + ); + return ( - + +
+ + +
From 7dc83114766dfe4be7a098f81ce5c20549606ee9 Mon Sep 17 00:00:00 2001 From: Mahdi Date: Fri, 3 Dec 2021 00:51:26 +0330 Subject: [PATCH 3/3] set the default animation duration to 0ms --- src/hooks/useZoomPanHelper.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hooks/useZoomPanHelper.ts b/src/hooks/useZoomPanHelper.ts index 1529474d..1502da65 100644 --- a/src/hooks/useZoomPanHelper.ts +++ b/src/hooks/useZoomPanHelper.ts @@ -20,7 +20,7 @@ const initialZoomPanHelper: ZoomPanHelperFunctions = { initialized: false, }; -const getTransition = (selection: D3Selection, duration: number = 300) => { +const getTransition = (selection: D3Selection, duration: number = 0) => { return selection.transition().duration(duration); };