react-financial-charts
Version:
React charts specific to finance.
489 lines • 19.5 kB
JavaScript
var __rest = (this && this.__rest) || function (s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function")
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
t[p[i]] = s[p[i]];
}
return t;
};
import { event as d3Event, mouse, select, touches } from "d3-selection";
import * as React from "react";
import { d3Window, getTouchProps, isDefined, MOUSEENTER, MOUSELEAVE, MOUSEMOVE, mousePosition, MOUSEUP, noop, TOUCHEND, TOUCHMOVE, touchPosition, } from "./utils";
import { getCurrentCharts } from "./utils/ChartDataUtil";
export class EventCapture extends React.Component {
constructor(props) {
super(props);
this.dx = 0;
this.dy = 0;
this.mouseInside = false;
this.mouseInteraction = true;
this.ref = React.createRef();
this.handleEnter = () => {
const { onMouseEnter } = this.props;
if (onMouseEnter === undefined) {
return;
}
const e = d3Event;
this.mouseInside = true;
if (!this.state.panInProgress
&& !this.state.dragInProgress) {
const win = d3Window(this.ref.current);
select(win)
.on(MOUSEMOVE, this.handleMouseMove);
}
onMouseEnter(e);
};
this.handleLeave = (e) => {
const { onMouseLeave } = this.props;
if (onMouseLeave === undefined) {
return;
}
this.mouseInside = false;
if (!this.state.panInProgress
&& !this.state.dragInProgress) {
const win = d3Window(this.ref.current);
select(win)
.on(MOUSEMOVE, null);
}
onMouseLeave(e);
};
this.handleWheel = (e) => {
const { zoom, onZoom } = this.props;
const { panInProgress } = this.state;
const yZoom = Math.abs(e.deltaY) > Math.abs(e.deltaX) && Math.abs(e.deltaY) > 0;
const mouseXY = mousePosition(e);
e.preventDefault();
if (zoom && this.focus && yZoom && !panInProgress) {
const zoomDir = e.deltaY > 0 ? 1 : -1;
if (onZoom !== undefined) {
onZoom(zoomDir, mouseXY, e);
}
}
else if (this.focus) {
if (this.shouldPan()) {
// pan already in progress
const { panStartXScale, chartsToPan, } = this.state.panStart;
this.lastNewPos = mouseXY;
this.panHappened = true;
this.dx -= e.deltaX;
this.dy += e.deltaY;
const dxdy = { dx: this.dx, dy: this.dy };
this.props.onPan(mouseXY, panStartXScale, dxdy, chartsToPan, e);
}
else {
const { xScale, chartConfig } = this.props;
const currentCharts = getCurrentCharts(chartConfig, mouseXY);
this.dx = 0;
this.dy = 0;
this.setState({
panInProgress: true,
panStart: {
panStartXScale: xScale,
panOrigin: mouseXY,
chartsToPan: currentCharts,
},
});
}
this.queuePanEnd();
}
};
this.handleMouseMove = () => {
const e = d3Event;
const { onMouseMove, mouseMove } = this.props;
if (this.mouseInteraction &&
mouseMove &&
!this.state.panInProgress) {
const newPos = mouse(this.ref.current);
if (onMouseMove !== undefined) {
onMouseMove(newPos, "mouse", e);
}
}
};
this.handleClick = (e) => {
const mouseXY = mousePosition(e);
const { onClick, onDoubleClick } = this.props;
if (!this.panHappened && !this.dragHappened) {
if (this.clicked && onDoubleClick !== undefined) {
onDoubleClick(mouseXY, e);
this.clicked = false;
}
else if (onClick !== undefined) {
onClick(mouseXY, e);
this.clicked = true;
setTimeout(() => {
if (this.clicked) {
this.clicked = false;
}
}, 400);
}
}
};
this.handleRightClick = (e) => {
e.stopPropagation();
e.preventDefault();
const { onContextMenu, onPanEnd } = this.props;
const mouseXY = mousePosition(e, this.ref.current.getBoundingClientRect());
if (isDefined(this.state.panStart)) {
const { panStartXScale, panOrigin, chartsToPan } = this.state.panStart;
if (this.panHappened) {
onPanEnd(mouseXY, panStartXScale, panOrigin, chartsToPan, e);
}
const win = d3Window(this.ref.current);
select(win)
.on(MOUSEMOVE, null)
.on(MOUSEUP, null);
this.setState({
panInProgress: false,
panStart: null,
});
}
if (onContextMenu !== undefined) {
onContextMenu(mouseXY, e);
}
};
this.handleDrag = () => {
const e = d3Event;
if (this.props.onDrag) {
this.dragHappened = true;
const mouseXY = mouse(this.ref.current);
this.props.onDrag({
startPos: this.state.dragStartPosition,
mouseXY,
}, e);
}
};
this.handleDragEnd = () => {
const e = d3Event;
const mouseXY = mouse(this.ref.current);
const win = d3Window(this.ref.current);
select(win)
// @ts-ignore
.on(MOUSEMOVE, this.mouseInside ? this.handleMouseMove : null)
.on(MOUSEUP, null);
if (this.dragHappened) {
const { onDragComplete } = this.props;
if (onDragComplete !== undefined) {
onDragComplete({ mouseXY }, e);
}
}
this.setState({
dragInProgress: false,
});
this.mouseInteraction = true;
};
this.canPan = () => {
const { getAllPanConditions } = this.props;
const { pan: initialPanEnabled } = this.props;
const { panEnabled, draggable: somethingSelected, } = getAllPanConditions()
.reduce((returnObj, a) => {
return {
draggable: returnObj.draggable || a.draggable,
panEnabled: returnObj.panEnabled && a.panEnabled,
};
}, {
draggable: false,
panEnabled: initialPanEnabled,
});
return {
panEnabled,
somethingSelected,
};
};
this.handleMouseDown = (e) => {
if (e.button !== 0) {
return;
}
const { xScale, chartConfig, onMouseDown } = this.props;
this.panHappened = false;
this.dragHappened = false;
this.focus = true;
if (!this.state.panInProgress
&& this.mouseInteraction) {
const mouseXY = mousePosition(e);
const currentCharts = getCurrentCharts(chartConfig, mouseXY);
const { panEnabled, somethingSelected, } = this.canPan();
const pan = panEnabled && !somethingSelected;
if (pan) {
this.setState({
panInProgress: pan,
panStart: {
panStartXScale: xScale,
panOrigin: mouseXY,
chartsToPan: currentCharts,
},
});
const win = d3Window(this.ref.current);
select(win)
.on(MOUSEMOVE, this.handlePan)
.on(MOUSEUP, this.handlePanEnd);
}
else if (somethingSelected) {
this.setState({
panInProgress: false,
dragInProgress: true,
panStart: null,
dragStartPosition: mouseXY,
});
const { onDragStart } = this.props;
if (onDragStart !== undefined) {
onDragStart({ startPos: mouseXY }, e);
}
const win = d3Window(this.ref.current);
select(win)
.on(MOUSEMOVE, this.handleDrag)
.on(MOUSEUP, this.handleDragEnd);
}
if (onMouseDown !== undefined) {
onMouseDown(mouseXY, currentCharts, e);
}
}
e.preventDefault();
};
this.shouldPan = () => {
const { pan: panEnabled, onPan } = this.props;
return panEnabled
&& onPan
&& isDefined(this.state.panStart);
};
this.handlePan = () => {
const e = d3Event;
if (this.shouldPan()) {
this.panHappened = true;
const { panStartXScale, panOrigin, chartsToPan } = this.state.panStart;
let dx;
let dy;
let mouseXY;
if (this.mouseInteraction) {
mouseXY = mouse(this.ref.current);
this.lastNewPos = mouseXY;
dx = mouseXY[0] - panOrigin[0];
dy = mouseXY[1] - panOrigin[1];
}
else {
mouseXY = touches(this.ref.current)[0];
this.lastNewPos = mouseXY;
dx = panOrigin[0] - mouseXY[0];
dy = panOrigin[1] - mouseXY[1];
}
this.dx = dx;
this.dy = dy;
this.props.onPan(mouseXY, panStartXScale, { dx, dy }, chartsToPan, e);
}
};
this.handlePanEnd = () => {
const e = d3Event;
const { pan: panEnabled, onPanEnd } = this.props;
if (isDefined(this.state.panStart)) {
const { panStartXScale, chartsToPan } = this.state.panStart;
const win = d3Window(this.ref.current);
select(win)
// @ts-ignore
.on(MOUSEMOVE, this.mouseInside ? this.handleMouseMove : null)
.on(MOUSEUP, null)
.on(TOUCHMOVE, null)
.on(TOUCHEND, null);
if (this.panHappened
&& panEnabled
&& onPanEnd) {
const { dx, dy } = this;
delete this.dx;
delete this.dy;
onPanEnd(this.lastNewPos, panStartXScale, { dx, dy }, chartsToPan, e);
}
this.setState({
panInProgress: false,
panStart: null,
});
}
};
this.handleTouchMove = (e) => {
const { onMouseMove } = this.props;
if (onMouseMove === undefined) {
return;
}
const touchXY = touchPosition(getTouchProps(e.touches[0]), e);
onMouseMove(touchXY, "touch", e);
};
this.handleTouchStart = (e) => {
this.mouseInteraction = false;
const { pan: panEnabled, chartConfig, onMouseMove } = this.props;
const { xScale, onPanEnd } = this.props;
if (e.touches.length === 1) {
this.panHappened = false;
const touchXY = touchPosition(getTouchProps(e.touches[0]), e);
if (onMouseMove !== undefined) {
onMouseMove(touchXY, "touch", e);
}
if (panEnabled) {
const currentCharts = getCurrentCharts(chartConfig, touchXY);
this.setState({
panInProgress: true,
panStart: {
panStartXScale: xScale,
panOrigin: touchXY,
chartsToPan: currentCharts,
},
});
const win = d3Window(this.ref.current);
select(win)
.on(TOUCHMOVE, this.handlePan, false)
.on(TOUCHEND, this.handlePanEnd, false);
}
}
else if (e.touches.length === 2) {
// pinch zoom begin
// do nothing pinch zoom is handled in handleTouchMove
const { panInProgress, panStart } = this.state;
if (panInProgress && panEnabled && onPanEnd) {
const { panStartXScale, panOrigin, chartsToPan } = panStart;
const win = d3Window(this.ref.current);
select(win)
// @ts-ignore
.on(MOUSEMOVE, this.mouseInside ? this.handleMouseMove : null)
.on(MOUSEUP, null)
.on(TOUCHMOVE, this.handlePinchZoom, false)
.on(TOUCHEND, this.handlePinchZoomEnd, false);
const touch1Pos = touchPosition(getTouchProps(e.touches[0]), e);
const touch2Pos = touchPosition(getTouchProps(e.touches[1]), e);
if (this.panHappened
&& panEnabled
&& onPanEnd) {
onPanEnd(this.lastNewPos, panStartXScale, panOrigin, chartsToPan, e);
}
this.setState({
panInProgress: false,
pinchZoomStart: {
xScale,
touch1Pos,
touch2Pos,
range: xScale.range(),
chartsToPan,
},
});
}
}
};
this.handlePinchZoom = () => {
const e = d3Event;
const [touch1Pos, touch2Pos] = touches(this.ref.current);
const { xScale, zoom: zoomEnabled, onPinchZoom } = this.props;
const _a = this.state.pinchZoomStart, { chartsToPan } = _a, initialPinch = __rest(_a, ["chartsToPan"]);
if (zoomEnabled && onPinchZoom) {
onPinchZoom(initialPinch, {
touch1Pos,
touch2Pos,
xScale,
}, e);
}
};
this.handlePinchZoomEnd = () => {
const e = d3Event;
const win = d3Window(this.ref.current);
select(win)
.on(TOUCHMOVE, null)
.on(TOUCHEND, null);
const { zoom: zoomEnabled, onPinchZoomEnd } = this.props;
const _a = this.state.pinchZoomStart, { chartsToPan } = _a, initialPinch = __rest(_a, ["chartsToPan"]);
if (zoomEnabled && onPinchZoomEnd) {
onPinchZoomEnd(initialPinch, e);
}
this.setState({
pinchZoomStart: undefined,
});
};
this.setCursorClass = (cursorOverrideClass) => {
if (cursorOverrideClass !== this.state.cursorOverrideClass) {
this.setState({
cursorOverrideClass,
});
}
};
this.focus = props.focus;
this.state = {
panInProgress: false,
};
}
componentDidMount() {
const { disableInteraction } = this.props;
const { current } = this.ref;
if (current === null) {
return;
}
if (!disableInteraction) {
select(current)
.on(MOUSEENTER, this.handleEnter)
.on(MOUSELEAVE, this.handleLeave);
// @ts-ignore
current.addEventListener("wheel", this.handleWheel, { passive: false });
}
}
componentDidUpdate() {
this.componentDidMount();
}
componentWillUnmount() {
const { disableInteraction } = this.props;
const { current } = this.ref;
if (current === null) {
return;
}
if (!disableInteraction) {
select(current)
.on(MOUSEENTER, null)
.on(MOUSELEAVE, null);
const win = d3Window(current);
select(win)
.on(MOUSEMOVE, null);
// @ts-ignore
current.removeEventListener("wheel", this.handleWheel, { passive: false });
}
}
queuePanEnd() {
if (isDefined(this.panEndTimeout)) {
clearTimeout(this.panEndTimeout);
}
this.panEndTimeout = setTimeout(() => {
this.handlePanEnd();
}, 100);
}
cancelDrag() {
const win = d3Window(this.ref.current);
select(win)
// @ts-ignore
.on(MOUSEMOVE, this.mouseInside ? this.handleMouseMove : null)
.on(MOUSEUP, null);
this.setState({
dragInProgress: false,
});
this.mouseInteraction = true;
}
render() {
const { height, width, disableInteraction, useCrossHairStyleCursor } = this.props;
const className = disableInteraction ? undefined :
this.state.cursorOverrideClass !== undefined
? this.state.cursorOverrideClass
: !useCrossHairStyleCursor ? undefined : this.state.panInProgress
? "react-financial-charts-grabbing-cursor"
: "react-financial-charts-crosshair-cursor";
const interactionProps = disableInteraction || {
onMouseDown: this.handleMouseDown,
onClick: this.handleClick,
onContextMenu: this.handleRightClick,
onTouchStart: this.handleTouchStart,
onTouchMove: this.handleTouchMove,
};
return (React.createElement("rect", Object.assign({ ref: this.ref, className: className, width: width, height: height, style: { opacity: 0 } }, interactionProps)));
}
}
EventCapture.defaultProps = {
mouseMove: false,
zoom: false,
pan: false,
panSpeedMultiplier: 1,
focus: false,
onDragComplete: noop,
disableInteraction: false,
};
//# sourceMappingURL=EventCapture.js.map