Files
xyflow/packages/system/src/xyminimap/index.ts
T
2024-04-11 12:41:27 +02:00

122 lines
3.2 KiB
TypeScript

import { type D3ZoomEvent, zoom } from 'd3-zoom';
import { select, pointer } from 'd3-selection';
import type { CoordinateExtent, PanZoomInstance, Transform } from '../types';
export type XYMinimapInstance = {
update: (params: XYMinimapUpdate) => void;
destroy: () => void;
pointer: typeof pointer;
};
export type XYMinimapParams = {
panZoom: PanZoomInstance;
domNode: Element;
getTransform: () => Transform;
getViewScale: () => number;
};
export type XYMinimapUpdate = {
translateExtent: CoordinateExtent;
width: number;
height: number;
inversePan?: boolean;
zoomStep?: number;
pannable?: boolean;
zoomable?: boolean;
};
export function XYMinimap({ domNode, panZoom, getTransform, getViewScale }: XYMinimapParams) {
const selection = select(domNode);
function update({
translateExtent,
width,
height,
zoomStep = 10,
pannable = true,
zoomable = true,
inversePan = false,
}: XYMinimapUpdate) {
const zoomHandler = (event: D3ZoomEvent<SVGSVGElement, any>) => {
const transform = getTransform();
if (event.sourceEvent.type !== 'wheel' || !panZoom) {
return;
}
const pinchDelta =
-event.sourceEvent.deltaY *
(event.sourceEvent.deltaMode === 1 ? 0.05 : event.sourceEvent.deltaMode ? 1 : 0.002) *
zoomStep;
const nextZoom = transform[2] * Math.pow(2, pinchDelta);
panZoom.scaleTo(nextZoom);
};
let panStart = [0, 0];
const panStartHandler = (event: D3ZoomEvent<HTMLDivElement, any>) => {
if (event.sourceEvent.type === 'mousedown' || event.sourceEvent.type === 'touchstart') {
panStart = [
event.sourceEvent.clientX ?? event.sourceEvent.touches[0].clientX,
event.sourceEvent.clientY ?? event.sourceEvent.touches[0].clientY,
];
}
};
const panHandler = (event: D3ZoomEvent<HTMLDivElement, any>) => {
const transform = getTransform();
if ((event.sourceEvent.type !== 'mousemove' && event.sourceEvent.type !== 'touchmove') || !panZoom) {
return;
}
const panCurrent = [
event.sourceEvent.clientX ?? event.sourceEvent.touches[0].clientX,
event.sourceEvent.clientY ?? event.sourceEvent.touches[0].clientY,
];
const panDelta = [panCurrent[0] - panStart[0], panCurrent[1] - panStart[1]];
panStart = panCurrent;
const moveScale = getViewScale() * Math.max(transform[2], Math.log(transform[2])) * (inversePan ? -1 : 1);
const position = {
x: transform[0] - panDelta[0] * moveScale,
y: transform[1] - panDelta[1] * moveScale,
};
const extent: CoordinateExtent = [
[0, 0],
[width, height],
];
panZoom.setViewportConstrained(
{
x: position.x,
y: position.y,
zoom: transform[2],
},
extent,
translateExtent
);
};
const zoomAndPanHandler = zoom()
.on('start', panStartHandler)
// @ts-ignore
.on('zoom', pannable ? panHandler : null)
// @ts-ignore
.on('zoom.wheel', zoomable ? zoomHandler : null);
selection.call(zoomAndPanHandler, {});
}
function destroy() {
selection.on('zoom', null);
}
return {
update,
destroy,
pointer,
};
}