feat(zoom): Add new zoom pan helper functions + transition for all helpers
Signed-off-by: Braks <78412429+bcakmakoglu@users.noreply.github.com>
This commit is contained in:
@@ -1,76 +1,76 @@
|
|||||||
import { zoomIdentity } from 'd3-zoom'
|
import { zoomIdentity } from 'd3-zoom'
|
||||||
import useStore from './useStore'
|
import useStore from './useStore'
|
||||||
import { getRectOfNodes, pointToRendererPoint, getTransformForBounds } from '~/utils'
|
import { getRectOfNodes, pointToRendererPoint, getTransformForBounds } from '~/utils'
|
||||||
import { FitViewParams, FlowTransform, GraphNode, FlowStore, Rect, UseZoomPanHelper, XYPosition } from '~/types'
|
import { GraphNode, FlowStore, UseZoomPanHelper, D3Selection } from '~/types'
|
||||||
|
|
||||||
const DEFAULT_PADDING = 0.1
|
const DEFAULT_PADDING = 0.1
|
||||||
|
|
||||||
export default (store: FlowStore = useStore()): UseZoomPanHelper => {
|
const transition = (selection: D3Selection, ms = 0) => selection.transition().duration(ms)
|
||||||
return {
|
|
||||||
zoomIn: () => store.d3Selection && store.d3Zoom?.scaleBy(store.d3Selection, 1.2),
|
|
||||||
zoomOut: () => store.d3Selection && store.d3Zoom?.scaleBy(store.d3Selection, 1 / 1.2),
|
|
||||||
zoomTo: (zoomLevel: number) => store.d3Selection && store.d3Zoom?.scaleTo(store.d3Selection, zoomLevel),
|
|
||||||
transform: (transform: FlowTransform) => {
|
|
||||||
const nextTransform = zoomIdentity.translate(transform.x, transform.y).scale(transform.zoom)
|
|
||||||
store.d3Selection && store.d3Zoom?.transform(store.d3Selection, nextTransform)
|
|
||||||
},
|
|
||||||
fitView: (
|
|
||||||
options: FitViewParams = {
|
|
||||||
padding: DEFAULT_PADDING,
|
|
||||||
includeHiddenNodes: false,
|
|
||||||
transitionDuration: 0,
|
|
||||||
},
|
|
||||||
) => {
|
|
||||||
if (!store.getNodes.length) return
|
|
||||||
|
|
||||||
let nodes: GraphNode[] = []
|
export default (store: FlowStore = useStore()): UseZoomPanHelper => ({
|
||||||
if (options.nodes) {
|
zoomIn: (options) => store.d3Selection && store.d3Zoom?.scaleBy(transition(store.d3Selection, options?.duration), 1.2),
|
||||||
nodes = store.nodes.filter((n) => options.nodes?.includes(n.id))
|
zoomOut: (options) => store.d3Selection && store.d3Zoom?.scaleBy(transition(store.d3Selection, options?.duration), 1 / 1.2),
|
||||||
}
|
zoomTo: (zoomLevel, options) =>
|
||||||
if (!nodes || !nodes.length) {
|
store.d3Selection && store.d3Zoom?.scaleTo(transition(store.d3Selection, options?.duration), zoomLevel),
|
||||||
nodes = options.includeHiddenNodes ? store.nodes : store.getNodes
|
setTransform: (transform, options) => {
|
||||||
}
|
const nextTransform = zoomIdentity.translate(transform.x, transform.y).scale(transform.zoom)
|
||||||
const bounds = getRectOfNodes(nodes)
|
store.d3Selection && store.d3Zoom?.transform(transition(store.d3Selection, options?.duration), nextTransform)
|
||||||
const [x, y, zoom] = getTransformForBounds(
|
},
|
||||||
bounds,
|
getTransform: () => ({
|
||||||
store.dimensions.width,
|
x: store.transform[0],
|
||||||
store.dimensions.height,
|
y: store.transform[1],
|
||||||
options.minZoom ?? store.minZoom,
|
zoom: store.transform[2],
|
||||||
options.maxZoom ?? store.maxZoom,
|
}),
|
||||||
options.padding ?? DEFAULT_PADDING,
|
fitView: (
|
||||||
options.offset,
|
options = {
|
||||||
)
|
padding: DEFAULT_PADDING,
|
||||||
const transform = zoomIdentity.translate(x, y).scale(zoom)
|
includeHiddenNodes: false,
|
||||||
|
duration: 0,
|
||||||
store.d3Selection &&
|
|
||||||
store.d3Zoom?.transform(
|
|
||||||
options.transitionDuration
|
|
||||||
? store.d3Selection.selection().transition().duration(options.transitionDuration)
|
|
||||||
: store.d3Selection,
|
|
||||||
transform,
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
setCenter: (x: number, y: number, zoom?: number) => {
|
) => {
|
||||||
const nextZoom = typeof zoom !== 'undefined' ? zoom : store.maxZoom
|
if (!store.getNodes.length) return
|
||||||
const centerX = store.dimensions.width / 2 - x * nextZoom
|
|
||||||
const centerY = store.dimensions.height / 2 - y * nextZoom
|
|
||||||
const transform = zoomIdentity.translate(centerX, centerY).scale(nextZoom)
|
|
||||||
|
|
||||||
store.d3Selection && store.d3Zoom?.transform(store.d3Selection, transform)
|
let nodes: GraphNode[] = []
|
||||||
},
|
if (options.nodes) {
|
||||||
fitBounds: (bounds: Rect, padding = DEFAULT_PADDING) => {
|
nodes = store.nodes.filter((n) => options.nodes?.includes(n.id))
|
||||||
const [x, y, zoom] = getTransformForBounds(
|
}
|
||||||
bounds,
|
if (!nodes || !nodes.length) {
|
||||||
store.dimensions.width,
|
nodes = options.includeHiddenNodes ? store.nodes : store.getNodes
|
||||||
store.dimensions.height,
|
}
|
||||||
store.minZoom,
|
const bounds = getRectOfNodes(nodes)
|
||||||
store.maxZoom,
|
const [x, y, zoom] = getTransformForBounds(
|
||||||
padding,
|
bounds,
|
||||||
)
|
store.dimensions.width,
|
||||||
const transform = zoomIdentity.translate(x, y).scale(zoom)
|
store.dimensions.height,
|
||||||
|
options.minZoom ?? store.minZoom,
|
||||||
|
options.maxZoom ?? store.maxZoom,
|
||||||
|
options.padding ?? DEFAULT_PADDING,
|
||||||
|
options.offset,
|
||||||
|
)
|
||||||
|
const transform = zoomIdentity.translate(x, y).scale(zoom)
|
||||||
|
|
||||||
store.d3Selection && store.d3Zoom?.transform(store.d3Selection, transform)
|
store.d3Selection && store.d3Zoom?.transform(transition(store.d3Selection, options?.duration), transform)
|
||||||
},
|
},
|
||||||
project: (position: XYPosition) => pointToRendererPoint(position, store.transform, store.snapToGrid, store.snapGrid),
|
setCenter: (x, y, options) => {
|
||||||
}
|
const nextZoom = typeof options?.zoom !== 'undefined' ? options.zoom : store.maxZoom
|
||||||
}
|
const centerX = store.dimensions.width / 2 - x * nextZoom
|
||||||
|
const centerY = store.dimensions.height / 2 - y * nextZoom
|
||||||
|
const transform = zoomIdentity.translate(centerX, centerY).scale(nextZoom)
|
||||||
|
|
||||||
|
store.d3Selection && store.d3Zoom?.transform(transition(store.d3Selection, options?.duration), transform)
|
||||||
|
},
|
||||||
|
fitBounds: (bounds, options = { padding: DEFAULT_PADDING }) => {
|
||||||
|
const [x, y, zoom] = getTransformForBounds(
|
||||||
|
bounds,
|
||||||
|
store.dimensions.width,
|
||||||
|
store.dimensions.height,
|
||||||
|
store.minZoom,
|
||||||
|
store.maxZoom,
|
||||||
|
options.padding,
|
||||||
|
)
|
||||||
|
const transform = zoomIdentity.translate(x, y).scale(zoom)
|
||||||
|
|
||||||
|
store.d3Selection && store.d3Zoom?.transform(transition(store.d3Selection, options.duration), transform)
|
||||||
|
},
|
||||||
|
project: (position) => pointToRendererPoint(position, store.transform, store.snapToGrid, store.snapGrid),
|
||||||
|
})
|
||||||
|
|||||||
+17
-49
@@ -2,11 +2,11 @@ import { CSSProperties } from 'vue'
|
|||||||
import { GraphEdge, Edge } from './edge'
|
import { GraphEdge, Edge } from './edge'
|
||||||
import { GraphNode, CoordinateExtent, Node } from './node'
|
import { GraphNode, CoordinateExtent, Node } from './node'
|
||||||
import { ConnectionLineType, ConnectionMode } from './connection'
|
import { ConnectionLineType, ConnectionMode } from './connection'
|
||||||
import { KeyCode, PanOnScrollMode } from './zoom'
|
import { KeyCode, PanOnScrollMode, UseZoomPanHelper } from './zoom'
|
||||||
import { EdgeTypes, NodeTypes } from './components'
|
import { EdgeTypes, NodeTypes } from './components'
|
||||||
|
|
||||||
export type FlowElement<DataNode = any, DataEdge = any> = GraphNode<DataNode> | GraphEdge<DataEdge>
|
export type FlowElement<N = any, E = any> = GraphNode<N> | GraphEdge<E>
|
||||||
export type FlowElements<DataNode = any, DataEdge = any> = FlowElement<DataNode, DataEdge>[]
|
export type FlowElements<N = any, E = any> = FlowElement<N, E>[]
|
||||||
export interface Element<Data = any> {
|
export interface Element<Data = any> {
|
||||||
id: string
|
id: string
|
||||||
label?: string
|
label?: string
|
||||||
@@ -16,12 +16,7 @@ export interface Element<Data = any> {
|
|||||||
style?: CSSProperties
|
style?: CSSProperties
|
||||||
hidden?: boolean
|
hidden?: boolean
|
||||||
}
|
}
|
||||||
export type Elements<DataNode = any, DataEdge = any> = (Node<DataNode> | Edge<DataEdge>)[]
|
export type Elements<N = any, E = any> = (Node<N> | Edge<E>)[]
|
||||||
|
|
||||||
export type NextElements = {
|
|
||||||
nodes: GraphNode[]
|
|
||||||
edges: GraphEdge[]
|
|
||||||
}
|
|
||||||
|
|
||||||
export type Transform = [number, number, number]
|
export type Transform = [number, number, number]
|
||||||
|
|
||||||
@@ -64,53 +59,26 @@ export interface SelectionRect extends Rect {
|
|||||||
draw: boolean
|
draw: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export type FitViewParams = {
|
export type FlowExportObject<N = any, E = N> = {
|
||||||
padding?: number
|
nodes: GraphNode<N>[]
|
||||||
includeHiddenNodes?: boolean
|
edges: GraphEdge<E>[]
|
||||||
minZoom?: number
|
|
||||||
maxZoom?: number
|
|
||||||
offset?: {
|
|
||||||
x?: number
|
|
||||||
y?: number
|
|
||||||
}
|
|
||||||
transitionDuration?: number
|
|
||||||
nodes?: string[]
|
|
||||||
}
|
|
||||||
|
|
||||||
export type FlowExportObject<DataNode = any, DataEdge = DataNode> = {
|
|
||||||
nodes: GraphNode<DataNode>[]
|
|
||||||
edges: GraphEdge<DataEdge>[]
|
|
||||||
position: [number, number]
|
position: [number, number]
|
||||||
zoom: number
|
zoom: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export type FlowTransform = {
|
export type ToObject<N = any, E = N> = () => FlowExportObject<N, E>
|
||||||
x: number
|
|
||||||
y: number
|
|
||||||
zoom: number
|
|
||||||
}
|
|
||||||
|
|
||||||
export type FitViewFunc = (fitViewOptions?: FitViewParams) => void
|
export type FlowInstance<N = any, E = N> = {
|
||||||
export type ProjectFunc = (position: XYPosition) => XYPosition
|
getElements: () => FlowElements<N, E>
|
||||||
export type ToObjectFunc<DataNode = any, DataEdge = DataNode> = () => FlowExportObject<DataNode, DataEdge>
|
getNodes: () => GraphNode<N>[]
|
||||||
|
getEdges: () => GraphEdge<E>[]
|
||||||
|
toObject: ToObject<N, E>
|
||||||
|
} & UseZoomPanHelper
|
||||||
|
|
||||||
export type FlowInstance<DataNode = any, DataEdge = DataNode> = {
|
export interface FlowProps<N = any, E = N> {
|
||||||
zoomIn: () => void
|
|
||||||
zoomOut: () => void
|
|
||||||
zoomTo: (zoomLevel: number) => void
|
|
||||||
fitView: FitViewFunc
|
|
||||||
project: ProjectFunc
|
|
||||||
getElements: () => FlowElements
|
|
||||||
getNodes: () => GraphNode[]
|
|
||||||
getEdges: () => GraphEdge[]
|
|
||||||
setTransform: (transform: FlowTransform) => void
|
|
||||||
toObject: ToObjectFunc<DataNode, DataEdge>
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface FlowProps<DataNode = any, DataEdge = DataNode> {
|
|
||||||
modelValue?: any[]
|
modelValue?: any[]
|
||||||
nodes?: Node<DataNode>[]
|
nodes?: Node<N>[]
|
||||||
edges?: Edge<DataEdge>[]
|
edges?: Edge<E>[]
|
||||||
elements?: Elements
|
elements?: Elements
|
||||||
id?: string
|
id?: string
|
||||||
nodeTypes?: NodeTypes
|
nodeTypes?: NodeTypes
|
||||||
|
|||||||
+49
-9
@@ -1,5 +1,5 @@
|
|||||||
import { Selection, ZoomBehavior } from 'd3'
|
import { Selection, ZoomBehavior } from 'd3'
|
||||||
import { FitViewFunc, FlowTransform, Rect, XYPosition } from './flow'
|
import { Rect, XYPosition } from './flow'
|
||||||
|
|
||||||
export type D3Zoom = ZoomBehavior<HTMLDivElement, unknown>
|
export type D3Zoom = ZoomBehavior<HTMLDivElement, unknown>
|
||||||
export type D3Selection = Selection<HTMLDivElement, unknown, any, any>
|
export type D3Selection = Selection<HTMLDivElement, unknown, any, any>
|
||||||
@@ -13,13 +13,53 @@ export enum PanOnScrollMode {
|
|||||||
Horizontal = 'horizontal',
|
Horizontal = 'horizontal',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type UseZoomPanHelperOptions = {
|
||||||
|
duration?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export type FitViewParams = {
|
||||||
|
padding?: number
|
||||||
|
includeHiddenNodes?: boolean
|
||||||
|
minZoom?: number
|
||||||
|
maxZoom?: number
|
||||||
|
offset?: {
|
||||||
|
x?: number
|
||||||
|
y?: number
|
||||||
|
}
|
||||||
|
nodes?: string[]
|
||||||
|
} & UseZoomPanHelperOptions
|
||||||
|
|
||||||
|
export type FlowTransform = {
|
||||||
|
x: number
|
||||||
|
y: number
|
||||||
|
zoom: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export type SetCenterOptions = UseZoomPanHelperOptions & {
|
||||||
|
zoom?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export type FitBoundsOptions = UseZoomPanHelperOptions & {
|
||||||
|
padding?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export type FitView = (fitViewOptions?: FitViewParams) => void
|
||||||
|
export type Project = (position: XYPosition) => XYPosition
|
||||||
|
export type SetCenter = (x: number, y: number, options?: SetCenterOptions) => void
|
||||||
|
export type FitBounds = (bounds: Rect, options?: FitBoundsOptions) => void
|
||||||
|
export type ZoomInOut = (options?: UseZoomPanHelperOptions) => void
|
||||||
|
export type ZoomTo = (zoomLevel: number, options?: UseZoomPanHelperOptions) => void
|
||||||
|
export type GetTransform = () => FlowTransform
|
||||||
|
export type SetTransform = (transform: FlowTransform, options?: UseZoomPanHelperOptions) => void
|
||||||
|
|
||||||
export interface UseZoomPanHelper {
|
export interface UseZoomPanHelper {
|
||||||
zoomIn: () => void
|
zoomIn: ZoomInOut
|
||||||
zoomOut: () => void
|
zoomOut: ZoomInOut
|
||||||
zoomTo: (zoomLevel: number) => void
|
zoomTo: ZoomTo
|
||||||
transform: (transform: FlowTransform) => void
|
setTransform: SetTransform
|
||||||
fitView: FitViewFunc
|
getTransform: GetTransform
|
||||||
setCenter: (x: number, y: number, zoom?: number) => void
|
fitView: FitView
|
||||||
fitBounds: (bounds: Rect, padding?: number) => void
|
setCenter: SetCenter
|
||||||
project: (position: XYPosition) => XYPosition
|
fitBounds: FitBounds
|
||||||
|
project: Project
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user