feat(core): add ease option to viewport transform functions (#1848)
* chore(core): update d3-deps Signed-off-by: braks <78412429+bcakmakoglu@users.noreply.github.com> * feat(core): add ease option to viewport transform functions Signed-off-by: braks <78412429+bcakmakoglu@users.noreply.github.com> * chore(changeset): add * chore: cleanup Signed-off-by: braks <78412429+bcakmakoglu@users.noreply.github.com> --------- Signed-off-by: braks <78412429+bcakmakoglu@users.noreply.github.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { zoomIdentity } from 'd3-zoom'
|
||||
import { computed } from 'vue'
|
||||
import type { D3Selection, GraphNode, Project, State, ViewportFunctions } from '../types'
|
||||
import type { D3Selection, GraphNode, Project, State, TransitionOptions, ViewportFunctions } from '../types'
|
||||
import { clampPosition, getRectOfNodes, getTransformForBounds, pointToRendererPoint, rendererPointToPoint, warn } from '../utils'
|
||||
|
||||
export interface ViewportHelper extends ViewportFunctions {
|
||||
@@ -11,6 +11,10 @@ export interface ViewportHelper extends ViewportFunctions {
|
||||
|
||||
const DEFAULT_PADDING = 0.1
|
||||
|
||||
// taken from d3-ease: https://github.com/d3/d3-ease/blob/main/src/cubic.js
|
||||
// eslint-disable-next-line no-cond-assign
|
||||
const defaultEase = (t: number) => ((t *= 2) <= 1 ? t * t * t : (t -= 2) * t * t + 2) / 2
|
||||
|
||||
function noop() {
|
||||
warn('Viewport not initialized yet.')
|
||||
|
||||
@@ -41,11 +45,11 @@ const initialViewportHelper: ViewportHelper = {
|
||||
* @param state
|
||||
*/
|
||||
export function useViewportHelper(state: State) {
|
||||
function zoom(scale: number, duration?: number) {
|
||||
function zoom(scale: number, transitionOptions?: TransitionOptions) {
|
||||
return new Promise<boolean>((resolve) => {
|
||||
if (state.d3Selection && state.d3Zoom) {
|
||||
state.d3Zoom.scaleBy(
|
||||
transition(state.d3Selection, duration, () => {
|
||||
getD3Transition(state.d3Selection, transitionOptions?.duration, transitionOptions?.ease, () => {
|
||||
resolve(true)
|
||||
}),
|
||||
scale,
|
||||
@@ -56,7 +60,7 @@ export function useViewportHelper(state: State) {
|
||||
})
|
||||
}
|
||||
|
||||
function transformViewport(x: number, y: number, zoom: number, duration?: number) {
|
||||
function transformViewport(x: number, y: number, zoom: number, transitionOptions?: TransitionOptions) {
|
||||
return new Promise<boolean>((resolve) => {
|
||||
// enforce translate extent
|
||||
const { x: clampedX, y: clampedY } = clampPosition({ x: -x, y: -y }, state.translateExtent)
|
||||
@@ -65,7 +69,7 @@ export function useViewportHelper(state: State) {
|
||||
|
||||
if (state.d3Selection && state.d3Zoom) {
|
||||
state.d3Zoom.transform(
|
||||
transition(state.d3Selection, duration, () => {
|
||||
getD3Transition(state.d3Selection, transitionOptions?.duration, transitionOptions?.ease, () => {
|
||||
resolve(true)
|
||||
}),
|
||||
nextTransform,
|
||||
@@ -87,16 +91,16 @@ export function useViewportHelper(state: State) {
|
||||
viewportInitialized: true,
|
||||
// todo: allow passing scale as option
|
||||
zoomIn: (options) => {
|
||||
return zoom(1.2, options?.duration)
|
||||
return zoom(1.2, options)
|
||||
},
|
||||
zoomOut: (options) => {
|
||||
return zoom(1 / 1.2, options?.duration)
|
||||
return zoom(1 / 1.2, options)
|
||||
},
|
||||
zoomTo: (zoomLevel, options) => {
|
||||
return new Promise<boolean>((resolve) => {
|
||||
if (state.d3Selection && state.d3Zoom) {
|
||||
state.d3Zoom.scaleTo(
|
||||
transition(state.d3Selection, options?.duration, () => {
|
||||
getD3Transition(state.d3Selection, options?.duration, options?.ease, () => {
|
||||
resolve(true)
|
||||
}),
|
||||
zoomLevel,
|
||||
@@ -107,10 +111,10 @@ export function useViewportHelper(state: State) {
|
||||
})
|
||||
},
|
||||
setViewport: (transform, options) => {
|
||||
return transformViewport(transform.x, transform.y, transform.zoom, options?.duration)
|
||||
return transformViewport(transform.x, transform.y, transform.zoom, options)
|
||||
},
|
||||
setTransform: (transform, options) => {
|
||||
return transformViewport(transform.x, transform.y, transform.zoom, options?.duration)
|
||||
return transformViewport(transform.x, transform.y, transform.zoom, options)
|
||||
},
|
||||
getViewport: () => ({
|
||||
x: state.viewport.x,
|
||||
@@ -158,14 +162,14 @@ export function useViewportHelper(state: State) {
|
||||
options.offset,
|
||||
)
|
||||
|
||||
return transformViewport(x, y, zoom, options?.duration)
|
||||
return transformViewport(x, y, zoom, options)
|
||||
},
|
||||
setCenter: (x, y, options) => {
|
||||
const nextZoom = typeof options?.zoom !== 'undefined' ? options.zoom : state.maxZoom
|
||||
const centerX = state.dimensions.width / 2 - x * nextZoom
|
||||
const centerY = state.dimensions.height / 2 - y * nextZoom
|
||||
|
||||
return transformViewport(centerX, centerY, nextZoom, options?.duration)
|
||||
return transformViewport(centerX, centerY, nextZoom, options)
|
||||
},
|
||||
fitBounds: (bounds, options = { padding: DEFAULT_PADDING }) => {
|
||||
const { x, y, zoom } = getTransformForBounds(
|
||||
@@ -177,7 +181,7 @@ export function useViewportHelper(state: State) {
|
||||
options.padding,
|
||||
)
|
||||
|
||||
return transformViewport(x, y, zoom, options?.duration)
|
||||
return transformViewport(x, y, zoom, options)
|
||||
},
|
||||
project: (position) => pointToRendererPoint(position, state.viewport, state.snapToGrid, state.snapGrid),
|
||||
screenToFlowCoordinate: (position) => {
|
||||
@@ -212,6 +216,12 @@ export function useViewportHelper(state: State) {
|
||||
})
|
||||
}
|
||||
|
||||
function transition(selection: D3Selection, ms = 0, onEnd: () => void) {
|
||||
return (selection as any).transition().duration(ms).on('end', onEnd)
|
||||
export function getD3Transition(selection: D3Selection, duration = 0, ease = defaultEase, onEnd = () => {}) {
|
||||
const hasDuration = typeof duration === 'number' && duration > 0
|
||||
|
||||
if (!hasDuration) {
|
||||
onEnd()
|
||||
}
|
||||
|
||||
return hasDuration ? selection.transition().duration(duration).ease(ease).on('end', onEnd) : selection
|
||||
}
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
import type { Selection } from 'd3-selection'
|
||||
|
||||
// eslint-disable-next-line unused-imports/no-unused-imports,@typescript-eslint/no-unused-vars -- this is needed for the Selection type to include the transition function :/
|
||||
import type { Transition } from 'd3-transition'
|
||||
import type { ZoomBehavior } from 'd3-zoom'
|
||||
|
||||
import type { Rect, XYPosition } from './flow'
|
||||
|
||||
export type D3Zoom = ZoomBehavior<HTMLDivElement, unknown>
|
||||
export type D3Selection = Selection<HTMLDivElement, any, any, any>
|
||||
export type D3Selection = Selection<HTMLDivElement, unknown, null, undefined>
|
||||
export type D3ZoomHandler = (this: HTMLDivElement, event: any, d: unknown) => void
|
||||
|
||||
export enum PanOnScrollMode {
|
||||
@@ -14,6 +18,7 @@ export enum PanOnScrollMode {
|
||||
|
||||
export interface TransitionOptions {
|
||||
duration?: number
|
||||
ease?: (t: number) => number
|
||||
}
|
||||
|
||||
export type FitViewParams = {
|
||||
|
||||
Reference in New Issue
Block a user