diff --git a/src/composables/useZoomPanHelper.ts b/src/composables/useZoomPanHelper.ts index 26458369..7d81375a 100644 --- a/src/composables/useZoomPanHelper.ts +++ b/src/composables/useZoomPanHelper.ts @@ -1,76 +1,76 @@ import { zoomIdentity } from 'd3-zoom' import useStore from './useStore' 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 -export default (store: FlowStore = useStore()): UseZoomPanHelper => { - 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 +const transition = (selection: D3Selection, ms = 0) => selection.transition().duration(ms) - let nodes: GraphNode[] = [] - if (options.nodes) { - nodes = store.nodes.filter((n) => options.nodes?.includes(n.id)) - } - if (!nodes || !nodes.length) { - nodes = options.includeHiddenNodes ? store.nodes : store.getNodes - } - const bounds = getRectOfNodes(nodes) - const [x, y, zoom] = getTransformForBounds( - bounds, - store.dimensions.width, - 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( - options.transitionDuration - ? store.d3Selection.selection().transition().duration(options.transitionDuration) - : store.d3Selection, - transform, - ) +export default (store: FlowStore = useStore()): UseZoomPanHelper => ({ + zoomIn: (options) => store.d3Selection && store.d3Zoom?.scaleBy(transition(store.d3Selection, options?.duration), 1.2), + zoomOut: (options) => store.d3Selection && store.d3Zoom?.scaleBy(transition(store.d3Selection, options?.duration), 1 / 1.2), + zoomTo: (zoomLevel, options) => + store.d3Selection && store.d3Zoom?.scaleTo(transition(store.d3Selection, options?.duration), zoomLevel), + setTransform: (transform, options) => { + const nextTransform = zoomIdentity.translate(transform.x, transform.y).scale(transform.zoom) + store.d3Selection && store.d3Zoom?.transform(transition(store.d3Selection, options?.duration), nextTransform) + }, + getTransform: () => ({ + x: store.transform[0], + y: store.transform[1], + zoom: store.transform[2], + }), + fitView: ( + options = { + padding: DEFAULT_PADDING, + includeHiddenNodes: false, + duration: 0, }, - setCenter: (x: number, y: number, zoom?: number) => { - const nextZoom = typeof zoom !== 'undefined' ? 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) + ) => { + if (!store.getNodes.length) return - store.d3Selection && store.d3Zoom?.transform(store.d3Selection, transform) - }, - fitBounds: (bounds: Rect, padding = DEFAULT_PADDING) => { - const [x, y, zoom] = getTransformForBounds( - bounds, - store.dimensions.width, - store.dimensions.height, - store.minZoom, - store.maxZoom, - padding, - ) - const transform = zoomIdentity.translate(x, y).scale(zoom) + let nodes: GraphNode[] = [] + if (options.nodes) { + nodes = store.nodes.filter((n) => options.nodes?.includes(n.id)) + } + if (!nodes || !nodes.length) { + nodes = options.includeHiddenNodes ? store.nodes : store.getNodes + } + const bounds = getRectOfNodes(nodes) + const [x, y, zoom] = getTransformForBounds( + bounds, + store.dimensions.width, + 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) - }, - project: (position: XYPosition) => pointToRendererPoint(position, store.transform, store.snapToGrid, store.snapGrid), - } -} + store.d3Selection && store.d3Zoom?.transform(transition(store.d3Selection, options?.duration), transform) + }, + 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), +}) diff --git a/src/types/flow.ts b/src/types/flow.ts index dfb6c399..5a7348ca 100644 --- a/src/types/flow.ts +++ b/src/types/flow.ts @@ -2,11 +2,11 @@ import { CSSProperties } from 'vue' import { GraphEdge, Edge } from './edge' import { GraphNode, CoordinateExtent, Node } from './node' import { ConnectionLineType, ConnectionMode } from './connection' -import { KeyCode, PanOnScrollMode } from './zoom' +import { KeyCode, PanOnScrollMode, UseZoomPanHelper } from './zoom' import { EdgeTypes, NodeTypes } from './components' -export type FlowElement = GraphNode | GraphEdge -export type FlowElements = FlowElement[] +export type FlowElement = GraphNode | GraphEdge +export type FlowElements = FlowElement[] export interface Element { id: string label?: string @@ -16,12 +16,7 @@ export interface Element { style?: CSSProperties hidden?: boolean } -export type Elements = (Node | Edge)[] - -export type NextElements = { - nodes: GraphNode[] - edges: GraphEdge[] -} +export type Elements = (Node | Edge)[] export type Transform = [number, number, number] @@ -64,53 +59,26 @@ export interface SelectionRect extends Rect { draw: boolean } -export type FitViewParams = { - padding?: number - includeHiddenNodes?: boolean - minZoom?: number - maxZoom?: number - offset?: { - x?: number - y?: number - } - transitionDuration?: number - nodes?: string[] -} - -export type FlowExportObject = { - nodes: GraphNode[] - edges: GraphEdge[] +export type FlowExportObject = { + nodes: GraphNode[] + edges: GraphEdge[] position: [number, number] zoom: number } -export type FlowTransform = { - x: number - y: number - zoom: number -} +export type ToObject = () => FlowExportObject -export type FitViewFunc = (fitViewOptions?: FitViewParams) => void -export type ProjectFunc = (position: XYPosition) => XYPosition -export type ToObjectFunc = () => FlowExportObject +export type FlowInstance = { + getElements: () => FlowElements + getNodes: () => GraphNode[] + getEdges: () => GraphEdge[] + toObject: ToObject +} & UseZoomPanHelper -export type FlowInstance = { - zoomIn: () => void - zoomOut: () => void - zoomTo: (zoomLevel: number) => void - fitView: FitViewFunc - project: ProjectFunc - getElements: () => FlowElements - getNodes: () => GraphNode[] - getEdges: () => GraphEdge[] - setTransform: (transform: FlowTransform) => void - toObject: ToObjectFunc -} - -export interface FlowProps { +export interface FlowProps { modelValue?: any[] - nodes?: Node[] - edges?: Edge[] + nodes?: Node[] + edges?: Edge[] elements?: Elements id?: string nodeTypes?: NodeTypes diff --git a/src/types/zoom.ts b/src/types/zoom.ts index d9026c33..f579dc68 100644 --- a/src/types/zoom.ts +++ b/src/types/zoom.ts @@ -1,5 +1,5 @@ import { Selection, ZoomBehavior } from 'd3' -import { FitViewFunc, FlowTransform, Rect, XYPosition } from './flow' +import { Rect, XYPosition } from './flow' export type D3Zoom = ZoomBehavior export type D3Selection = Selection @@ -13,13 +13,53 @@ export enum PanOnScrollMode { 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 { - zoomIn: () => void - zoomOut: () => void - zoomTo: (zoomLevel: number) => void - transform: (transform: FlowTransform) => void - fitView: FitViewFunc - setCenter: (x: number, y: number, zoom?: number) => void - fitBounds: (bounds: Rect, padding?: number) => void - project: (position: XYPosition) => XYPosition + zoomIn: ZoomInOut + zoomOut: ZoomInOut + zoomTo: ZoomTo + setTransform: SetTransform + getTransform: GetTransform + fitView: FitView + setCenter: SetCenter + fitBounds: FitBounds + project: Project }