UNPKG

react-financial-charts

Version:
489 lines 19.5 kB
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