react-financial-charts
Version:
React charts specific to finance.
877 lines • 37.4 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 { extent as d3Extent, max, min } from "d3-array";
import * as PropTypes from "prop-types";
import * as React from "react";
import { clearCanvas, functor, head, identity, isDefined, isNotDefined, last, noop, shallowEqual, } from "./utils";
import { mouseBasedZoomAnchor, } from "./utils/zoomBehavior";
import { getChartConfigWithUpdatedYScales, getCurrentCharts, getCurrentItem, getNewChartConfig } from "./utils/ChartDataUtil";
import { EventCapture } from "./EventCapture";
import { CanvasContainer } from "./CanvasContainer";
import evaluator from "./scale/evaluator";
const CANDIDATES_FOR_RESET = [
"seriesName",
];
function shouldResetChart(thisProps, nextProps) {
return !CANDIDATES_FOR_RESET.every((key) => {
const result = shallowEqual(thisProps[key], nextProps[key]);
// console.log(key, result);
return result;
});
}
function getCursorStyle() {
const tooltipStyle = `
.react-financial-charts-grabbing-cursor {
pointer-events: all;
cursor: -moz-grabbing;
cursor: -webkit-grabbing;
cursor: grabbing;
}
.react-financial-charts-crosshair-cursor {
pointer-events: all;
cursor: crosshair;
}
.react-financial-charts-tooltip-hover {
pointer-events: all;
cursor: pointer;
}
.react-financial-charts-avoid-interaction {
pointer-events: none;
}
.react-financial-charts-enable-interaction {
pointer-events: all;
}
.react-financial-charts-tooltip {
pointer-events: all;
cursor: pointer;
}
.react-financial-charts-default-cursor {
cursor: default;
}
.react-financial-charts-move-cursor {
cursor: move;
}
.react-financial-charts-pointer-cursor {
cursor: pointer;
}
.react-financial-charts-ns-resize-cursor {
cursor: ns-resize;
}
.react-financial-charts-ew-resize-cursor {
cursor: ew-resize;
}`;
return (React.createElement("style", { type: "text/css" }, tooltipStyle));
}
function getDimensions(props) {
return {
height: props.height - props.margin.top - props.margin.bottom,
width: props.width - props.margin.left - props.margin.right,
};
}
function getXScaleDirection(flipXScale) {
return flipXScale ? -1 : 1;
}
function calculateFullData(props) {
const { data: fullData, plotFull, xScale, clamp, pointsPerPxThreshold, flipXScale } = props;
const { xAccessor, displayXAccessor, minPointsPerPxThreshold } = props;
const useWholeData = isDefined(plotFull)
? plotFull
: xAccessor === identity;
const { filterData } = evaluator({
xScale,
useWholeData,
clamp,
pointsPerPxThreshold,
minPointsPerPxThreshold,
flipXScale,
});
return {
xAccessor,
displayXAccessor: displayXAccessor || xAccessor,
xScale: xScale.copy(),
fullData,
filterData,
};
}
function resetChart(props, firstCalculation = false) {
const state = calculateState(props);
const { xAccessor, displayXAccessor, fullData } = state;
const { plotData: initialPlotData, xScale } = state;
const { postCalculator, children } = props;
const plotData = postCalculator(initialPlotData);
const dimensions = getDimensions(props);
// @ts-ignore
const chartConfig = getChartConfigWithUpdatedYScales(getNewChartConfig(dimensions, children), { plotData, xAccessor, displayXAccessor, fullData }, xScale.domain());
return Object.assign(Object.assign({}, state), { xScale,
plotData,
chartConfig });
}
function updateChart(newState, initialXScale, props, lastItemWasVisible, initialChartConfig) {
const { fullData, xScale, xAccessor, displayXAccessor, filterData } = newState;
const lastItem = last(fullData);
const [start, end] = initialXScale.domain();
const { postCalculator, children, padding, flipXScale } = props;
const { maintainPointsPerPixelOnResize } = props;
const direction = getXScaleDirection(flipXScale);
const dimensions = getDimensions(props);
const updatedXScale = setXRange(xScale, dimensions, padding, direction);
// console.log("lastItemWasVisible =", lastItemWasVisible, end, xAccessor(lastItem), end >= xAccessor(lastItem));
let initialPlotData;
if (!lastItemWasVisible || end >= xAccessor(lastItem)) {
// resize comes here...
// get plotData between [start, end] and do not change the domain
const [rangeStart, rangeEnd] = initialXScale.range();
const [newRangeStart, newRangeEnd] = updatedXScale.range();
const newDomainExtent = ((newRangeEnd - newRangeStart) / (rangeEnd - rangeStart)) * (end - start);
const newStart = maintainPointsPerPixelOnResize
? end - newDomainExtent
: start;
const lastItemX = initialXScale(xAccessor(lastItem));
// console.log("pointsPerPixel => ", newStart, start, end, updatedXScale(end));
const response = filterData(fullData, [newStart, end], xAccessor, updatedXScale, { fallbackStart: start, fallbackEnd: { lastItem, lastItemX } });
initialPlotData = response.plotData;
updatedXScale.domain(response.domain);
// console.log("HERE!!!!!", start, end);
}
else if (lastItemWasVisible
&& end < xAccessor(lastItem)) {
// this is when a new item is added and last item was visible
// so slide over and show the new item also
// get plotData between [xAccessor(l) - (end - start), xAccessor(l)] and DO change the domain
const dx = initialXScale(xAccessor(lastItem)) - initialXScale.range()[1];
const [newStart, newEnd] = initialXScale.range().map((x) => x + dx).map(initialXScale.invert);
const response = filterData(fullData, [newStart, newEnd], xAccessor, updatedXScale);
initialPlotData = response.plotData;
updatedXScale.domain(response.domain); // if last item was visible, then shift
}
// plotData = getDataOfLength(fullData, showingInterval, plotData.length)
const plotData = postCalculator(initialPlotData);
// @ts-ignore
const chartConfig = getChartConfigWithUpdatedYScales(getNewChartConfig(dimensions, children, initialChartConfig), { plotData, xAccessor, displayXAccessor, fullData }, updatedXScale.domain());
return {
xScale: updatedXScale,
xAccessor,
chartConfig,
plotData,
fullData,
filterData,
};
}
function calculateState(props) {
const { xAccessor: inputXAccesor, xExtents: xExtentsProp, data, padding, flipXScale, } = props;
if (process.env.NODE_ENV !== "production" && isDefined(props.xScale.invert)) {
for (let i = 1; i < data.length; i++) {
const prev = data[i - 1];
const curr = data[i];
if (inputXAccesor(prev) > inputXAccesor(curr)) {
throw new Error("'data' is not sorted on 'xAccessor', send 'data' sorted in ascending order of 'xAccessor'");
}
}
}
const direction = getXScaleDirection(flipXScale);
const dimensions = getDimensions(props);
const extent = typeof xExtentsProp === "function"
? xExtentsProp(data)
: d3Extent(xExtentsProp.map((d) => functor(d)).map((each) => each(data, inputXAccesor)));
const { xAccessor, displayXAccessor, xScale, fullData, filterData } = calculateFullData(props);
const updatedXScale = setXRange(xScale, dimensions, padding, direction);
const { plotData, domain } = filterData(fullData, extent, inputXAccesor, updatedXScale);
if (process.env.NODE_ENV !== "production" && plotData.length <= 1) {
throw new Error(`Showing ${plotData.length} datapoints, review the 'xExtents' prop of ChartCanvas`);
}
return {
plotData,
xScale: updatedXScale.domain(domain),
xAccessor,
displayXAccessor,
fullData,
filterData,
};
}
function setXRange(xScale, dimensions, padding, direction = 1) {
if (xScale.rangeRoundPoints) {
if (isNaN(padding)) {
throw new Error("padding has to be a number for ordinal scale");
}
xScale.rangeRoundPoints([0, dimensions.width], padding);
}
else if (xScale.padding) {
if (isNaN(padding)) {
throw new Error("padding has to be a number for ordinal scale");
}
xScale.range([0, dimensions.width]);
xScale.padding(padding / 2);
}
else {
const { left, right } = isNaN(padding)
? padding
: { left: padding, right: padding };
if (direction > 0) {
xScale.range([left, dimensions.width - right]);
}
else {
xScale.range([dimensions.width - right, left]);
}
}
return xScale;
}
function pinchCoordinates(pinch) {
const { touch1Pos, touch2Pos } = pinch;
return {
topLeft: [Math.min(touch1Pos[0], touch2Pos[0]), Math.min(touch1Pos[1], touch2Pos[1])],
bottomRight: [Math.max(touch1Pos[0], touch2Pos[0]), Math.max(touch1Pos[1], touch2Pos[1])],
};
}
function isInteractionEnabled(xScale, xAccessor, data) {
const interaction = !isNaN(xScale(xAccessor(head(data)))) && isDefined(xScale.invert);
return interaction;
}
export class ChartCanvas extends React.Component {
constructor(props) {
super(props);
this.getDataInfo = this.getDataInfo.bind(this);
this.getCanvasContexts = this.getCanvasContexts.bind(this);
this.handleMouseMove = this.handleMouseMove.bind(this);
this.handleMouseEnter = this.handleMouseEnter.bind(this);
this.handleMouseLeave = this.handleMouseLeave.bind(this);
this.handleZoom = this.handleZoom.bind(this);
this.handlePinchZoom = this.handlePinchZoom.bind(this);
this.handlePinchZoomEnd = this.handlePinchZoomEnd.bind(this);
this.handlePan = this.handlePan.bind(this);
this.handlePanEnd = this.handlePanEnd.bind(this);
this.handleClick = this.handleClick.bind(this);
this.handleMouseDown = this.handleMouseDown.bind(this);
this.handleDoubleClick = this.handleDoubleClick.bind(this);
this.handleContextMenu = this.handleContextMenu.bind(this);
this.handleDragStart = this.handleDragStart.bind(this);
this.handleDrag = this.handleDrag.bind(this);
this.handleDragEnd = this.handleDragEnd.bind(this);
this.panHelper = this.panHelper.bind(this);
this.pinchZoomHelper = this.pinchZoomHelper.bind(this);
this.xAxisZoom = this.xAxisZoom.bind(this);
this.yAxisZoom = this.yAxisZoom.bind(this);
this.resetYDomain = this.resetYDomain.bind(this);
this.calculateStateForDomain = this.calculateStateForDomain.bind(this);
this.generateSubscriptionId = this.generateSubscriptionId.bind(this);
this.draw = this.draw.bind(this);
this.redraw = this.redraw.bind(this);
this.getAllPanConditions = this.getAllPanConditions.bind(this);
this.subscriptions = [];
this.subscribe = this.subscribe.bind(this);
this.unsubscribe = this.unsubscribe.bind(this);
this.amIOnTop = this.amIOnTop.bind(this);
this.saveEventCaptureNode = this.saveEventCaptureNode.bind(this);
this.saveCanvasContainerNode = this.saveCanvasContainerNode.bind(this);
this.setCursorClass = this.setCursorClass.bind(this);
this.getMutableState = this.getMutableState.bind(this);
this.panInProgress = false;
this.state = {};
this.mutableState = {};
this.lastSubscriptionId = 0;
const _a = resetChart(props, true), { fullData } = _a, state = __rest(_a, ["fullData"]);
this.state = state;
this.fullData = fullData;
}
saveEventCaptureNode(node) {
this.eventCaptureNode = node;
}
saveCanvasContainerNode(node) {
this.canvasContainerNode = node;
}
getMutableState() {
return this.mutableState;
}
getDataInfo() {
return Object.assign(Object.assign({}, this.state), { fullData: this.fullData });
}
getCanvasContexts() {
if (this.canvasContainerNode) {
return this.canvasContainerNode.getCanvasContexts();
}
}
generateSubscriptionId() {
this.lastSubscriptionId++;
return this.lastSubscriptionId;
}
clearBothCanvas() {
const canvases = this.getCanvasContexts();
if (canvases && canvases.axes) {
clearCanvas([
canvases.axes,
canvases.mouseCoord,
], this.props.ratio);
}
}
clearMouseCanvas() {
const canvases = this.getCanvasContexts();
if (canvases && canvases.mouseCoord) {
clearCanvas([
canvases.mouseCoord,
], this.props.ratio);
}
}
clearThreeCanvas() {
const canvases = this.getCanvasContexts();
if (canvases && canvases.axes) {
clearCanvas([
canvases.axes,
canvases.mouseCoord,
canvases.bg,
], this.props.ratio);
}
}
subscribe(id, rest) {
const { getPanConditions = functor({
draggable: false,
panEnabled: true,
}) } = rest;
this.subscriptions = this.subscriptions.concat(Object.assign(Object.assign({ id }, rest), { getPanConditions }));
}
unsubscribe(id) {
this.subscriptions = this.subscriptions.filter((each) => each.id !== id);
}
getAllPanConditions() {
return this.subscriptions
.map((each) => each.getPanConditions());
}
setCursorClass(className) {
if (this.eventCaptureNode != null) {
this.eventCaptureNode.setCursorClass(className);
}
}
amIOnTop(id) {
const dragableComponents = this.subscriptions
.filter((each) => each.getPanConditions().draggable);
return dragableComponents.length > 0
&& last(dragableComponents).id === id;
}
handleContextMenu(mouseXY, e) {
const { xAccessor, chartConfig, plotData, xScale } = this.state;
const currentCharts = getCurrentCharts(chartConfig, mouseXY);
const currentItem = getCurrentItem(xScale, xAccessor, mouseXY, plotData);
this.triggerEvent("contextmenu", {
mouseXY,
currentItem,
currentCharts,
}, e);
}
calculateStateForDomain(newDomain) {
const { xAccessor, displayXAccessor, xScale: initialXScale, chartConfig: initialChartConfig, plotData: initialPlotData, } = this.state;
const { filterData } = this.state;
const { fullData } = this;
const { postCalculator } = this.props;
const { plotData: beforePlotData, domain } = filterData(fullData, newDomain, xAccessor, initialXScale, {
currentPlotData: initialPlotData,
currentDomain: initialXScale.domain(),
});
const plotData = postCalculator(beforePlotData);
const updatedScale = initialXScale.copy().domain(domain);
// @ts-ignore
const chartConfig = getChartConfigWithUpdatedYScales(initialChartConfig, { plotData, xAccessor, displayXAccessor, fullData }, updatedScale.domain());
return {
xScale: updatedScale,
plotData,
chartConfig,
};
}
pinchZoomHelper(initialPinch, finalPinch) {
const { xScale: initialPinchXScale } = initialPinch;
const { xScale: initialXScale, chartConfig: initialChartConfig, plotData: initialPlotData, xAccessor, displayXAccessor, } = this.state;
const { filterData } = this.state;
const { fullData } = this;
const { postCalculator } = this.props;
const { topLeft: iTL, bottomRight: iBR } = pinchCoordinates(initialPinch);
const { topLeft: fTL, bottomRight: fBR } = pinchCoordinates(finalPinch);
const e = initialPinchXScale.range()[1];
const xDash = Math.round(-(iBR[0] * fTL[0] - iTL[0] * fBR[0]) / (iTL[0] - iBR[0]));
const yDash = Math.round(e + ((e - iBR[0]) * (e - fTL[0]) - (e - iTL[0]) * (e - fBR[0])) / ((e - iTL[0]) - (e - iBR[0])));
const x = Math.round(-xDash * iTL[0] / (-xDash + fTL[0]));
const y = Math.round(e - (yDash - e) * (e - iTL[0]) / (yDash + (e - fTL[0])));
const newDomain = [x, y].map(initialPinchXScale.invert);
// var domainR = initial.right + right;
const { plotData: beforePlotData, domain } = filterData(fullData, newDomain, xAccessor, initialPinchXScale, {
currentPlotData: initialPlotData,
currentDomain: initialXScale.domain(),
});
const plotData = postCalculator(beforePlotData);
const updatedScale = initialXScale.copy().domain(domain);
const mouseXY = finalPinch.touch1Pos;
// @ts-ignore
const chartConfig = getChartConfigWithUpdatedYScales(initialChartConfig, { plotData, xAccessor, displayXAccessor, fullData }, updatedScale.domain());
const currentItem = getCurrentItem(updatedScale, xAccessor, mouseXY, plotData);
return {
chartConfig,
xScale: updatedScale,
plotData,
mouseXY,
currentItem,
};
}
cancelDrag() {
this.eventCaptureNode.cancelDrag();
this.triggerEvent("dragcancel");
}
handlePinchZoom(initialPinch, finalPinch, e) {
if (!this.waitingForPinchZoomAnimationFrame) {
this.waitingForPinchZoomAnimationFrame = true;
const state = this.pinchZoomHelper(initialPinch, finalPinch);
this.triggerEvent("pinchzoom", state, e);
this.finalPinch = finalPinch;
requestAnimationFrame(() => {
this.clearBothCanvas();
this.draw({ trigger: "pinchzoom" });
this.waitingForPinchZoomAnimationFrame = false;
});
}
}
handlePinchZoomEnd(initialPinch, e) {
const { xAccessor } = this.state;
if (this.finalPinch) {
const state = this.pinchZoomHelper(initialPinch, this.finalPinch);
const { xScale } = state;
this.triggerEvent("pinchzoom", state, e);
this.finalPinch = undefined;
this.clearThreeCanvas();
const { fullData } = this;
const firstItem = head(fullData);
const start = head(xScale.domain());
const end = xAccessor(firstItem);
const { onLoadMore } = this.props;
this.setState(state, () => {
if (start < end) {
onLoadMore(start, end);
}
});
}
}
handleZoom(zoomDirection, mouseXY, e) {
if (this.panInProgress) {
return;
}
const { xAccessor, xScale: initialXScale, plotData: initialPlotData } = this.state;
const { zoomMultiplier = ChartCanvas.defaultProps.zoomMultiplier, zoomAnchor, } = this.props;
const { fullData } = this;
const item = zoomAnchor({
xScale: initialXScale,
xAccessor,
mouseXY,
plotData: initialPlotData,
fullData,
});
const cx = initialXScale(item);
const c = zoomDirection > 0 ? 1 * zoomMultiplier : 1 / zoomMultiplier;
const newDomain = initialXScale.range().map((x) => cx + (x - cx) * c).map(initialXScale.invert);
const { xScale, plotData, chartConfig } = this.calculateStateForDomain(newDomain);
const currentItem = getCurrentItem(xScale, xAccessor, mouseXY, plotData);
const currentCharts = getCurrentCharts(chartConfig, mouseXY);
this.clearThreeCanvas();
const firstItem = head(fullData);
const start = head(xScale.domain());
const end = xAccessor(firstItem);
const { onLoadMore } = this.props;
this.mutableState = {
mouseXY,
currentItem,
currentCharts,
};
this.triggerEvent("zoom", {
xScale,
plotData,
chartConfig,
mouseXY,
currentCharts,
currentItem,
show: true,
}, e);
this.setState({
xScale,
plotData,
chartConfig,
}, () => {
if (start < end) {
onLoadMore(start, end);
}
});
}
xAxisZoom(newDomain) {
const { xScale, plotData, chartConfig } = this.calculateStateForDomain(newDomain);
this.clearThreeCanvas();
const { xAccessor } = this.state;
const { fullData } = this;
const firstItem = head(fullData);
const start = head(xScale.domain());
const end = xAccessor(firstItem);
const { onLoadMore } = this.props;
this.setState({
xScale,
plotData,
chartConfig,
}, () => {
if (start < end) {
onLoadMore(start, end);
}
});
}
yAxisZoom(chartId, newDomain) {
this.clearThreeCanvas();
const { chartConfig: initialChartConfig } = this.state;
const chartConfig = initialChartConfig
.map((each) => {
if (each.id === chartId) {
const { yScale } = each;
return Object.assign(Object.assign({}, each), { yScale: yScale.copy().domain(newDomain), yPanEnabled: true });
}
else {
return each;
}
});
this.setState({
chartConfig,
});
}
triggerEvent(type, props, e) {
this.subscriptions.forEach((each) => {
const state = Object.assign(Object.assign({}, this.state), { fullData: this.fullData, subscriptions: this.subscriptions });
each.listener(type, props, state, e);
});
}
draw(props) {
this.subscriptions.forEach((each) => {
if (isDefined(each.draw)) {
each.draw(props);
}
});
}
redraw() {
this.clearThreeCanvas();
this.draw({ force: true });
}
panHelper(mouseXY, initialXScale, { dx, dy }, chartsToPan) {
const { xAccessor, displayXAccessor, chartConfig: initialChartConfig } = this.state;
const { filterData } = this.state;
const { fullData } = this;
const { postCalculator } = this.props;
if (isNotDefined(initialXScale.invert)) {
throw new Error("xScale provided does not have an invert() method."
+ "You are likely using an ordinal scale. This scale does not support zoom, pan");
}
const newDomain = initialXScale.range().map((x) => x - dx).map(initialXScale.invert);
const { plotData: beforePlotData, domain } = filterData(fullData, newDomain, xAccessor, initialXScale, {
currentPlotData: this.hackyWayToStopPanBeyondBounds__plotData,
currentDomain: this.hackyWayToStopPanBeyondBounds__domain,
});
const updatedScale = initialXScale.copy().domain(domain);
const plotData = postCalculator(beforePlotData);
const currentItem = getCurrentItem(updatedScale, xAccessor, mouseXY, plotData);
const chartConfig = getChartConfigWithUpdatedYScales(initialChartConfig, { plotData, xAccessor, displayXAccessor, fullData }, updatedScale.domain(), dy, chartsToPan);
const currentCharts = getCurrentCharts(chartConfig, mouseXY);
return {
xScale: updatedScale,
plotData,
chartConfig,
mouseXY,
currentCharts,
currentItem,
};
}
handlePan(mousePosition, panStartXScale, dxdy, chartsToPan, e) {
if (!this.waitingForPanAnimationFrame) {
this.waitingForPanAnimationFrame = true;
this.hackyWayToStopPanBeyondBounds__plotData = this.hackyWayToStopPanBeyondBounds__plotData || this.state.plotData;
this.hackyWayToStopPanBeyondBounds__domain = this.hackyWayToStopPanBeyondBounds__domain || this.state.xScale.domain();
const state = this.panHelper(mousePosition, panStartXScale, dxdy, chartsToPan);
this.hackyWayToStopPanBeyondBounds__plotData = state.plotData;
this.hackyWayToStopPanBeyondBounds__domain = state.xScale.domain();
this.panInProgress = true;
this.triggerEvent("pan", state, e);
this.mutableState = {
mouseXY: state.mouseXY,
currentItem: state.currentItem,
currentCharts: state.currentCharts,
};
requestAnimationFrame(() => {
this.waitingForPanAnimationFrame = false;
this.clearBothCanvas();
this.draw({ trigger: "pan" });
});
}
}
handlePanEnd(mousePosition, panStartXScale, dxdy, chartsToPan, e) {
const state = this.panHelper(mousePosition, panStartXScale, dxdy, chartsToPan);
this.hackyWayToStopPanBeyondBounds__plotData = null;
this.hackyWayToStopPanBeyondBounds__domain = null;
this.panInProgress = false;
const { xScale, plotData, chartConfig, } = state;
this.triggerEvent("panend", state, e);
requestAnimationFrame(() => {
const { xAccessor } = this.state;
const { fullData } = this;
const firstItem = head(fullData);
const start = head(xScale.domain());
const end = xAccessor(firstItem);
const { onLoadMore } = this.props;
this.clearThreeCanvas();
this.setState({
xScale,
plotData,
chartConfig,
}, () => {
if (start < end) {
onLoadMore(start, end);
}
});
});
}
handleMouseDown(mousePosition, currentCharts, e) {
this.triggerEvent("mousedown", this.mutableState, e);
}
handleMouseEnter(e) {
this.triggerEvent("mouseenter", {
show: true,
}, e);
}
handleMouseMove(mouseXY, inputType, e) {
if (!this.waitingForMouseMoveAnimationFrame) {
this.waitingForMouseMoveAnimationFrame = true;
const { chartConfig, plotData, xScale, xAccessor } = this.state;
const currentCharts = getCurrentCharts(chartConfig, mouseXY);
const currentItem = getCurrentItem(xScale, xAccessor, mouseXY, plotData);
this.triggerEvent("mousemove", {
show: true,
mouseXY,
// prevMouseXY is used in interactive components
prevMouseXY: this.prevMouseXY,
currentItem,
currentCharts,
}, e);
this.prevMouseXY = mouseXY;
this.mutableState = {
mouseXY,
currentItem,
currentCharts,
};
requestAnimationFrame(() => {
this.clearMouseCanvas();
this.draw({ trigger: "mousemove" });
this.waitingForMouseMoveAnimationFrame = false;
});
}
}
handleMouseLeave(e) {
this.triggerEvent("mouseleave", { show: false }, e);
this.clearMouseCanvas();
this.draw({ trigger: "mouseleave" });
}
handleDragStart({ startPos }, e) {
this.triggerEvent("dragstart", { startPos }, e);
}
handleDrag({ startPos, mouseXY }, e) {
const { chartConfig, plotData, xScale, xAccessor } = this.state;
const currentCharts = getCurrentCharts(chartConfig, mouseXY);
const currentItem = getCurrentItem(xScale, xAccessor, mouseXY, plotData);
this.triggerEvent("drag", {
startPos,
mouseXY,
currentItem,
currentCharts,
}, e);
this.mutableState = {
mouseXY,
currentItem,
currentCharts,
};
requestAnimationFrame(() => {
this.clearMouseCanvas();
this.draw({ trigger: "drag" });
});
}
handleDragEnd({ mouseXY }, e) {
this.triggerEvent("dragend", { mouseXY }, e);
requestAnimationFrame(() => {
this.clearMouseCanvas();
this.draw({ trigger: "dragend" });
});
}
handleClick(mousePosition, e) {
this.triggerEvent("click", this.mutableState, e);
requestAnimationFrame(() => {
this.clearMouseCanvas();
this.draw({ trigger: "click" });
});
}
handleDoubleClick(mousePosition, e) {
this.triggerEvent("dblclick", {}, e);
}
getChildContext() {
const dimensions = getDimensions(this.props);
return {
fullData: this.fullData,
plotData: this.state.plotData,
width: dimensions.width,
height: dimensions.height,
chartConfig: this.state.chartConfig,
xScale: this.state.xScale,
xAccessor: this.state.xAccessor,
displayXAccessor: this.state.displayXAccessor,
chartCanvasType: this.props.type,
margin: this.props.margin,
ratio: this.props.ratio,
xAxisZoom: this.xAxisZoom,
yAxisZoom: this.yAxisZoom,
getCanvasContexts: this.getCanvasContexts,
redraw: this.redraw,
subscribe: this.subscribe,
unsubscribe: this.unsubscribe,
generateSubscriptionId: this.generateSubscriptionId,
getMutableState: this.getMutableState,
amIOnTop: this.amIOnTop,
setCursorClass: this.setCursorClass,
};
}
UNSAFE_componentWillReceiveProps(nextProps) {
const reset = shouldResetChart(this.props, nextProps);
const interaction = isInteractionEnabled(this.state.xScale, this.state.xAccessor, this.state.plotData);
const { chartConfig: initialChartConfig } = this.state;
let newState;
if (!interaction || reset || !shallowEqual(this.props.xExtents, nextProps.xExtents)) {
// do reset
newState = resetChart(nextProps);
this.mutableState = {};
}
else {
const [start, end] = this.state.xScale.domain();
const prevLastItem = last(this.fullData);
const calculatedState = calculateFullData(nextProps);
const { xAccessor } = calculatedState;
const lastItemWasVisible = xAccessor(prevLastItem) <= end && xAccessor(prevLastItem) >= start;
newState = updateChart(calculatedState, this.state.xScale, nextProps, lastItemWasVisible, initialChartConfig);
}
const { fullData } = newState, state = __rest(newState, ["fullData"]);
if (!this.panInProgress) {
this.clearThreeCanvas();
this.setState(state);
}
this.fullData = fullData;
}
resetYDomain(chartId) {
const { chartConfig } = this.state;
let changed = false;
const newChartConfig = chartConfig
.map((each) => {
if ((isNotDefined(chartId) || each.id === chartId)
&& !shallowEqual(each.yScale.domain(), each.realYDomain)) {
changed = true;
return Object.assign(Object.assign({}, each), { yScale: each.yScale.domain(each.realYDomain), yPanEnabled: false });
}
return each;
});
if (changed) {
this.clearThreeCanvas();
this.setState({
chartConfig: newChartConfig,
});
}
}
shouldComponentUpdate() {
return !this.panInProgress;
}
render() {
const { type = ChartCanvas.defaultProps.type, useCrossHairStyleCursor, onSelect, height, width, margin = ChartCanvas.defaultProps.margin, className, zIndex = ChartCanvas.defaultProps.zIndex, defaultFocus, ratio, mouseMoveEvent, panEvent, zoomEvent, disableInteraction, } = this.props;
const { plotData, xScale, xAccessor, chartConfig } = this.state;
const dimensions = getDimensions(this.props);
const interaction = isInteractionEnabled(xScale, xAccessor, plotData);
const cursorStyle = useCrossHairStyleCursor && interaction;
const cursor = getCursorStyle();
return (React.createElement("div", { style: { position: "relative", width, height }, className: className, onClick: onSelect },
React.createElement(CanvasContainer, { ref: this.saveCanvasContainerNode, type: type, ratio: ratio, width: width, height: height, zIndex: zIndex }),
React.createElement("svg", { className: className, width: width, height: height, style: { position: "absolute", zIndex: (zIndex + 5) } },
cursor,
React.createElement("defs", null,
React.createElement("clipPath", { id: "chart-area-clip" },
React.createElement("rect", { x: "0", y: "0", width: dimensions.width, height: dimensions.height })),
chartConfig
.map((each, idx) => React.createElement("clipPath", { key: idx, id: `chart-area-clip-${each.id}` },
React.createElement("rect", { x: "0", y: "0", width: each.width, height: each.height })))),
React.createElement("g", { transform: `translate(${margin.left + 0.5}, ${margin.top + 0.5})` },
React.createElement(EventCapture, { ref: this.saveEventCaptureNode, useCrossHairStyleCursor: cursorStyle, mouseMove: mouseMoveEvent && interaction, zoom: zoomEvent && interaction, pan: panEvent && interaction, width: dimensions.width, height: dimensions.height, chartConfig: chartConfig, xScale: xScale, xAccessor: xAccessor, focus: defaultFocus, disableInteraction: disableInteraction, getAllPanConditions: this.getAllPanConditions, onContextMenu: this.handleContextMenu, onClick: this.handleClick, onDoubleClick: this.handleDoubleClick, onMouseDown: this.handleMouseDown, onMouseMove: this.handleMouseMove, onMouseEnter: this.handleMouseEnter, onMouseLeave: this.handleMouseLeave, onDragStart: this.handleDragStart, onDrag: this.handleDrag, onDragComplete: this.handleDragEnd, onZoom: this.handleZoom, onPinchZoom: this.handlePinchZoom, onPinchZoomEnd: this.handlePinchZoomEnd, onPan: this.handlePan, onPanEnd: this.handlePanEnd }),
React.createElement("g", { className: "react-financial-charts-avoid-interaction" }, this.props.children)))));
}
}
ChartCanvas.defaultProps = {
margin: { top: 0, right: 40, bottom: 40, left: 0 },
type: "hybrid",
pointsPerPxThreshold: 2,
minPointsPerPxThreshold: 1 / 100,
className: "react-financial-charts",
zIndex: 1,
xExtents: [min, max],
postCalculator: identity,
padding: 0,
xAccessor: identity,
flipXScale: false,
useCrossHairStyleCursor: true,
defaultFocus: true,
onLoadMore: noop,
onSelect: noop,
mouseMoveEvent: true,
panEvent: true,
zoomEvent: true,
zoomMultiplier: 1.1,
clamp: false,
zoomAnchor: mouseBasedZoomAnchor,
maintainPointsPerPixelOnResize: true,
disableInteraction: false,
};
ChartCanvas.childContextTypes = {
plotData: PropTypes.array,
fullData: PropTypes.array,
chartConfig: PropTypes.arrayOf(PropTypes.shape({
id: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired,
origin: PropTypes.arrayOf(PropTypes.number).isRequired,
padding: PropTypes.oneOfType([
PropTypes.number,
PropTypes.shape({
top: PropTypes.number,
bottom: PropTypes.number,
}),
]),
yExtents: PropTypes.arrayOf(PropTypes.func),
yExtentsProvider: PropTypes.func,
yScale: PropTypes.func.isRequired,
mouseCoordinates: PropTypes.shape({
at: PropTypes.string,
format: PropTypes.func,
}),
width: PropTypes.number.isRequired,
height: PropTypes.number.isRequired,
})).isRequired,
xScale: PropTypes.func.isRequired,
xAccessor: PropTypes.func.isRequired,
displayXAccessor: PropTypes.func.isRequired,
width: PropTypes.number.isRequired,
height: PropTypes.number.isRequired,
chartCanvasType: PropTypes.oneOf(["svg", "hybrid"]).isRequired,
margin: PropTypes.object.isRequired,
ratio: PropTypes.number.isRequired,
getCanvasContexts: PropTypes.func,
xAxisZoom: PropTypes.func,
yAxisZoom: PropTypes.func,
amIOnTop: PropTypes.func,
redraw: PropTypes.func,
subscribe: PropTypes.func,
unsubscribe: PropTypes.func,
setCursorClass: PropTypes.func,
generateSubscriptionId: PropTypes.func,
getMutableState: PropTypes.func,
};
ChartCanvas.ohlcv = (d) => ({ date: d.date, open: d.open, high: d.high, low: d.low, close: d.close, volume: d.volume });
//# sourceMappingURL=ChartCanvas.js.map