react-pixi-plot
Version:
A React component rendering a zoomable and draggable PIXI.js scene. Intended to render 2d plots
111 lines • 5.39 kB
JavaScript
import { CustomPIXIComponent, AppContext } from 'react-pixi-fiber';
import * as PIXI from 'pixi.js';
import React from 'react';
import normalizeWheel from 'normalize-wheel';
import { distance } from '../../utils';
import { PixiPlotContext } from '../../PlotContext';
const TYPE = 'ZoomableContainer';
/**
* Sets the zoom of the view.
* @method
* @param delta The magitude of the zoom change.
* @param mousePosition The location of the mouse when the zoom occured.
*/
const zoom = (instance, factorX, factorY, mousePosition) => {
const { scale, position } = instance;
const localPositionBefore = instance.toLocal(mousePosition);
const nextXScale = factorX * scale.x;
const nextYScale = factorY * scale.y;
scale.set(nextXScale, nextYScale);
const localPositionAfter = instance.toLocal(mousePosition);
// reposition the container so that the mouse points to the same position after zooming
const nextXPos = position.x + (localPositionAfter.x - localPositionBefore.x) * scale.x;
const nextYPos = position.y + (localPositionAfter.y - localPositionBefore.y) * scale.y;
position.set(nextXPos, nextYPos);
instance._dispatch({ type: 'zoom', payload: {
position: { x: nextXPos, y: nextYPos },
scale: { x: nextXScale, y: nextYScale },
} });
};
class ZoomableContainerBehavior {
constructor() {
this.customDisplayObject = (props) => {
const instance = new PIXI.Container();
instance._dispatch = props.dispatch;
props.app.view.addEventListener('wheel', (e) => {
this.props = props;
const normalizedEvent = normalizeWheel(e);
const mousePosition = new PIXI.Point();
this.props.app.renderer.
plugins.interaction.
mapPositionToPoint(mousePosition, e.clientX, e.clientY);
const zoomFactor = Math.pow(2, -normalizedEvent.pixelY / 500);
zoom(instance, zoomFactor, zoomFactor, mousePosition);
e.stopPropagation();
e.preventDefault();
});
/*props.app.view.addEventListener('touchstart', (e) => {
const position = new PIXI.Point(e.touches.item(0).clientX, e.touches.item(0).clientY);
const found = props.app.renderer.plugins.interaction.hitTest(position, zoomable);
if (found) zoomable.emit('starttouch', e);
});*/
return instance;
};
this.customWillDetach = (instance) => {
instance.removeAllListeners();
};
this.handleTouchStart = (e) => {
if (e.targetTouches.length === 2) {
const a = this.getTouchPosition(e.targetTouches.item(0));
const b = this.getTouchPosition(e.targetTouches.item(1));
this.initialPinchDistance = distance(a, b);
}
};
this.handleTouchEnd = (e) => {
if (e.data.originalEvent.targetTouches.length === 1) {
delete this.initialPinchDistance;
}
};
this.handleTouchMove = (e) => {
const targetTouches = e.data.originalEvent.targetTouches;
if (targetTouches.length === 2) {
const a = this.getTouchPosition(targetTouches.item(0));
const b = this.getTouchPosition(targetTouches.item(1));
const newPinchDistance = distance(a, b);
const zoomFactor = newPinchDistance / this.initialPinchDistance;
zoom(e.target, zoomFactor, zoomFactor, a);
}
};
this.getTouchPosition = (mouseEvent) => {
const mousePosition = new PIXI.Point();
this.props.app.renderer.plugins.interaction
.mapPositionToPoint(mousePosition, mouseEvent.clientX, mouseEvent.clientY);
return mousePosition;
};
}
customApplyProps(displayObject, oldProps, newProps) {
this.applyDisplayObjectProps(oldProps, newProps);
if (oldProps.dispatch !== newProps.dispatch) {
displayObject._dispatch = newProps.dispatch;
}
if (oldProps.appWidth !== newProps.appWidth || oldProps.appWidth !== newProps.appHeight) {
const updateZoom = () => {
const bounds = displayObject.getBounds();
if (bounds.height !== 0 && bounds.width !== 0) {
zoom(displayObject, newProps.appWidth / displayObject.width, newProps.appHeight / displayObject.height, new PIXI.Point());
}
else {
/**
* Before the first render, the bounds are not calculated, so we wait a bit and try again
*/
setTimeout(updateZoom, 10);
}
};
updateZoom();
}
}
}
const ZoomablePIXI = CustomPIXIComponent(new ZoomableContainerBehavior(), TYPE);
const ZoomableContainer = props => (React.createElement(PixiPlotContext.Consumer, null, context => React.createElement(AppContext.Consumer, null, app => React.createElement(ZoomablePIXI, { app: app, dispatch: context.dispatch, appHeight: context.state.appHeight, appWidth: context.state.appWidth }, props.children))));
export default ZoomableContainer;
//# sourceMappingURL=ZoomableContainer.js.map