diff --git a/examples/react/src/examples/UseReactFlow/index.tsx b/examples/react/src/examples/UseReactFlow/index.tsx index cf6ae59f..3e1d8f92 100644 --- a/examples/react/src/examples/UseReactFlow/index.tsx +++ b/examples/react/src/examples/UseReactFlow/index.tsx @@ -170,7 +170,6 @@ const UseZoomPanHelperFlow = () => { onConnect={onConnect} onPaneClick={onPaneClick} fitView - fitViewOptions={{ duration: 1200, padding: 0.2 }} maxZoom={Infinity} > @@ -183,7 +182,16 @@ const UseZoomPanHelperFlow = () => { console.log('fit view success'); }} > - fitView + fitView default + + diff --git a/examples/svelte/src/routes/examples/usesvelteflow/Flow.svelte b/examples/svelte/src/routes/examples/usesvelteflow/Flow.svelte index aa37a49a..cab0bdde 100644 --- a/examples/svelte/src/routes/examples/usesvelteflow/Flow.svelte +++ b/examples/svelte/src/routes/examples/usesvelteflow/Flow.svelte @@ -44,7 +44,7 @@
- + diff --git a/examples/svelte/src/routes/examples/usesvelteflow/Sidebar.svelte b/examples/svelte/src/routes/examples/usesvelteflow/Sidebar.svelte index c64131f0..6c56e10a 100644 --- a/examples/svelte/src/routes/examples/usesvelteflow/Sidebar.svelte +++ b/examples/svelte/src/routes/examples/usesvelteflow/Sidebar.svelte @@ -30,7 +30,8 @@ - + + diff --git a/packages/react/src/hooks/useViewportHelper.ts b/packages/react/src/hooks/useViewportHelper.ts index a88bd3fc..3f115152 100644 --- a/packages/react/src/hooks/useViewportHelper.ts +++ b/packages/react/src/hooks/useViewportHelper.ts @@ -53,7 +53,7 @@ const useViewportHelper = (): ViewportHelperFunctions => { y: viewport.y ?? tY, zoom: viewport.zoom ?? tZoom, }, - { duration: options?.duration } + options ); return Promise.resolve(true); @@ -78,7 +78,7 @@ const useViewportHelper = (): ViewportHelperFunctions => { y: centerY, zoom: nextZoom, }, - { duration: options?.duration } + { duration: options?.duration, ease: options?.ease } ); return Promise.resolve(true); @@ -91,7 +91,7 @@ const useViewportHelper = (): ViewportHelperFunctions => { return Promise.resolve(false); } - await panZoom.setViewport(viewport, { duration: options?.duration }); + await panZoom.setViewport(viewport, { duration: options?.duration, ease: options?.ease }); return Promise.resolve(true); }, diff --git a/packages/react/src/types/general.ts b/packages/react/src/types/general.ts index 98302a86..80acc45c 100644 --- a/packages/react/src/types/general.ts +++ b/packages/react/src/types/general.ts @@ -149,6 +149,7 @@ export type ViewportHelperFunctions = { * * @param viewport - the viewport to set * @param options.duration - optional duration. If set, a transition will be applied + * @param options.ease - optional ease function. */ setViewport: SetViewport; /** @@ -164,6 +165,8 @@ export type ViewportHelperFunctions = { * @param x - x position * @param y - y position * @param options.zoom - optional zoom + * @param options.duration - optional duration. If set, a transition will be applied + * @param options.ease - optional ease function. */ setCenter: SetCenter; /** @@ -173,6 +176,8 @@ export type ViewportHelperFunctions = { * * @param bounds - the bounds ({ x: number, y: number, width: number, height: number }) to fit the view to * @param options.padding - optional padding + * @param options.duration - optional duration. If set, a transition will be applied + * @param options.ease - optional ease function. */ fitBounds: FitBounds; /** diff --git a/packages/svelte/src/lib/hooks/useSvelteFlow.svelte.ts b/packages/svelte/src/lib/hooks/useSvelteFlow.svelte.ts index e3dcf1f4..d56b3992 100644 --- a/packages/svelte/src/lib/hooks/useSvelteFlow.svelte.ts +++ b/packages/svelte/src/lib/hooks/useSvelteFlow.svelte.ts @@ -360,7 +360,7 @@ export function useSvelteFlow = { minZoom?: number; maxZoom?: number; duration?: number; + ease?: (t: number) => number; nodes?: (NodeType | { id: string })[]; }; @@ -173,6 +174,7 @@ export enum PanOnScrollMode { */ export type ViewportHelperFunctionOptions = { duration?: number; + ease?: (t: number) => number; }; export type SetCenterOptions = ViewportHelperFunctionOptions & { diff --git a/packages/system/src/types/panzoom.ts b/packages/system/src/types/panzoom.ts index a2577505..1738bc50 100644 --- a/packages/system/src/types/panzoom.ts +++ b/packages/system/src/types/panzoom.ts @@ -20,6 +20,7 @@ export type PanZoomParams = { export type PanZoomTransformOptions = { duration?: number; + ease?: (t: number) => number; }; export type OnPanZoom = (event: MouseEvent | TouchEvent | null, viewport: Viewport) => void; diff --git a/packages/system/src/utils/graph.ts b/packages/system/src/utils/graph.ts index 9bd7beff..386eac79 100644 --- a/packages/system/src/utils/graph.ts +++ b/packages/system/src/utils/graph.ts @@ -378,7 +378,7 @@ export async function fitViewport< options?.padding ?? 0.1 ); - await panZoom.setViewport(viewport, { duration: options?.duration }); + await panZoom.setViewport(viewport, { duration: options?.duration, ease: options?.ease }); return Promise.resolve(true); } diff --git a/packages/system/src/xypanzoom/XYPanZoom.ts b/packages/system/src/xypanzoom/XYPanZoom.ts index 93bc79f8..b30ec8f5 100644 --- a/packages/system/src/xypanzoom/XYPanZoom.ts +++ b/packages/system/src/xypanzoom/XYPanZoom.ts @@ -19,6 +19,7 @@ import { createZoomOnScrollHandler, } from './eventhandler'; import { createFilter } from './filter'; +import { transition } from 'd3-transition'; export type ZoomPanValues = { isZoomingOrPanning: boolean; @@ -79,7 +80,7 @@ export function XYPanZoom({ if (d3Selection) { return new Promise((resolve) => { d3ZoomInstance?.transform( - getD3Transition(d3Selection, options?.duration, () => resolve(true)), + getD3Transition(d3Selection, options?.duration, options?.ease, () => resolve(true)), transform ); }); @@ -114,22 +115,22 @@ export function XYPanZoom({ const wheelHandler = isPanOnScroll ? createPanOnScrollHandler({ - zoomPanValues, - noWheelClassName, - d3Selection, - d3Zoom: d3ZoomInstance, - panOnScrollMode, - panOnScrollSpeed, - zoomOnPinch, - onPanZoomStart, - onPanZoom, - onPanZoomEnd, - }) + zoomPanValues, + noWheelClassName, + d3Selection, + d3Zoom: d3ZoomInstance, + panOnScrollMode, + panOnScrollSpeed, + zoomOnPinch, + onPanZoomStart, + onPanZoom, + onPanZoomEnd, + }) : createZoomOnScrollHandler({ - noWheelClassName, - preventScrolling, - d3ZoomHandler, - }); + noWheelClassName, + preventScrolling, + d3ZoomHandler, + }); d3Selection.on('wheel.zoom', wheelHandler, { passive: false }); @@ -243,7 +244,7 @@ export function XYPanZoom({ if (d3Selection) { return new Promise((resolve) => { d3ZoomInstance?.scaleTo( - getD3Transition(d3Selection, options?.duration, () => resolve(true)), + getD3Transition(d3Selection, options?.duration, options?.ease, () => resolve(true)), zoom ); }); @@ -256,7 +257,7 @@ export function XYPanZoom({ if (d3Selection) { return new Promise((resolve) => { d3ZoomInstance?.scaleBy( - getD3Transition(d3Selection, options?.duration, () => resolve(true)), + getD3Transition(d3Selection, options?.duration, options?.ease, () => resolve(true)), factor ); }); diff --git a/packages/system/src/xypanzoom/utils.ts b/packages/system/src/xypanzoom/utils.ts index fbd0324d..b6fd71f3 100644 --- a/packages/system/src/xypanzoom/utils.ts +++ b/packages/system/src/xypanzoom/utils.ts @@ -1,5 +1,6 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import { type ZoomTransform, zoomIdentity } from 'd3-zoom'; +import { transition } from 'd3-transition'; import { type D3SelectionInstance, type Viewport } from '../types'; import { isMacOs } from '../utils'; @@ -21,14 +22,19 @@ export const isWrappedWithClass = (event: any, className: string | undefined) => export const isRightClickPan = (panOnDrag: boolean | number[], usedButton: number) => usedButton === 2 && Array.isArray(panOnDrag) && panOnDrag.includes(2); -export const getD3Transition = (selection: D3SelectionInstance, duration = 0, onEnd = () => {}) => { +// taken from d3-ease: https://github.com/d3/d3-ease/blob/main/src/cubic.js +const defaultEase = (t: number) => ((t *= 2) <= 1 ? t * t * t : (t -= 2) * t * t + 2) / 2; + +export const getD3Transition = (selection: D3SelectionInstance, duration = 0, ease = defaultEase, onEnd = () => {}) => { const hasDuration = typeof duration === 'number' && duration > 0; if (!hasDuration) { onEnd(); } - return hasDuration ? selection.transition().duration(duration).on('end', onEnd) : selection; + console.log(ease); + + return hasDuration ? selection.transition().duration(duration).ease(ease).on('end', onEnd) : selection; }; export const wheelDelta = (event: any) => {