refactor(flow!): Change transform to viewport

* Change array type to object type and rename transform type to viewport
* Update examples & docs
This commit is contained in:
Braks
2022-04-11 11:30:10 +02:00
parent 47f5a761a1
commit fcdbc21d34
29 changed files with 255 additions and 633 deletions
+1 -1
View File
@@ -9,7 +9,7 @@
"build": "vuepress build src"
},
"dependencies": {
"@braks/vue-flow": "^0.4.2-0",
"@braks/vue-flow": "^0.4.2-3",
"blobity": "^0.1.7"
},
"devDependencies": {
+4 -4
View File
@@ -331,9 +331,9 @@ const edges = ref([
- Details:
Trigger fit view when transformation-pane is mounted.
Trigger fit view when viewport is mounted.
## Zoom & Pan Options
## Viewport Options
### zoom-activation-key-code <Badge class="text-white" style="line-height: inherit" text="optional" vertical="top" />
@@ -475,7 +475,7 @@ const edges = ref([
- Details:
Default zoom pane position on initial load.
Default viewport position on initial load.
### translate-extent <Badge class="text-white" style="line-height: inherit" text="optional" vertical="top" />
@@ -492,7 +492,7 @@ const edges = ref([
- Details:
The area in which the zoom pane can be moved around.
The area in which the viewport can be moved around.
## Selection Pane Options
+1 -1
View File
@@ -5,7 +5,7 @@ title: Introduction
# Introduction
Vue Flow is a library for building node-based applications. These can be simple static diagrams or complex node-based
editors. You can implement custom nodes and edges. It also comes with components like a mini-map, zoom pane controls and
editors. You can implement custom nodes and edges. It also comes with components like a mini-map, viewport controls and
a background.
Check out the [examples](/examples/) if you want to dive directly into the code.
-349
View File
@@ -1,349 +0,0 @@
# Utilities
Vue Flow comes with built-in utilities to control the zoom-pane, state and more.
## Graph Utilities
### isEdge
- Details:
Confirms if an element is an edge.
- Example:
```vue:no-line-numbers{13}
<script setup>
import { VueFlow, isEdge } from '@braks/vue-flow'
const elements = ref([
{ id: '1', label: 'Node 1', position: { x: 250, y: 5 }, },
{ id: '2', label: 'Node 2', position: { x: 100, y: 100 }, },
{ id: 'e1-2', source: '1', target: '2', class: 'light' },
])
const toggleClass = () => {
elements.value.forEach((el) => {
if (isEdge(el)) {
el.class = el.class === 'light' ? 'dark' : 'light'
}
})
}
</script>
<template>
<VueFlow v-model="elements">
<button style="position: absolute; top: 10px; right: 10px;" @click="toggleClass">Toggle classes</button>
</VueFlow>
</template>
```
### isNode
- Details:
Confirms if an element is a node.
- Example:
```vue:no-line-numbers{13}
<script setup>
import { VueFlow, isNode } from '@braks/vue-flow'
const elements = ref([
{ id: '1', label: 'Node 1', position: { x: 250, y: 5 }, class: 'light' },
{ id: '2', label: 'Node 2', position: { x: 100, y: 100 }, class: 'light' },
{ id: 'e1-2', source: '1', target: '2' },
])
const toggleClass = () => {
elements.value.forEach((el) => {
if (isNode(el)) {
el.class = el.class === 'light' ? 'dark' : 'light'
}
})
}
</script>
<template>
<VueFlow v-model="elements">
<button style="position: absolute; top: 10px; right: 10px;" @click="toggleClass">Toggle classes</button>
</VueFlow>
</template>
```
### addEdge
::: warning
When using composition you should access `addEdges` from `useVueFlow`
:::
- Details:
Confirms if an element is a node.
- Example:
```vue:no-line-numbers{12}
<script setup>
import { VueFlow, addEdge } from '@braks/vue-flow'
const elements = ref([
{ id: '1', label: 'Node 1', position: { x: 250, y: 5 }, class: 'light' },
{ id: '2', label: 'Node 2', position: { x: 100, y: 100 }, class: 'light' },
{ id: 'e1-2', source: '1', target: '2' },
])
const onConnect = (params) => {
addEdge(params, elements.value)
}
</script>
<template>
<VueFlow v-model="elements" @connect="onConnect" />
</template>
```
### updateEdge
::: warning
When using composition you should access `updateEdge` from `useVueFlow`
:::
- Details:
Updates an edge and applies new source/target.
- Example:
```vue:no-line-numbers{12}
<script setup>
import { VueFlow, updateEdge } from '@braks/vue-flow'
const elements = ref([
{ id: '1', label: 'Node 1', position: { x: 250, y: 5 }, class: 'light' },
{ id: '2', label: 'Node 2', position: { x: 100, y: 100 }, class: 'light' },
{ id: 'e1-2', source: '1', target: '2' },
])
const onEdgeUpdate = ({ edge, connection }) => {
elements.value = updateEdge(edge, connection, elements.value)
}
</script>
<template>
<VueFlow v-model="elements" @edge-update="onEdgeUpdate" />
</template>
```
### [getOutgoers](https://types.vueflow.dev/modules.html#getOutgoers)
- Details:
Returns all target elements of a node.
### [getIncomers](https://types.vueflow.dev/modules.html#getIncomers)
- Details:
Returns all source elements of a node.
### [getConnectedEdges](https://types.vueflow.dev/modules.html#getConnectedEdges)
- Details:
Returns all connected edges of a node.
### [getTransformForBounds](https://types.vueflow.dev/modules.html#getTransformForBounds)
- Details:
Returns a transformation for the zoom pane according to input bounds.
### [getRectOfNodes](https://types.vueflow.dev/modules.html#getRectOfNodes)
- Details:
Returns a rect of node elements.
Useful when you need to know the boundaries of a set of nodes.
### [getNodesInside](https://types.vueflow.dev/modules.html#getNodesInside)
- Details:
Returns node elements that are inside a specified rect.
### [getMarkerId](https://types.vueflow.dev/modules.html#getMarkerId)
- Details:
Returns a marker id for a marker definition.
## Instance
The Vue Flow instance provides easy access to zoom-pan-helper functions.
It can be accessed either directly from the state using `useVueFlow` or
you can receive the instance with a `onPaneReady` event handler.
<CodeGroup>
<CodeGroupItem title="Composition API" active>
```vue:no-line-numbers
<script setup>
import { VueFlow, useVueFlow } from '@braks/vue-flow'
const { onPaneReady, instance } = useVueFlow()
// event handler
onPaneReady((instance) => instance.fitView())
onMounted(() => {
// or directly try to access the instance
instance.value?.fitView()
})
</script>
```
</CodeGroupItem>
<CodeGroupItem title="Options API">
```vue:no-line-numbers
<script>
import { VueFlow } from '@braks/vue-flow'
export default defineComponent({
components: { VueFlow },
data() {
return {
instance: null,
}
},
methods: {
onPaneReady(vueFlowInstance) {
vueFlowInstance.fitView()
this.instance = vueFlowInstance
}
}
})
</script>
<template>
<VueFlow @pane-ready="onPaneReady" />
</template>
```
</CodeGroupItem>
</CodeGroup>
### [project](https://types.vueflow.dev/modules.html#Project)
- Details:
Transforms pixel coordinates to the internal VueFlow coordinate system.
This can be used when you drag nodes (from a sidebar for example) and need the internal position on the pane.
- Example:
```ts:no-line-numbers
vueFlowInstance.project({ x: 100, y: 100 })
```
### [fitView](https://types.vueflow.dev/modules.html#FitView)
- Details:
Fits the view port so that all nodes are visible.
Padding is 0.1 and includeHiddenNodes is false by default.
- Example:
```ts:no-line-numbers
vueFlowInstance.fitView({ padding: 0.25, includeHiddenNodes: true })
```
### [fitBounds](https://types.vueflow.dev/modules.html#FitBounds)
- Details:
Fits the view port according to the bounds' rect input.
- Example:
```ts:no-line-numbers
vueFlowInstance.fitBounds(getRectOfNodes(nodes.value))
```
### [setTransform](https://types.vueflow.dev/modules.html#SetTransform)
- Details:
Sets position and zoom of the pane.
- Example:
```ts:no-line-numbers
vueFlowInstance.setTransform({ x: 100, y: 100, zoom: 1.5 })
```
### [getTransform](https://types.vueflow.dev/modules.html#GetTransform)
- Details:
Gets position and zoom of the pane.
### [zoomIn](https://types.vueflow.dev/modules.html#ZoomInOut)
- Details:
Zooms in.
### [zoomOut](https://types.vueflow.dev/modules.html#ZoomInOut)
- Details:
Zooms out.
### [zoomTo](https://types.vueflow.dev/modules.html#ZoomTo)
- Details:
Zooms to specific level.
### getElements
- Details:
Returns currently stored elements (nodes + edges).
### getNodes
- Details:
Returns currently stored nodes.
### getEdges
- Details:
Returns currently stored edges.
### toObject
- Details:
Returns elements, position and zoom of the current flow state.
- Example:
```ts:no-line-numbers
toObject = (): {
elements: FlowElements,
position: [x, y],
zoom: scale,
}
```
+1 -1
View File
@@ -154,7 +154,7 @@ const onEdgeUpdate = ({ edge, connection }) => {
- Details:
Returns a transformation for the zoom pane according to input bounds.
Returns a transformation for the viewport according to input bounds.
### getRectOfNodes
+1 -1
View File
@@ -10,7 +10,7 @@
"serve": "vite serve"
},
"dependencies": {
"@braks/vue-flow": "^0.4.2-0"
"@braks/vue-flow": "^0.4.2-3"
},
"devDependencies": {
"@types/dagre": "^0.7.46",
+2 -2
View File
@@ -3,7 +3,7 @@ import { Handle, Position, getNodesInside, useVueFlow } from '@braks/vue-flow'
import type { NodeProps } from '@braks/vue-flow'
const props = defineProps<NodeProps>()
const { onNodeDragStop, getNodes, transform } = useVueFlow()
const { onNodeDragStop, getNodes, viewport } = useVueFlow()
onNodeDragStop(({ node }) => {
const nodes = getNodesInside(
getNodes.value,
@@ -12,7 +12,7 @@ onNodeDragStop(({ node }) => {
x: props.computedPosition.x,
y: props.computedPosition.y,
},
transform.value,
viewport.value,
)
if (nodes.some((n) => n.id === node.id && n.id !== props.id)) {
node.label = `In ${props.id}`
+2 -2
View File
@@ -1,6 +1,6 @@
<script lang="ts" setup>
import { useVueFlow } from '@braks/vue-flow'
const { nodesSelectionActive, addSelectedNodes, getNodes, transform } = useVueFlow()
const { nodesSelectionActive, addSelectedNodes, getNodes, viewport } = useVueFlow()
const selectAll = () => {
addSelectedNodes(getNodes.value)
@@ -14,7 +14,7 @@ const selectAll = () => {
</div>
<div class="title">Zoom & pan transform</div>
<div class="transform">
{{ [transform[0].toFixed(2), transform[1].toFixed(2), transform[2].toFixed(2)] }}
{{ [viewport.x.toFixed(2), viewport.y.toFixed(2), viewport.zoom.toFixed(2)] }}
</div>
<div class="title">Nodes</div>
<div v-for="node of getNodes" :key="node.id">
@@ -21,10 +21,10 @@ const defaultColors: Record<BackgroundVariant, string> = {
const { store } = useVueFlow()
const background = computed(() => {
const scaledGap = props.gap && props.gap * store.transform[2]
const xOffset = scaledGap && store.transform[0] % scaledGap
const yOffset = scaledGap && store.transform[1] % scaledGap
const size = props.size || 0.4 * store.transform[2]
const scaledGap = props.gap && props.gap * store.viewport.zoom
const xOffset = scaledGap && store.viewport.x % scaledGap
const yOffset = scaledGap && store.viewport.y % scaledGap
const size = props.size || 0.4 * store.viewport.zoom
return {
scaledGap,
@@ -28,16 +28,17 @@ const nodeColorFunc: MiniMapNodeFunc = props.nodeColor instanceof Function ? pro
const nodeStrokeColorFunc: MiniMapNodeFunc =
props.nodeStrokeColor instanceof Function ? props.nodeStrokeColor : () => props.nodeStrokeColor as string
const nodeClassNameFunc = props.nodeClassName instanceof Function ? props.nodeClassName : () => props.nodeClassName as MiniMapNodeFunc
const nodeClassNameFunc =
props.nodeClassName instanceof Function ? props.nodeClassName : () => props.nodeClassName as MiniMapNodeFunc
const shapeRendering: ShapeRendering = typeof window === 'undefined' || !!window.chrome ? 'crispEdges' : 'geometricPrecision'
const bb = computed(() => getRectOfNodes(store.getNodes))
const viewBB = computed(() => ({
x: -store.transform[0] / store.transform[2],
y: -store.transform[1] / store.transform[2],
width: store.dimensions.width / store.transform[2],
height: store.dimensions.height / store.transform[2],
x: -store.viewport.x / store.viewport.zoom,
y: -store.viewport.y / store.viewport.zoom,
width: store.dimensions.width / store.viewport.zoom,
height: store.dimensions.height / store.viewport.zoom,
}))
const viewBox = computed(() => {
const boundingRect = store.getNodes && store.getNodes.length ? getBoundsofRects(bb.value, viewBB.value) : viewBB.value
+21 -1
View File
@@ -3,14 +3,17 @@
declare global {
const asyncComputed: typeof import('@vueuse/core')['asyncComputed']
const autoResetRef: typeof import('@vueuse/core')['autoResetRef']
const biSyncRef: typeof import('@vueuse/core')['biSyncRef']
const computed: typeof import('vue')['computed']
const computedAsync: typeof import('@vueuse/core')['computedAsync']
const computedEager: typeof import('@vueuse/core')['computedEager']
const computedInject: typeof import('@vueuse/core')['computedInject']
const computedWithControl: typeof import('@vueuse/core')['computedWithControl']
const controlledComputed: typeof import('@vueuse/core')['controlledComputed']
const controlledRef: typeof import('@vueuse/core')['controlledRef']
const createApp: typeof import('vue')['createApp']
const createEventHook: typeof import('@vueuse/core')['createEventHook']
const createGlobalState: typeof import('@vueuse/core')['createGlobalState']
const createInjectionState: typeof import('@vueuse/core')['createInjectionState']
const createReactiveFn: typeof import('@vueuse/core')['createReactiveFn']
const createSharedComposable: typeof import('@vueuse/core')['createSharedComposable']
const createUnrefFn: typeof import('@vueuse/core')['createUnrefFn']
@@ -31,6 +34,9 @@ declare global {
const isDefined: typeof import('@vueuse/core')['isDefined']
const isReadonly: typeof import('vue')['isReadonly']
const isRef: typeof import('vue')['isRef']
const logicAnd: typeof import('@vueuse/core')['logicAnd']
const logicNot: typeof import('@vueuse/core')['logicNot']
const logicOr: typeof import('@vueuse/core')['logicOr']
const makeDestructurable: typeof import('@vueuse/core')['makeDestructurable']
const markRaw: typeof import('vue')['markRaw']
const nextTick: typeof import('vue')['nextTick']
@@ -61,12 +67,17 @@ declare global {
const reactivePick: typeof import('@vueuse/core')['reactivePick']
const readonly: typeof import('vue')['readonly']
const ref: typeof import('vue')['ref']
const refAutoReset: typeof import('@vueuse/core')['refAutoReset']
const refDebounced: typeof import('@vueuse/core')['refDebounced']
const refDefault: typeof import('@vueuse/core')['refDefault']
const refThrottled: typeof import('@vueuse/core')['refThrottled']
const refWithControl: typeof import('@vueuse/core')['refWithControl']
const resolveComponent: typeof import('vue')['resolveComponent']
const shallowReactive: typeof import('vue')['shallowReactive']
const shallowReadonly: typeof import('vue')['shallowReadonly']
const shallowRef: typeof import('vue')['shallowRef']
const syncRef: typeof import('@vueuse/core')['syncRef']
const syncRefs: typeof import('@vueuse/core')['syncRefs']
const templateRef: typeof import('@vueuse/core')['templateRef']
const throttledRef: typeof import('@vueuse/core')['throttledRef']
const throttledWatch: typeof import('@vueuse/core')['throttledWatch']
@@ -75,6 +86,7 @@ declare global {
const toRef: typeof import('vue')['toRef']
const toRefs: typeof import('vue')['toRefs']
const triggerRef: typeof import('vue')['triggerRef']
const tryOnBeforeMount: typeof import('@vueuse/core')['tryOnBeforeMount']
const tryOnBeforeUnmount: typeof import('@vueuse/core')['tryOnBeforeUnmount']
const tryOnMounted: typeof import('@vueuse/core')['tryOnMounted']
const tryOnScopeDispose: typeof import('@vueuse/core')['tryOnScopeDispose']
@@ -102,6 +114,7 @@ declare global {
const useCssVars: typeof import('vue')['useCssVars']
const useCycleList: typeof import('@vueuse/core')['useCycleList']
const useDark: typeof import('@vueuse/core')['useDark']
const useDateFormat: typeof import('@vueuse/core')['useDateFormat']
const useDebounce: typeof import('@vueuse/core')['useDebounce']
const useDebouncedRefHistory: typeof import('@vueuse/core')['useDebouncedRefHistory']
const useDebounceFn: typeof import('@vueuse/core')['useDebounceFn']
@@ -123,10 +136,12 @@ declare global {
const useEyeDropper: typeof import('@vueuse/core')['useEyeDropper']
const useFavicon: typeof import('@vueuse/core')['useFavicon']
const useFetch: typeof import('@vueuse/core')['useFetch']
const useFileSystemAccess: typeof import('@vueuse/core')['useFileSystemAccess']
const useFocus: typeof import('@vueuse/core')['useFocus']
const useFocusWithin: typeof import('@vueuse/core')['useFocusWithin']
const useFps: typeof import('@vueuse/core')['useFps']
const useFullscreen: typeof import('@vueuse/core')['useFullscreen']
const useGamepad: typeof import('@vueuse/core')['useGamepad']
const useGeolocation: typeof import('@vueuse/core')['useGeolocation']
const useIdle: typeof import('@vueuse/core')['useIdle']
const useInfiniteScroll: typeof import('@vueuse/core')['useInfiniteScroll']
@@ -184,6 +199,7 @@ declare global {
const useTimeAgo: typeof import('@vueuse/core')['useTimeAgo']
const useTimeout: typeof import('@vueuse/core')['useTimeout']
const useTimeoutFn: typeof import('@vueuse/core')['useTimeoutFn']
const useTimeoutPoll: typeof import('@vueuse/core')['useTimeoutPoll']
const useTimestamp: typeof import('@vueuse/core')['useTimestamp']
const useTitle: typeof import('@vueuse/core')['useTitle']
const useToggle: typeof import('@vueuse/core')['useToggle']
@@ -204,8 +220,12 @@ declare global {
const useWindowSize: typeof import('@vueuse/core')['useWindowSize']
const watch: typeof import('vue')['watch']
const watchAtMost: typeof import('@vueuse/core')['watchAtMost']
const watchDebounced: typeof import('@vueuse/core')['watchDebounced']
const watchEffect: typeof import('vue')['watchEffect']
const watchIgnorable: typeof import('@vueuse/core')['watchIgnorable']
const watchOnce: typeof import('@vueuse/core')['watchOnce']
const watchPausable: typeof import('@vueuse/core')['watchPausable']
const watchThrottled: typeof import('@vueuse/core')['watchThrottled']
const watchWithFilter: typeof import('@vueuse/core')['watchWithFilter']
const whenever: typeof import('@vueuse/core')['whenever']
}
@@ -27,8 +27,8 @@ const sourceY = props.sourceNode.computedPosition.y + sourceHandleY
const isRightOrLeft = sourceHandle?.position === Position.Left || sourceHandle?.position === Position.Right
const targetPosition = isRightOrLeft ? Position.Left : Position.Top
const targetX = computed(() => (store.connectionPosition.x - store.transform[0]) / store.transform[2])
const targetY = computed(() => (store.connectionPosition.y - store.transform[1]) / store.transform[2])
const targetX = computed(() => (store.connectionPosition.x - store.viewport.x) / store.viewport.zoom)
const targetY = computed(() => (store.connectionPosition.y - store.viewport.y) / store.viewport.zoom)
const dAttr = computed(() => {
let path = `M${sourceX},${sourceY} ${targetX.value},${targetY.value}`
+3 -3
View File
@@ -117,14 +117,14 @@ const { scale, onDrag, onDragStart, onDragStop } = useDraggableCore(nodeElement,
grid: props.snapGrid,
cancel: `.${store.noDragClassName}`,
enableUserSelectHack: false,
scale: store.transform[2],
scale: store.viewport.zoom,
})
onMounted(() => {
debouncedWatch(
() => store.transform[2],
() => store.viewport.zoom,
() => {
scale.value = store.transform[2]
scale.value = store.viewport.zoom
},
{ debounce: 5 },
)
@@ -16,7 +16,7 @@ const innerStyle = computed(() => ({
left: `${selectedNodesBBox.value.x}px`,
}))
const transform = computed(() => `translate(${store.transform[0]}px,${store.transform[1]}px) scale(${store.transform[2]})`)
const transform = computed(() => `translate(${store.viewport.x}px,${store.viewport.y}px) scale(${store.viewport.zoom})`)
watch(selectedNodesBBox, (v) => (store.selectedNodesBbox = v))
@@ -25,14 +25,14 @@ const onContextMenu = (event: MouseEvent) => store.hooks.selectionContextMenu.tr
const { onDragStart, onDrag, onDragStop, scale } = useDraggableCore(el, {
grid: store.snapToGrid ? store.snapGrid : undefined,
enableUserSelectHack: false,
scale: store.transform[2],
scale: store.viewport.zoom,
})
onMounted(() => {
debouncedWatch(
() => store.transform[2],
() => store.viewport.zoom,
() => {
scale.value = store.transform[2]
scale.value = store.viewport.zoom
},
{ debounce: 5 },
)
@@ -64,7 +64,7 @@ const onMouseMove = (event: MouseEvent) => {
height: Math.abs(mousePos.y - startY),
}
const selectedNodes = getNodesInside(store.getNodes, rect.value, store.transform)
const selectedNodes = getNodesInside(store.getNodes, rect.value, store.viewport)
const selectedEdges = getConnectedEdges(selectedNodes, store.getEdges)
rect.value = nextUserSelectRect
+8 -8
View File
@@ -2,13 +2,13 @@ import { zoomIdentity } from 'd3-zoom'
import useVueFlow from './useVueFlow'
import useWindow from './useWindow'
import { getRectOfNodes, pointToRendererPoint, getTransformForBounds } from '~/utils'
import { GraphNode, Store, Viewport, D3Selection } from '~/types'
import { GraphNode, Store, ViewportFuncs, D3Selection } from '~/types'
const DEFAULT_PADDING = 0.1
const transition = (selection: D3Selection, ms = 0) => selection.transition().duration(ms)
export default (store: Store = useVueFlow().store): Viewport => ({
export default (store: Store = useVueFlow().store): ViewportFuncs => ({
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) =>
@@ -18,9 +18,9 @@ export default (store: Store = useVueFlow().store): Viewport => ({
store.d3Selection && store.d3Zoom?.transform(transition(store.d3Selection, options?.duration), nextTransform)
},
getTransform: () => ({
x: store.transform[0],
y: store.transform[1],
zoom: store.transform[2],
x: store.viewport.x,
y: store.viewport.y,
zoom: store.viewport.zoom,
}),
fitView: async (
options = {
@@ -44,7 +44,7 @@ export default (store: Store = useVueFlow().store): Viewport => ({
nodes = options.includeHiddenNodes ? store.nodes : store.getNodes
}
const bounds = getRectOfNodes(nodes)
const [x, y, zoom] = getTransformForBounds(
const { x, y, zoom } = getTransformForBounds(
bounds,
store.dimensions.width,
store.dimensions.height,
@@ -66,7 +66,7 @@ export default (store: Store = useVueFlow().store): Viewport => ({
store.d3Selection && store.d3Zoom?.transform(transition(store.d3Selection, options?.duration), transform)
},
fitBounds: (bounds, options = { padding: DEFAULT_PADDING }) => {
const [x, y, zoom] = getTransformForBounds(
const { x, y, zoom } = getTransformForBounds(
bounds,
store.dimensions.width,
store.dimensions.height,
@@ -78,5 +78,5 @@ export default (store: Store = useVueFlow().store): Viewport => ({
store.d3Selection && store.d3Zoom?.transform(transition(store.d3Selection, options.duration), transform)
},
project: (position) => pointToRendererPoint(position, store.transform, store.snapToGrid, store.snapGrid),
project: (position) => pointToRendererPoint(position, store.viewport, store.snapToGrid, store.snapGrid),
})
+2 -2
View File
@@ -1,7 +1,7 @@
<script lang="ts" setup>
import NodeRenderer from '../NodeRenderer/NodeRenderer.vue'
import EdgeRenderer from '../EdgeRenderer/EdgeRenderer.vue'
import { useKeyPress, useVueFlow, useZoomPanHelper } from '../../composables'
import { useVueFlow, useZoomPanHelper } from '../../composables'
import { FlowInstance } from '../../types'
import { onLoadGetEdges, onLoadGetElements, onLoadGetNodes, onLoadProject, onLoadToObject } from '../../utils'
@@ -29,7 +29,7 @@ onMounted(() => {
store.hooks.paneReady.trigger(instance)
})
const transform = computed(() => `translate(${store.transform[0]}px,${store.transform[1]}px) scale(${store.transform[2]})`)
const transform = computed(() => `translate(${store.viewport.x}px,${store.viewport.y}px) scale(${store.viewport.zoom})`)
</script>
<script lang="ts">
export default {
+148 -29
View File
@@ -1,33 +1,159 @@
<script lang="ts" setup>
import { zoom } from 'd3-zoom'
import { select } from 'd3-selection'
import { useVueFlow } from '../../composables'
import { D3ZoomEvent, zoom, zoomIdentity, ZoomTransform } from 'd3-zoom'
import { pointer, select } from 'd3-selection'
import { FlowTransform, PanOnScrollMode } from '../../types'
import { useKeyPress, useVueFlow } from '../../composables'
import { clamp, clampPosition } from '../../utils'
import SelectionPane from '../SelectionPane/SelectionPane.vue'
import Zoom from './Zoom.vue'
import Transform from './Transform.vue'
const { id, store } = useVueFlow()
const zoomPaneEl = templateRef<HTMLDivElement>('viewport', null)
const viewportEl = templateRef<HTMLDivElement>('viewport', null)
const { width, height } = useElementBounding(zoomPaneEl)
const viewChanged = (prevTransform: FlowTransform, eventTransform: ZoomTransform): boolean =>
(prevTransform.x !== eventTransform.x && !isNaN(eventTransform.x)) ||
(prevTransform.y !== eventTransform.y && !isNaN(eventTransform.y)) ||
(prevTransform.zoom !== eventTransform.k && !isNaN(eventTransform.k))
useResizeObserver(zoomPaneEl, () => {
store.dimensions.width = width.value
store.dimensions.height = height.value
const eventToFlowTransform = (eventTransform: ZoomTransform): FlowTransform => ({
x: eventTransform.x,
y: eventTransform.y,
zoom: eventTransform.k,
})
const d3Zoom = ref()
const d3Selection = ref()
const d3ZoomHandler = ref()
const isWrappedWithClass = (event: Event, className: string | undefined) => (event.target as Element).closest(`.${className}`)
const clampedZoom = clamp(store.defaultZoom, store.minZoom, store.maxZoom)
const transform = ref({
...clampPosition({ x: store.defaultPosition[0], y: store.defaultPosition[1] }, store.translateExtent),
zoom: clampedZoom,
})
const { width, height } = useElementBounding(viewportEl)
watch(
[width, height],
([newWidth, newHeight]) => {
store.dimensions.width = newWidth
store.dimensions.height = newHeight
},
{ immediate: true },
)
onMounted(() => {
d3Zoom.value = zoom<HTMLDivElement, any>().scaleExtent([store.minZoom, store.maxZoom]).translateExtent(store.translateExtent)
d3Selection.value = select(zoomPaneEl.value).call(d3Zoom.value)
d3ZoomHandler.value = d3Selection.value.on('wheel.zoom')
const d3Zoom = zoom<HTMLDivElement, any>().scaleExtent([store.minZoom, store.maxZoom]).translateExtent(store.translateExtent)
const d3Selection = select(viewportEl.value).call(d3Zoom)
const d3ZoomHandler = d3Selection.on('wheel.zoom')
const updatedTransform = zoomIdentity.translate(transform.value.x, transform.value.y).scale(transform.value.zoom)
d3Zoom.transform(d3Selection, updatedTransform)
store.setState({
d3Zoom: d3Zoom.value,
d3Selection: d3Selection.value,
d3ZoomHandler: d3ZoomHandler.value,
d3Zoom,
d3Selection,
d3ZoomHandler,
viewport: { x: updatedTransform.x, y: updatedTransform.y, zoom: updatedTransform.k },
})
const selectionKeyPressed = useKeyPress(store.selectionKeyCode, (keyPress) => {
if (keyPress) {
d3Zoom.on('zoom', null)
} else {
d3Zoom.on('zoom', (event: D3ZoomEvent<HTMLDivElement, any>) => {
store.setState({ viewport: { x: event.transform.x, y: event.transform.y, zoom: event.transform.k } })
const flowTransform = eventToFlowTransform(event.transform)
store.hooks.move.trigger({ event, flowTransform })
})
}
})
const zoomKeyPressed = useKeyPress(store.zoomActivationKeyCode)
d3Zoom.on('start', (event: D3ZoomEvent<HTMLDivElement, any>) => {
const flowTransform = eventToFlowTransform(event.transform)
transform.value = flowTransform
store.hooks.moveStart.trigger({ event, flowTransform })
})
d3Zoom.on('end', (event: D3ZoomEvent<HTMLDivElement, any>) => {
if (viewChanged(transform.value, event.transform)) {
const flowTransform = eventToFlowTransform(event.transform)
transform.value = flowTransform
store.hooks.moveEnd.trigger({ event, flowTransform })
}
})
d3Selection
?.on('wheel', (event: WheelEvent) => {
if (store.panOnScroll && !zoomKeyPressed.value) {
if (isWrappedWithClass(event, store.noWheelClassName)) return
event.preventDefault()
event.stopImmediatePropagation()
const currentZoom = d3Selection?.property('__zoom').k || 1
if (event.ctrlKey && store.zoomOnPinch) {
const point = pointer(event)
// taken from https://github.com/d3/d3-zoom/blob/master/src/zoom.js
const pinchDelta = -event.deltaY * (event.deltaMode === 1 ? 0.05 : event.deltaMode ? 1 : 0.002) * 10
const zoom = currentZoom * Math.pow(2, pinchDelta)
if (d3Selection) d3Zoom.scaleTo(d3Selection, zoom, point)
return
}
// increase scroll speed in firefox
// firefox: deltaMode === 1; chrome: deltaMode === 0
const deltaNormalize = event.deltaMode === 1 ? 20 : 1
const deltaX = store.panOnScrollMode === PanOnScrollMode.Vertical ? 0 : event.deltaX * deltaNormalize
const deltaY = store.panOnScrollMode === PanOnScrollMode.Horizontal ? 0 : event.deltaY * deltaNormalize
if (d3Selection && store.panOnScrollSpeed)
d3Zoom?.translateBy(
d3Selection,
-(deltaX / currentZoom) * store.panOnScrollSpeed,
-(deltaY / currentZoom) * store.panOnScrollSpeed,
)
} else {
if (
(!store.zoomOnScroll && store.preventScrolling) ||
!store.preventScrolling ||
isWrappedWithClass(event, store.noWheelClassName)
)
return null
event.preventDefault()
}
})
.on('wheel.zoom', store.panOnScroll || typeof d3ZoomHandler === 'undefined' ? null : (d3ZoomHandler as any))
d3Zoom.filter((event: MouseEvent) => {
const zoomScroll = zoomKeyPressed.value || store.zoomOnScroll
const pinchZoom = store.zoomOnPinch && event.ctrlKey
// if all interactions are disabled, we prevent all zoom events
if (!store.panOnDrag && !zoomScroll && !store.panOnScroll && !store.zoomOnDoubleClick && !store.zoomOnPinch) return false
// during a selection we prevent all other interactions
if (selectionKeyPressed.value) return false
// if zoom on double click is disabled, we prevent the double click event
if (!store.zoomOnDoubleClick && event.type === 'dblclick') return false
// if the target element is inside an element with the nowheel class, we prevent zooming
if (isWrappedWithClass(event, store.noWheelClassName) && event.type === 'wheel') return false
// if the target element is inside an element with the nopan class, we prevent panning
if (isWrappedWithClass(event, store.noPanClassName) && event.type !== 'wheel') return false
if (!store.zoomOnPinch && event.ctrlKey && event.type === 'wheel') return false
// when there is no scroll handling enabled, we prevent all wheel events
if (!zoomScroll && !store.panOnScroll && !pinchZoom && event.type === 'wheel') return false
// if the pane is not movable, we prevent dragging it with mousestart or touchstart
if (!store.panOnDrag && (event.type === 'mousedown' || event.type === 'touchstart')) return false
// default filter for d3-zoom
return (!event.ctrlKey || event.type === 'wheel') && !event.button
})
})
</script>
@@ -37,17 +163,10 @@ export default {
}
</script>
<template>
<div ref="viewport" class="vue-flow__viewport vue-flow__container">
<Zoom
v-if="d3Zoom && d3Selection && d3ZoomHandler"
:d3-zoom="d3Zoom"
:d3-selection="d3Selection"
:d3-zoom-handler="d3ZoomHandler"
>
<Transform>
<slot />
</Transform>
</Zoom>
<div ref="viewport" :key="`viewport-${id}`" class="vue-flow__viewport vue-flow__container">
<Transform>
<slot />
</Transform>
<SelectionPane :key="`selection-pane-${id}`" />
</div>
</template>
-151
View File
@@ -1,151 +0,0 @@
<script lang="ts" setup>
import { D3ZoomEvent, zoomIdentity, ZoomTransform } from 'd3-zoom'
import { pointer } from 'd3-selection'
import { useKeyPress, useVueFlow } from '../../composables'
import { D3Selection, D3Zoom, D3ZoomHandler, FlowTransform, PanOnScrollMode } from '../../types'
import { clamp, clampPosition } from '../../utils'
interface Props {
d3Zoom: D3Zoom
d3Selection: D3Selection
d3ZoomHandler: D3ZoomHandler
}
const props = defineProps<Props>()
const { store } = useVueFlow()
const viewChanged = (prevTransform: FlowTransform, eventTransform: ZoomTransform): boolean =>
(prevTransform.x !== eventTransform.x && !isNaN(eventTransform.x)) ||
(prevTransform.y !== eventTransform.y && !isNaN(eventTransform.y)) ||
(prevTransform.zoom !== eventTransform.k && !isNaN(eventTransform.k))
const eventToFlowTransform = (eventTransform: ZoomTransform): FlowTransform => ({
x: eventTransform.x,
y: eventTransform.y,
zoom: eventTransform.k,
})
const isWrappedWithClass = (event: Event, className: string | undefined) => (event.target as Element).closest(`.${className}`)
const clampedZoom = clamp(store.defaultZoom, store.minZoom, store.maxZoom)
const transform = ref({
...clampPosition({ x: store.defaultPosition[0], y: store.defaultPosition[1] }, store.translateExtent),
zoom: clampedZoom,
})
const updatedTransform = zoomIdentity.translate(transform.value.x, transform.value.y).scale(transform.value.zoom)
props.d3Zoom.transform(props.d3Selection, updatedTransform)
store.transform = [updatedTransform.x, updatedTransform.y, updatedTransform.k]
const selectionKeyPressed = useKeyPress(store.selectionKeyCode, (keyPress) => {
if (keyPress) {
props.d3Zoom.on('zoom', null)
} else {
props.d3Zoom.on('zoom', (event: D3ZoomEvent<HTMLDivElement, any>) => {
store.setState({ transform: [event.transform.x, event.transform.y, event.transform.k] })
const flowTransform = eventToFlowTransform(event.transform)
store.hooks.move.trigger({ event, flowTransform })
})
}
})
const zoomKeyPressed = useKeyPress(store.zoomActivationKeyCode)
props.d3Zoom.on('start', (event: D3ZoomEvent<HTMLDivElement, any>) => {
const flowTransform = eventToFlowTransform(event.transform)
transform.value = flowTransform
store.hooks.moveStart.trigger({ event, flowTransform })
})
props.d3Zoom.on('end', (event: D3ZoomEvent<HTMLDivElement, any>) => {
if (viewChanged(transform.value, event.transform)) {
const flowTransform = eventToFlowTransform(event.transform)
transform.value = flowTransform
store.hooks.moveEnd.trigger({ event, flowTransform })
}
})
props.d3Selection
?.on('wheel', (event: WheelEvent) => {
if (store.panOnScroll && !zoomKeyPressed.value) {
if (isWrappedWithClass(event, store.noWheelClassName)) return
event.preventDefault()
event.stopImmediatePropagation()
const currentZoom = props.d3Selection?.property('__zoom').k || 1
if (event.ctrlKey && store.zoomOnPinch) {
const point = pointer(event)
// taken from https://github.com/d3/d3-zoom/blob/master/src/zoom.js
const pinchDelta = -event.deltaY * (event.deltaMode === 1 ? 0.05 : event.deltaMode ? 1 : 0.002) * 10
const zoom = currentZoom * Math.pow(2, pinchDelta)
if (props.d3Selection) props.d3Zoom.scaleTo(props.d3Selection, zoom, point)
return
}
// increase scroll speed in firefox
// firefox: deltaMode === 1; chrome: deltaMode === 0
const deltaNormalize = event.deltaMode === 1 ? 20 : 1
const deltaX = store.panOnScrollMode === PanOnScrollMode.Vertical ? 0 : event.deltaX * deltaNormalize
const deltaY = store.panOnScrollMode === PanOnScrollMode.Horizontal ? 0 : event.deltaY * deltaNormalize
if (props.d3Selection && store.panOnScrollSpeed)
props.d3Zoom?.translateBy(
props.d3Selection,
-(deltaX / currentZoom) * store.panOnScrollSpeed,
-(deltaY / currentZoom) * store.panOnScrollSpeed,
)
} else {
if (
(!store.zoomOnScroll && store.preventScrolling) ||
!store.preventScrolling ||
isWrappedWithClass(event, store.noWheelClassName)
)
return null
event.preventDefault()
}
})
.on('wheel.zoom', store.panOnScroll || typeof props.d3ZoomHandler === 'undefined' ? null : (props.d3ZoomHandler as any))
props.d3Zoom.filter((event: MouseEvent) => {
const zoomScroll = zoomKeyPressed.value || store.zoomOnScroll
const pinchZoom = store.zoomOnPinch && event.ctrlKey
// if all interactions are disabled, we prevent all zoom events
if (!store.panOnDrag && !zoomScroll && !store.panOnScroll && !store.zoomOnDoubleClick && !store.zoomOnPinch) return false
// during a selection we prevent all other interactions
if (selectionKeyPressed.value) return false
// if zoom on double click is disabled, we prevent the double click event
if (!store.zoomOnDoubleClick && event.type === 'dblclick') return false
// if the target element is inside an element with the nowheel class, we prevent zooming
if (isWrappedWithClass(event, store.noWheelClassName) && event.type === 'wheel') return false
// if the target element is inside an element with the nopan class, we prevent panning
if (isWrappedWithClass(event, store.noPanClassName) && event.type !== 'wheel') return false
if (!store.zoomOnPinch && event.ctrlKey && event.type === 'wheel') return false
// when there is no scroll handling enabled, we prevent all wheel events
if (!zoomScroll && !store.panOnScroll && !pinchZoom && event.type === 'wheel') return false
// if the pane is not movable, we prevent dragging it with mousestart or touchstart
if (!store.panOnDrag && (event.type === 'mousedown' || event.type === 'touchstart')) return false
// default filter for d3-zoom
return (!event.ctrlKey || event.type === 'wheel') && !event.button
})
</script>
<script lang="ts">
export default {
name: 'Zoom',
}
</script>
<template>
<slot />
</template>
+1 -1
View File
@@ -152,7 +152,7 @@ export default (state: State, getters: ComputedGetters): Actions => {
dimensions.height &&
(node.dimensions.width !== dimensions.width || node.dimensions.height !== dimensions.height || update.forceUpdate)
)
node.handleBounds = getHandleBounds(update.nodeElement, state.transform[2])
node.handleBounds = getHandleBounds(update.nodeElement, state.viewport.zoom)
if (doUpdate) {
node.dimensions = dimensions
+2 -2
View File
@@ -35,7 +35,7 @@ export default (state: State): ComputedGetters => {
width: state.dimensions.width,
height: state.dimensions.height,
},
state.transform,
state.viewport,
true,
)
: nodes ?? []
@@ -63,7 +63,7 @@ export default (state: State): ComputedGetters => {
targetHeight: e.targetNode.dimensions.height,
width: state.dimensions.width,
height: state.dimensions.height,
transform: state.transform,
viewport: state.viewport,
}),
)
})
+1 -1
View File
@@ -46,7 +46,7 @@ const defaultState = (): State => ({
width: 0,
height: 0,
},
transform: [0, 0, 1],
viewport: { x: 0, y: 0, zoom: 1 },
d3Zoom: null,
d3Selection: null,
+2 -5
View File
@@ -2,7 +2,7 @@ import { CSSProperties, VNode } from 'vue'
import { GraphEdge, Edge, DefaultEdgeOptions } from './edge'
import { GraphNode, CoordinateExtent, Node } from './node'
import { ConnectionLineType, ConnectionMode } from './connection'
import { KeyCode, PanOnScrollMode, Viewport } from './zoom'
import { KeyCode, PanOnScrollMode, ViewportFuncs } from './zoom'
import { DefaultEdgeTypes, DefaultNodeTypes, EdgeComponent, NodeComponent } from './components'
export type ElementData = any
@@ -36,9 +36,6 @@ export interface Element<Data extends ElementData = ElementData> {
}
export type Elements<NodeData = ElementData, EdgeData = ElementData> = (Node<NodeData> | Edge<EdgeData>)[]
/** Transform x, y, z */
export type Transform = [number, number, number]
/** Handle Positions */
export enum Position {
Left = 'left',
@@ -93,7 +90,7 @@ interface Exports {
toObject: () => FlowExportObject
}
export type FlowInstance = Exports & Viewport
export type FlowInstance = Exports & ViewportFuncs
export interface FlowProps<NodeData = ElementData, EdgeData = ElementData> {
id?: string
+3 -14
View File
@@ -1,21 +1,10 @@
import { ComputedRef, CSSProperties, ToRefs } from 'vue'
import {
Dimensions,
ElementData,
Elements,
FlowElements,
FlowInstance,
FlowOptions,
Rect,
SnapGrid,
Transform,
XYPosition,
} from './flow'
import { Dimensions, ElementData, Elements, FlowElements, FlowInstance, FlowOptions, Rect, SnapGrid, XYPosition } from './flow'
import { EdgeComponent, NodeComponent, DefaultNodeTypes, DefaultEdgeTypes } from './components'
import { Connection, ConnectionLineType, ConnectionMode } from './connection'
import { DefaultEdgeOptions, Edge, GraphEdge } from './edge'
import { GraphNode, CoordinateExtent, Node } from './node'
import { D3Selection, D3Zoom, D3ZoomHandler, KeyCode, PanOnScrollMode } from './zoom'
import { D3Selection, D3Zoom, D3ZoomHandler, KeyCode, PanOnScrollMode, Viewport } from './zoom'
import { FlowHooks, FlowHooksOn } from './hooks'
import { NodeChange, EdgeChange } from './changes'
import { StartHandle, HandleType } from './handle'
@@ -44,7 +33,7 @@ export interface State<NodeData = ElementData, EdgeData = ElementData>
/** viewport dimensions */
dimensions: Dimensions
/** viewport transform x, y, z */
transform: Transform
viewport: Viewport
onlyRenderVisibleElements: boolean
defaultPosition: [number, number]
+11 -8
View File
@@ -5,6 +5,9 @@ export type D3Zoom = ZoomBehavior<HTMLDivElement, unknown>
export type D3Selection = Selection<HTMLDivElement, unknown, any, any>
export type D3ZoomHandler = (this: HTMLDivElement, event: any, d: unknown) => void
/** Transform x, y, z */
export type Viewport = { x: number; y: number; zoom: number }
export type KeyCode = number | string
export enum PanOnScrollMode {
@@ -13,7 +16,7 @@ export enum PanOnScrollMode {
Horizontal = 'horizontal',
}
export type UseZoomPanHelperOptions = {
export type ViewportFuncsOptions = {
duration?: number
}
@@ -27,7 +30,7 @@ export type FitViewParams = {
y?: number
}
nodes?: string[]
} & UseZoomPanHelperOptions
} & ViewportFuncsOptions
export type FlowTransform = {
x: number
@@ -35,11 +38,11 @@ export type FlowTransform = {
zoom: number
}
export type SetCenterOptions = UseZoomPanHelperOptions & {
export type SetCenterOptions = ViewportFuncsOptions & {
zoom?: number
}
export type FitBoundsOptions = UseZoomPanHelperOptions & {
export type FitBoundsOptions = ViewportFuncsOptions & {
padding?: number
}
@@ -47,12 +50,12 @@ 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 ZoomInOut = (options?: ViewportFuncsOptions) => void
export type ZoomTo = (zoomLevel: number, options?: ViewportFuncsOptions) => void
export type GetTransform = () => FlowTransform
export type SetTransform = (transform: FlowTransform, options?: UseZoomPanHelperOptions) => void
export type SetTransform = (transform: FlowTransform, options?: ViewportFuncsOptions) => void
export interface Viewport {
export interface ViewportFuncs {
zoomIn: ZoomInOut
zoomOut: ZoomInOut
zoomTo: ZoomTo
+7 -7
View File
@@ -1,5 +1,5 @@
import { rectToBox } from './graph'
import { EdgePositions, GraphEdge, GraphNode, HandleElement, Position, Rect, Transform, XYPosition } from '~/types'
import { EdgePositions, GraphEdge, GraphNode, HandleElement, Position, Rect, Viewport, XYPosition } from '~/types'
export const getHandlePosition = (position: Position, rect: Rect, handle?: HandleElement): XYPosition => {
const x = (handle?.x ?? 0) + rect.x
@@ -83,7 +83,7 @@ interface IsEdgeVisibleParams {
targetHeight: number
width: number
height: number
transform: Transform
viewport: Viewport
}
export function isEdgeVisible({
@@ -95,7 +95,7 @@ export function isEdgeVisible({
targetHeight,
width,
height,
transform,
viewport,
}: IsEdgeVisibleParams): boolean {
const edgeBox = {
x: Math.min(sourcePos.x, targetPos.x),
@@ -113,10 +113,10 @@ export function isEdgeVisible({
}
const viewBox = rectToBox({
x: (0 - transform[0]) / transform[2],
y: (0 - transform[1]) / transform[2],
width: width / transform[2],
height: height / transform[2],
x: (0 - viewport.x) / viewport.zoom,
y: (0 - viewport.y) / viewport.zoom,
width: width / viewport.zoom,
height: height / viewport.zoom,
})
const xOverlap = Math.max(0, Math.min(viewBox.x2, edgeBox.x2) - Math.max(viewBox.x, edgeBox.x))
+14 -9
View File
@@ -16,7 +16,7 @@ import {
Rect,
State,
Store,
Transform,
Viewport,
XYPosition,
XYZPosition,
} from '~/types'
@@ -200,7 +200,7 @@ export const updateEdge = (oldEdge: Edge, newConnection: Connection, elements: E
export const pointToRendererPoint = (
{ x, y }: XYPosition,
[tx, ty, tScale]: Transform,
{ x: tx, y: ty, zoom: tScale }: Viewport,
snapToGrid: boolean,
[snapX, snapY]: [number, number],
) => {
@@ -220,7 +220,7 @@ export const pointToRendererPoint = (
}
export const onLoadProject = (currentStore: Store) => (position: XYPosition) =>
pointToRendererPoint(position, currentStore.transform, currentStore.snapToGrid, currentStore.snapGrid)
pointToRendererPoint(position, currentStore.viewport, currentStore.snapToGrid, currentStore.snapGrid)
const getBoundsOfBoxes = (box1: Box, box2: Box): Box => ({
x: Math.min(box1.x, box2.x),
@@ -261,12 +261,17 @@ export const getRectOfNodes = (nodes: GraphNode[]) => {
return boxToRect(box)
}
export const graphPosToZoomedPos = ({ x, y }: XYPosition, [tx, ty, tScale]: Transform): XYPosition => ({
export const graphPosToZoomedPos = ({ x, y }: XYPosition, { x: tx, y: ty, zoom: tScale }: Viewport): XYPosition => ({
x: x * tScale + tx,
y: y * tScale + ty,
})
export const getNodesInside = (nodes: GraphNode[], rect: Rect, [tx, ty, tScale]: Transform = [0, 0, 1], partially = false) => {
export const getNodesInside = (
nodes: GraphNode[],
rect: Rect,
{ x: tx, y: ty, zoom: tScale }: Viewport = { x: 0, y: 0, zoom: 1 },
partially = false,
) => {
const rBox = rectToBox({
x: (rect.x - tx) / tScale,
y: (rect.y - ty) / tScale,
@@ -309,8 +314,8 @@ export const onLoadToObject = (store: State) => (): FlowExportObject => {
JSON.stringify({
nodes: store.nodes,
edges: store.edges,
position: [store.transform[0], store.transform[1]],
zoom: store.transform[2],
position: [store.viewport.x, store.viewport.y],
zoom: store.viewport.zoom,
} as FlowExportObject),
)
}
@@ -326,7 +331,7 @@ export const getTransformForBounds = (
x?: number
y?: number
} = { x: 0, y: 0 },
): Transform => {
): Viewport => {
const xZoom = width / (bounds.width * (1 + padding))
const yZoom = height / (bounds.height * (1 + padding))
const zoom = Math.min(xZoom, yZoom)
@@ -336,7 +341,7 @@ export const getTransformForBounds = (
const x = width / 2 - boundsCenterX * clampedZoom + (offset.x ?? 0)
const y = height / 2 - boundsCenterY * clampedZoom + (offset.y ?? 0)
return [x, y, clampedZoom]
return { x, y, zoom: clampedZoom }
}
export const getXYZPos = (parentPos: XYZPosition, computedPosition: XYZPosition): XYZPosition => {
+1 -1
View File
@@ -7,7 +7,7 @@
"open": "cypress open-ct"
},
"dependencies": {
"@braks/vue-flow": "^0.4.0-17"
"@braks/vue-flow": "^0.4.2-3"
},
"devDependencies": {
"@cypress/vite-dev-server": "^2.2.1",
+2 -14
View File
@@ -1196,18 +1196,6 @@
"@vueuse/core" "^7.0.1"
vue-demi latest
"@braks/vue-flow@^0.4.0-17":
version "0.4.1"
resolved "https://registry.yarnpkg.com/@braks/vue-flow/-/vue-flow-0.4.1.tgz#81ce05e6a47f7232f09ec78f93ea371105b85b43"
integrity sha512-EOTo/zAvtnP7ZtXVJ8DSKJrQWMoajVVaFgzx5/ghVTn2KWU846S2AIaahy2L95x8gyzob0YmsJ73tkgoPgmuJg==
dependencies:
"@braks/revue-draggable" "^0.4.2"
"@types/d3" "^7.1.0"
"@vueuse/core" "^7.5.5"
d3 "^7.1.1"
d3-selection "^3.0.0"
d3-zoom "^3.0.0"
"@cypress/mount-utils@1.0.2":
version "1.0.2"
resolved "https://registry.yarnpkg.com/@cypress/mount-utils/-/mount-utils-1.0.2.tgz#afbc4f8c350b7cd86edc5ad0db0cbe1e0181edc8"
@@ -2352,7 +2340,7 @@
ora "^5.4.1"
upath "^2.0.1"
"@vueuse/core@^7.0.1", "@vueuse/core@^7.5.5":
"@vueuse/core@^7.0.1":
version "7.7.1"
resolved "https://registry.yarnpkg.com/@vueuse/core/-/core-7.7.1.tgz#fc284f4103de73c7fb79bc06579d8066790db511"
integrity sha512-PRRgbATMpoeUmkCEBtUeJgOwtew8s+4UsEd+Pm7MhkjL2ihCNrSqxNVtM6NFE4uP2sWnkGcZpCjPuNSxowJ1Ow==
@@ -3550,7 +3538,7 @@ d3-zoom@3, d3-zoom@^3.0.0:
d3-selection "2 - 3"
d3-transition "2 - 3"
d3@^7.1.1, d3@^7.4.2:
d3@^7.4.2:
version "7.4.2"
resolved "https://registry.yarnpkg.com/d3/-/d3-7.4.2.tgz#c5d1001459dc9517bc774949f71b95e8735b3b4e"
integrity sha512-7VK+QBAWtNDbP2EU/ThkXgjd0u1MsXYYgCK2ElQ4BBWh0usE75tHVVeYx47m2pqQEy4isYKAA0tAFSln0l+9EQ==