mirror of
https://github.com/projectstorm/react-diagrams.git
synced 2025-08-15 01:00:40 +08:00
Merge pull request #915 from carb/master
Add optional PanAndZoomCanvasAction for trackpad support
This commit is contained in:
@ -14,6 +14,7 @@ import demo_listeners from './demo-listeners';
|
||||
import demo_zoom from './demo-zoom-to-fit';
|
||||
import demo_zoom_nodes from './demo-zoom-to-fit-nodes';
|
||||
import demo_canvas_drag from './demo-canvas-drag';
|
||||
import demo_pan_and_zoom from './demo-pan-and-zoom';
|
||||
import demo_dynamic_ports from './demo-dynamic-ports';
|
||||
import demo_labels from './demo-labelled-links';
|
||||
|
||||
@ -26,5 +27,6 @@ export const EventsAndListeners = demo_listeners;
|
||||
export const ZoomToFit = demo_zoom;
|
||||
export const ZoomToFitSelectNodes = demo_zoom_nodes;
|
||||
export const CanvasDrag = demo_canvas_drag;
|
||||
export const CanvasPanAndZoom = demo_pan_and_zoom;
|
||||
export const DynamicPorts = demo_dynamic_ports;
|
||||
export const LinksWithLabels = demo_labels;
|
||||
|
57
diagrams-demo-gallery/demos/demo-pan-and-zoom/index.tsx
Normal file
57
diagrams-demo-gallery/demos/demo-pan-and-zoom/index.tsx
Normal file
@ -0,0 +1,57 @@
|
||||
import * as React from 'react';
|
||||
import createEngine, { DiagramModel, DefaultNodeModel } from '@projectstorm/react-diagrams';
|
||||
import { DemoButton, DemoWorkspaceWidget } from '../helpers/DemoWorkspaceWidget';
|
||||
import { CanvasWidget } from '@projectstorm/react-canvas-core';
|
||||
import { DemoCanvasWidget } from '../helpers/DemoCanvasWidget';
|
||||
|
||||
/**
|
||||
* Tests the pan and zoom action, which is intended as a trackpad/mobile
|
||||
* alternative to the standard ZoomCanvasAction
|
||||
*/
|
||||
class CanvasPanAndZoomToggle extends React.Component<any, any> {
|
||||
render() {
|
||||
const { engine } = this.props;
|
||||
return (
|
||||
<DemoCanvasWidget>
|
||||
<CanvasWidget engine={engine} />
|
||||
</DemoCanvasWidget>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default () => {
|
||||
/**
|
||||
* 1) setup the diagram engine
|
||||
* PandAndZoomCanvasAction and ZoomCanvasAction are mutually exclusive
|
||||
* If both are enabled, ZoomCanvasAction will override.
|
||||
*/
|
||||
var engine = createEngine({
|
||||
registerDefaultPanAndZoomCanvasAction: true,
|
||||
registerDefaultZoomCanvasAction: false
|
||||
});
|
||||
|
||||
//2) setup the diagram model
|
||||
var model = new DiagramModel();
|
||||
|
||||
//3-A) create a default node
|
||||
var node1 = new DefaultNodeModel('Node 1', 'rgb(0,192,255)');
|
||||
var port1 = node1.addOutPort('Out');
|
||||
node1.setPosition(100, 100);
|
||||
|
||||
//3-B) create another default node
|
||||
var node2 = new DefaultNodeModel('Node 2', 'rgb(192,255,0)');
|
||||
var port2 = node2.addInPort('In');
|
||||
node2.setPosition(400, 100);
|
||||
|
||||
//3-C) link the 2 nodes together
|
||||
var link1 = port1.link(port2);
|
||||
|
||||
//4) add the models to the root graph
|
||||
model.addAll(node1, node2, link1);
|
||||
|
||||
//5) load model into engine
|
||||
engine.setModel(model);
|
||||
|
||||
//6) render the diagram!
|
||||
return <CanvasPanAndZoomToggle engine={engine} model={model} />;
|
||||
};
|
@ -8,6 +8,7 @@ import { MouseEvent } from 'react';
|
||||
import { BaseModel } from './core-models/BaseModel';
|
||||
import { Point } from '@projectstorm/geometry';
|
||||
import { ActionEventBus } from './core-actions/ActionEventBus';
|
||||
import { PanAndZoomCanvasAction } from './actions/PanAndZoomCanvasAction';
|
||||
import { ZoomCanvasAction } from './actions/ZoomCanvasAction';
|
||||
import { DeleteItemsAction } from './actions/DeleteItemsAction';
|
||||
import { StateMachine } from './core-state/StateMachine';
|
||||
@ -25,6 +26,7 @@ export interface CanvasEngineListener extends BaseListener {
|
||||
*/
|
||||
export interface CanvasEngineOptions {
|
||||
registerDefaultDeleteItemsAction?: boolean;
|
||||
registerDefaultPanAndZoomCanvasAction?: boolean;
|
||||
registerDefaultZoomCanvasAction?: boolean;
|
||||
/**
|
||||
* Defines the debounce wait time in milliseconds if > 0
|
||||
@ -62,6 +64,8 @@ export class CanvasEngine<
|
||||
};
|
||||
if (this.options.registerDefaultZoomCanvasAction === true) {
|
||||
this.eventBus.registerAction(new ZoomCanvasAction());
|
||||
} else if (this.options.registerDefaultPanAndZoomCanvasAction === true) {
|
||||
this.eventBus.registerAction(new PanAndZoomCanvasAction());
|
||||
}
|
||||
if (this.options.registerDefaultDeleteItemsAction === true) {
|
||||
this.eventBus.registerAction(new DeleteItemsAction());
|
||||
|
@ -0,0 +1,64 @@
|
||||
import { WheelEvent } from 'react';
|
||||
import { Action, ActionEvent, InputType } from '../core-actions/Action';
|
||||
|
||||
export interface PanAndZoomCanvasActionOptions {
|
||||
inverseZoom?: boolean;
|
||||
}
|
||||
|
||||
export class PanAndZoomCanvasAction extends Action {
|
||||
constructor(options: PanAndZoomCanvasActionOptions = {}) {
|
||||
super({
|
||||
type: InputType.MOUSE_WHEEL,
|
||||
fire: (actionEvent: ActionEvent<WheelEvent>) => {
|
||||
const { event } = actionEvent;
|
||||
// we can block layer rendering because we are only targeting the transforms
|
||||
for (let layer of this.engine.getModel().getLayers()) {
|
||||
layer.allowRepaint(false);
|
||||
}
|
||||
|
||||
const model = this.engine.getModel();
|
||||
event.stopPropagation();
|
||||
if (event.ctrlKey) {
|
||||
// Pinch and zoom gesture
|
||||
const oldZoomFactor = this.engine.getModel().getZoomLevel() / 100;
|
||||
|
||||
let scrollDelta = options.inverseZoom ? event.deltaY : -event.deltaY;
|
||||
scrollDelta /= 3;
|
||||
|
||||
if (model.getZoomLevel() + scrollDelta > 10) {
|
||||
model.setZoomLevel(model.getZoomLevel() + scrollDelta);
|
||||
}
|
||||
|
||||
const zoomFactor = model.getZoomLevel() / 100;
|
||||
|
||||
const boundingRect = event.currentTarget.getBoundingClientRect();
|
||||
const clientWidth = boundingRect.width;
|
||||
const clientHeight = boundingRect.height;
|
||||
// compute difference between rect before and after scroll
|
||||
const widthDiff = clientWidth * zoomFactor - clientWidth * oldZoomFactor;
|
||||
const heightDiff = clientHeight * zoomFactor - clientHeight * oldZoomFactor;
|
||||
// compute mouse coords relative to canvas
|
||||
const clientX = event.clientX - boundingRect.left;
|
||||
const clientY = event.clientY - boundingRect.top;
|
||||
|
||||
// compute width and height increment factor
|
||||
const xFactor = (clientX - model.getOffsetX()) / oldZoomFactor / clientWidth;
|
||||
const yFactor = (clientY - model.getOffsetY()) / oldZoomFactor / clientHeight;
|
||||
|
||||
model.setOffset(model.getOffsetX() - widthDiff * xFactor, model.getOffsetY() - heightDiff * yFactor);
|
||||
} else {
|
||||
// Pan gesture
|
||||
let yDelta = options.inverseZoom ? -event.deltaY : event.deltaY;
|
||||
let xDelta = options.inverseZoom ? -event.deltaX : event.deltaX;
|
||||
model.setOffset(model.getOffsetX() - xDelta, model.getOffsetY() - yDelta);
|
||||
}
|
||||
this.engine.repaintCanvas();
|
||||
|
||||
// re-enable rendering
|
||||
for (let layer of this.engine.getModel().getLayers()) {
|
||||
layer.allowRepaint(true);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@ -41,3 +41,4 @@ export * from './states/MoveItemsState';
|
||||
|
||||
export * from './actions/DeleteItemsAction';
|
||||
export * from './actions/ZoomCanvasAction';
|
||||
export * from './actions/PanAndZoomCanvasAction';
|
||||
|
Reference in New Issue
Block a user