Use d3-zoom to handle zoom and pan

This commit is contained in:
mvdbeek
2023-01-09 17:15:46 +01:00
parent e78a66c629
commit dfd0bc4e81
5 changed files with 192 additions and 38 deletions
+1
View File
@@ -49,6 +49,7 @@
"core-js": "^3.21.0",
"csv-parse": "^5.3.0",
"d3": "3",
"d3-zoom": "^3.0.0",
"date-fns": "^2.28.0",
"date-fns-tz": "^1.3.3",
"decode-uri-component": "^0.2.1",
@@ -0,0 +1,85 @@
<script setup>
import { zoom, zoomIdentity } from "d3-zoom";
import { ref, onMounted, templateRef } from "vue";
import { pointer, select } from "d3-selection";
const props = defineProps({
zoom: {
type: Number,
default: 1,
},
minZoom: {
type: Number,
default: 0.2,
},
maxZoom: {
type: Number,
default: 5,
},
});
const emit = defineEmits(["transform"]);
const viewport = ref(null);
const transform = { x: 0, y: 0, zoom: props.zoom };
let isZoomingOrPanning = false;
const panOnScrollSpeed = 0.5;
const eventToTransform = (transform) => {
return `translate(${transform.x}px, ${transform.y}px) scale(${transform.k})`;
};
onMounted(() => {
const d3Zoom = zoom().scaleExtent([props.minZoom, props.maxZoom]);
const d3Selection = select(viewport.value).call(d3Zoom);
const d3ZoomHandler = d3Selection.on("wheel.zoom");
const updatedTransform = zoomIdentity.translate(transform.x, transform.y).scale(transform.zoom);
d3Zoom.transform(d3Selection, updatedTransform);
d3Selection
.on("wheel", (event) => {
event.preventDefault();
event.stopImmediatePropagation();
const currentZoom = d3Selection.property("__zoom").k || 1;
// if (event.ctrlKey && zoomOnPinch) {
// // 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 point = pointer(event);
const zoom = currentZoom * 2 ** pinchDelta;
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 = event.deltaX * deltaNormalize;
// const deltaY = event.deltaY * deltaNormalize;
// d3Zoom.translateBy(
// d3Selection,
// -(deltaX / currentZoom) * panOnScrollSpeed,
// -(deltaY / currentZoom) * panOnScrollSpeed
// );
})
.on("wheel.zoom", null);
d3Zoom.on("start", (event) => {
isZoomingOrPanning = true;
emit("transform", event.transform);
});
d3Zoom.on("end", (event) => {
isZoomingOrPanning = false;
emit("transform", event.transform);
});
});
</script>
<template>
<div ref="viewport">
<slot></slot>
</div>
</template>
@@ -7,47 +7,46 @@
@reset-all="onResetAll"
@update:pan="onPan" />
<div ref="el" class="canvas-viewport" @drop.prevent>
<PinchScrollZoom
<d3-zoom
id="canvas-container"
ref="zoomControl"
:width="position.width"
:height="position.height"
:within="false"
minScale="0.1"
maxScale="10"
throttleDelay="16"
wheelVelocity="0.0015"
@transform="onTransform"
@dragging="onDrag"
@scaling="onScale">
<svg class="canvas-svg" width="100%" height="100%" ref="svg">
<g class="zoomable">
<svg :width="position.width" :height="position.height" class="canvas-svg">
<svg>
<raw-connector v-if="draggingConnection" :position="draggingConnection"></raw-connector>
<terminal-connector
v-for="connection in connections"
:key="connection.id"
:connection="connection"></terminal-connector>
</g>
</svg>
</svg>
<WorkflowNode
v-for="(step, key) in steps"
:id="key"
:key="key"
:name="step.name"
:type="step.type"
:content-id="step.content_id"
:step="step"
:datatypes-mapper="datatypesMapper"
:get-manager="getManager"
:activeNodeId="activeNodeId"
:root-offset="position"
:scale="zoomLevel"
@pan-by="onPan"
@stopDragging="onStopDragging"
@onDragConnector="onDragConnector"
@onActivate="onActivate"
@onRemove="onRemove"
v-on="$listeners" />
</PinchScrollZoom>
<div ref="nodes" class="nodeArea">
<WorkflowNode
v-for="(step, key) in steps"
:id="key"
:key="key"
:name="step.name"
:type="step.type"
:content-id="step.content_id"
:step="step"
:datatypes-mapper="datatypesMapper"
:get-manager="getManager"
:activeNodeId="activeNodeId"
:root-offset="position"
:scale="zoomLevel"
@pan-by="onPan"
@stopDragging="onStopDragging"
@onDragConnector="onDragConnector"
@onActivate="onActivate"
@onRemove="onRemove"
v-on="$listeners" />
</div>
</d3-zoom>
</div>
<Minimap
:nodes="nodes"
@@ -65,10 +64,9 @@ import WorkflowNode from "./Node";
import RawConnector from "./Connector";
import TerminalConnector from "./TerminalConnector";
import Minimap from "./Minimap.vue";
import SvgPanZoom from "vue-svg-pan-zoom";
import { reactive, ref } from "vue";
import { useElementBounding } from "@vueuse/core";
import PinchScrollZoom, { PinchScrollZoomEmitData } from "@coddicat/vue-pinch-scroll-zoom";
import D3Zoom from "./D3Zoom.vue";
export default {
setup() {
@@ -77,13 +75,12 @@ export default {
return { el, position };
},
components: {
D3Zoom,
RawConnector,
SvgPanZoom,
TerminalConnector,
WorkflowNode,
Minimap,
ZoomControl,
PinchScrollZoom,
},
data() {
return {
@@ -92,6 +89,7 @@ export default {
svgpanzoom: null,
transform: null,
pan: { x: 0, y: 0 },
transform: "",
};
},
props: {
@@ -113,6 +111,11 @@ export default {
},
},
methods: {
onTransform(transform) {
const cssTransform = `translate(${transform.x}px, ${transform.y}px) scale(${transform.k})`;
this.transform = transform.toString();
this.$refs.nodes.style.transform = cssTransform;
},
onPan(pan) {
console.log("onPan", pan);
this.pan = pan;
+8 -5
View File
@@ -6,12 +6,15 @@
// not sure if hack or not ...
overflow: visible;
display: block;
}
.nodeArea {
position: absolute;
position: absolute;
top: 0px;
left: 0px;
left: 0px
}
.nodeArea {
position: absolute;
top: 0px;
left: 0px;
}
.workflow-client {
.workflow-panel {
+63 -1
View File
@@ -3079,7 +3079,7 @@ char-regex@^1.0.2:
resolved "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz"
integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==
chokidar@3.5.3, "chokidar@>=3.0.0 <4.0.0", chokidar@^2.0.0, chokidar@^3.5.1, chokidar@^3.5.3:
chokidar@3.5.3, "chokidar@>=3.0.0 <4.0.0", chokidar@^2.0.0, chokidar@^3.5.1, chokidar@^3.5.2, chokidar@^3.5.3:
version "3.5.3"
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd"
integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==
@@ -3675,6 +3675,68 @@ custom-event@~1.0.0:
resolved "https://registry.npmjs.org/custom-event/-/custom-event-1.0.1.tgz"
integrity sha512-GAj5FOq0Hd+RsCGVJxZuKaIDXDf3h6GQoNEjFgbLLI/trgtavwUbSnZ5pVfg27DVCaWjIohryS0JFwIJyT2cMg==
"d3-color@1 - 3":
version "3.1.0"
resolved "https://registry.yarnpkg.com/d3-color/-/d3-color-3.1.0.tgz#395b2833dfac71507f12ac2f7af23bf819de24e2"
integrity sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==
"d3-dispatch@1 - 3":
version "3.0.1"
resolved "https://registry.yarnpkg.com/d3-dispatch/-/d3-dispatch-3.0.1.tgz#5fc75284e9c2375c36c839411a0cf550cbfc4d5e"
integrity sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==
"d3-drag@2 - 3":
version "3.0.0"
resolved "https://registry.yarnpkg.com/d3-drag/-/d3-drag-3.0.0.tgz#994aae9cd23c719f53b5e10e3a0a6108c69607ba"
integrity sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==
dependencies:
d3-dispatch "1 - 3"
d3-selection "3"
"d3-ease@1 - 3":
version "3.0.1"
resolved "https://registry.yarnpkg.com/d3-ease/-/d3-ease-3.0.1.tgz#9658ac38a2140d59d346160f1f6c30fda0bd12f4"
integrity sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==
"d3-interpolate@1 - 3":
version "3.0.1"
resolved "https://registry.yarnpkg.com/d3-interpolate/-/d3-interpolate-3.0.1.tgz#3c47aa5b32c5b3dfb56ef3fd4342078a632b400d"
integrity sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==
dependencies:
d3-color "1 - 3"
"d3-selection@2 - 3", d3-selection@3:
version "3.0.0"
resolved "https://registry.yarnpkg.com/d3-selection/-/d3-selection-3.0.0.tgz#c25338207efa72cc5b9bd1458a1a41901f1e1b31"
integrity sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==
"d3-timer@1 - 3":
version "3.0.1"
resolved "https://registry.yarnpkg.com/d3-timer/-/d3-timer-3.0.1.tgz#6284d2a2708285b1abb7e201eda4380af35e63b0"
integrity sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==
"d3-transition@2 - 3":
version "3.0.1"
resolved "https://registry.yarnpkg.com/d3-transition/-/d3-transition-3.0.1.tgz#6869fdde1448868077fdd5989200cb61b2a1645f"
integrity sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==
dependencies:
d3-color "1 - 3"
d3-dispatch "1 - 3"
d3-ease "1 - 3"
d3-interpolate "1 - 3"
d3-timer "1 - 3"
d3-zoom@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/d3-zoom/-/d3-zoom-3.0.0.tgz#d13f4165c73217ffeaa54295cd6969b3e7aee8f3"
integrity sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==
dependencies:
d3-dispatch "1 - 3"
d3-drag "2 - 3"
d3-interpolate "1 - 3"
d3-selection "2 - 3"
d3-transition "2 - 3"
d3@3:
version "3.5.17"
resolved "https://registry.npmjs.org/d3/-/d3-3.5.17.tgz"