Skip to content

Commit 7df2bfa

Browse files
committed
feat(core): resolve promise from viewport actions on transition end
1 parent fb569b4 commit 7df2bfa

File tree

3 files changed

+79
-46
lines changed

3 files changed

+79
-46
lines changed

examples/vite/src/Basic/BasicOptionsAPI.vue

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ export default defineComponent({
6464
:min-zoom="0.2"
6565
:max-zoom="4"
6666
:zoom-on-scroll="false"
67+
fit-view-on-init
6768
@connect="onConnect"
6869
@pane-ready="onPaneReady"
6970
@node-drag-stop="onNodeDragStop"
Lines changed: 72 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { zoomIdentity } from 'd3-zoom'
2-
import { computed, ref } from 'vue'
2+
import { computed } from 'vue'
33
import type { ComputedGetters, D3Selection, GraphNode, State, ViewportFunctions } from '~/types'
44
import { clampPosition, getRectOfNodes, getTransformForBounds, pointToRendererPoint } from '~/utils'
55

@@ -9,7 +9,9 @@ interface ExtendedViewport extends ViewportFunctions {
99

1010
const DEFAULT_PADDING = 0.1
1111

12-
function noop() {}
12+
function noop() {
13+
return Promise.resolve(false)
14+
}
1315

1416
const initialViewportHelper: ExtendedViewport = {
1517
zoomIn: noop,
@@ -25,59 +27,89 @@ const initialViewportHelper: ExtendedViewport = {
2527
}
2628

2729
export function useViewport(state: State, getters: ComputedGetters) {
28-
const { nodes, d3Zoom, d3Selection, dimensions, translateExtent, minZoom, maxZoom, viewport, snapToGrid, snapGrid, hooks } =
29-
$(state)
30-
31-
const { getNodes } = getters
30+
const { nodes, d3Zoom, d3Selection, dimensions, translateExtent, minZoom, maxZoom, viewport, snapToGrid, snapGrid } = $(state)
3231

33-
const nodesInitialized = ref(false)
34-
35-
hooks.nodesInitialized.on(() => {
36-
nodesInitialized.value = true
37-
})
32+
const { getNodes, getNodesInitialized } = getters
3833

39-
const isReady = computed(() => !!d3Zoom && !!d3Selection && !!dimensions.width && !!dimensions.height && nodesInitialized.value)
34+
const isReady = computed(
35+
() =>
36+
!!d3Zoom &&
37+
!!d3Selection &&
38+
!!dimensions.width &&
39+
!!dimensions.height &&
40+
getNodesInitialized.value.length === getNodes.value.length,
41+
)
4042

4143
function zoom(scale: number, duration?: number) {
42-
if (d3Selection && d3Zoom) {
43-
d3Zoom.scaleBy(transition(d3Selection, duration), scale)
44-
}
44+
return new Promise<boolean>((resolve) => {
45+
if (d3Selection && d3Zoom) {
46+
d3Zoom.scaleBy(
47+
transition(d3Selection, duration, () => {
48+
resolve(true)
49+
}),
50+
scale,
51+
)
52+
} else {
53+
resolve(false)
54+
}
55+
})
4556
}
4657

4758
function transformViewport(x: number, y: number, zoom: number, duration?: number) {
48-
// enforce translate extent
49-
const { x: clampedX, y: clampedY } = clampPosition({ x: -x, y: -y }, translateExtent)
50-
51-
const nextTransform = zoomIdentity.translate(-clampedX, -clampedY).scale(zoom)
52-
53-
if (d3Selection && d3Zoom) {
54-
d3Zoom.transform(transition(d3Selection, duration), nextTransform)
55-
}
59+
return new Promise<boolean>((resolve) => {
60+
// enforce translate extent
61+
const { x: clampedX, y: clampedY } = clampPosition({ x: -x, y: -y }, translateExtent)
62+
63+
const nextTransform = zoomIdentity.translate(-clampedX, -clampedY).scale(zoom)
64+
65+
if (d3Selection && d3Zoom) {
66+
d3Zoom.transform(
67+
transition(d3Selection, duration, () => {
68+
resolve(true)
69+
}),
70+
nextTransform,
71+
)
72+
} else {
73+
resolve(false)
74+
}
75+
})
5676
}
5777

5878
return computed<ExtendedViewport>(() => {
5979
if (isReady.value) {
6080
return {
6181
initialized: true,
82+
// todo: allow passing scale as option
6283
zoomIn: (options) => {
63-
zoom(1.2, options?.duration)
84+
return zoom(1.2, options?.duration)
6485
},
6586
zoomOut: (options) => {
66-
zoom(1 / 1.2, options?.duration)
87+
return zoom(1 / 1.2, options?.duration)
6788
},
6889
zoomTo: (zoomLevel, options) => {
69-
if (d3Selection && d3Zoom) {
70-
d3Zoom.scaleTo(transition(d3Selection, options?.duration), zoomLevel)
71-
}
90+
return new Promise<boolean>((resolve) => {
91+
if (d3Selection && d3Zoom) {
92+
d3Zoom.scaleTo(
93+
transition(d3Selection, options?.duration, () => {
94+
resolve(true)
95+
}),
96+
zoomLevel,
97+
)
98+
} else {
99+
resolve(false)
100+
}
101+
})
72102
},
73103
setTransform: (transform, options) => {
74-
transformViewport(transform.x, transform.y, transform.zoom, options?.duration)
104+
return transformViewport(transform.x, transform.y, transform.zoom, options?.duration)
105+
},
106+
getTransform: () => {
107+
return {
108+
x: viewport.x,
109+
y: viewport.y,
110+
zoom: viewport.zoom,
111+
}
75112
},
76-
getTransform: () => ({
77-
x: viewport.x,
78-
y: viewport.y,
79-
zoom: viewport.zoom,
80-
}),
81113
fitView: (
82114
options = {
83115
padding: DEFAULT_PADDING,
@@ -97,7 +129,7 @@ export function useViewport(state: State, getters: ComputedGetters) {
97129
})
98130

99131
if (!nodesToFit.length) {
100-
return
132+
return Promise.resolve(false)
101133
}
102134

103135
const bounds = getRectOfNodes(nodesToFit)
@@ -112,14 +144,14 @@ export function useViewport(state: State, getters: ComputedGetters) {
112144
options.offset,
113145
)
114146

115-
transformViewport(x, y, zoom, options?.duration)
147+
return transformViewport(x, y, zoom, options?.duration)
116148
},
117149
setCenter: (x, y, options) => {
118150
const nextZoom = typeof options?.zoom !== 'undefined' ? options.zoom : maxZoom
119151
const centerX = dimensions.width / 2 - x * nextZoom
120152
const centerY = dimensions.height / 2 - y * nextZoom
121153

122-
transformViewport(centerX, centerY, nextZoom, options?.duration)
154+
return transformViewport(centerX, centerY, nextZoom, options?.duration)
123155
},
124156
fitBounds: (bounds, options = { padding: DEFAULT_PADDING }) => {
125157
const { x, y, zoom } = getTransformForBounds(
@@ -131,7 +163,7 @@ export function useViewport(state: State, getters: ComputedGetters) {
131163
options.padding,
132164
)
133165

134-
transformViewport(x, y, zoom, options?.duration)
166+
return transformViewport(x, y, zoom, options?.duration)
135167
},
136168
project: (position) => pointToRendererPoint(position, viewport, snapToGrid, snapGrid),
137169
}
@@ -141,6 +173,6 @@ export function useViewport(state: State, getters: ComputedGetters) {
141173
})
142174
}
143175

144-
function transition(selection: D3Selection, ms = 0) {
145-
return selection.transition().duration(ms)
176+
function transition(selection: D3Selection, ms = 0, onEnd: () => void) {
177+
return selection.transition().duration(ms).on('end', onEnd)
146178
}

packages/core/src/types/zoom.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,28 +42,28 @@ export type FitBoundsOptions = TransitionOptions & {
4242
}
4343

4444
/** Fit the viewport around visible nodes */
45-
export type FitView = (fitViewOptions?: FitViewParams) => void
45+
export type FitView = (fitViewOptions?: FitViewParams) => Promise<boolean>
4646

4747
/** project a position onto the viewport, i.e. a mouse event clientX/clientY onto graph coordinates */
4848
export type Project = (position: XYPosition) => XYPosition
4949

5050
/** set center of viewport */
51-
export type SetCenter = (x: number, y: number, options?: SetCenterOptions) => void
51+
export type SetCenter = (x: number, y: number, options?: SetCenterOptions) => Promise<boolean>
5252

5353
/** fit the viewport around bounds */
54-
export type FitBounds = (bounds: Rect, options?: FitBoundsOptions) => void
54+
export type FitBounds = (bounds: Rect, options?: FitBoundsOptions) => Promise<boolean>
5555

5656
/** zoom in/out */
57-
export type ZoomInOut = (options?: TransitionOptions) => void
57+
export type ZoomInOut = (options?: TransitionOptions) => Promise<boolean>
5858

5959
/** zoom to a specific level */
60-
export type ZoomTo = (zoomLevel: number, options?: TransitionOptions) => void
60+
export type ZoomTo = (zoomLevel: number, options?: TransitionOptions) => Promise<boolean>
6161

6262
/** get current viewport transform */
6363
export type GetTransform = () => ViewportTransform
6464

6565
/** set current viewport transform */
66-
export type SetTransform = (transform: ViewportTransform, options?: TransitionOptions) => void
66+
export type SetTransform = (transform: ViewportTransform, options?: TransitionOptions) => Promise<boolean>
6767

6868
export interface ViewportFunctions {
6969
zoomIn: ZoomInOut

0 commit comments

Comments
 (0)