@gpa-gemstone/react-graph
Version:
Interactive UI Components for GPA products
856 lines • 110 kB
JavaScript
"use strict";
// ******************************************************************************************************
// Plot.tsx - Gbtc
//
// Copyright © 2020, Grid Protection Alliance. All Rights Reserved.
//
// Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See
// the NOTICE file distributed with this work for additional information regarding copyright ownership.
// The GPA licenses this file to you under the MIT License (MIT), the "License"; you may not use this
// file except in compliance with the License. You may obtain a copy of the License at:
//
// http://opensource.org/licenses/MIT
//
// Unless agreed to in writing, the subject software distributed under the License is distributed on an
// "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the
// License for the specific language governing permissions and limitations.
//
// Code Modification History:
// ----------------------------------------------------------------------------------------------------
// 03/18/2021 - C Lackner
// Generated original version of source code.
//
// ******************************************************************************************************
var __read = (this && this.__read) || function (o, n) {
var m = typeof Symbol === "function" && o[Symbol.iterator];
if (!m) return o;
var i = m.call(o), r, ar = [], e;
try {
while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
}
catch (error) { e = { error: error }; }
finally {
try {
if (r && !r.done && (m = i["return"])) m.call(i);
}
finally { if (e) throw e.error; }
}
return ar;
};
var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
if (ar || !(i in from)) {
if (!ar) ar = Array.prototype.slice.call(from, 0, i);
ar[i] = from[i];
}
}
return to.concat(ar || Array.prototype.slice.call(from));
};
var __values = (this && this.__values) || function(o) {
var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0;
if (m) return m.call(o);
if (o && typeof o.length === "number") return {
next: function () {
if (o && i >= o.length) o = void 0;
return { value: o && o[i++], done: !o };
}
};
throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
};
Object.defineProperty(exports, "__esModule", { value: true });
var React = require("react");
var _ = require("lodash");
var InteractiveButtons_1 = require("./InteractiveButtons");
var GraphContext_1 = require("./GraphContext");
var helper_functions_1 = require("@gpa-gemstone/helper-functions");
var lodash_1 = require("lodash");
var TimeAxis_1 = require("./TimeAxis");
var LogAxis_1 = require("./LogAxis");
var YValueAxis_1 = require("./YValueAxis");
var LegendWithContext_1 = require("./LegendWithContext");
var LegendEntry_1 = require("./LegendEntry");
var LineWithThreshold_1 = require("./LineWithThreshold");
var Line_1 = require("./Line");
var Button_1 = require("./Button");
var HorizontalMarker_1 = require("./HorizontalMarker");
var VerticalMarker_1 = require("./VerticalMarker");
var SymbolicMarker_1 = require("./SymbolicMarker");
var Circle_1 = require("./Circle");
var Pill_1 = require("./Pill");
var AggregatingCircles_1 = require("./AggregatingCircles");
var Infobox_1 = require("./Infobox");
var HeatMapChart_1 = require("./HeatMapChart");
var _html2canvas = require("html2canvas");
var HighlightBox_1 = require("./HighlightBox");
var XValueAxis_1 = require("./XValueAxis");
var StreamingLine_1 = require("./StreamingLine");
var PlotGroupContext_1 = require("./PlotGroupContext");
var Bar_1 = require("./Bar");
var BarAggregate_1 = require("./BarAggregate");
var CircleGroups_1 = require("./CircleGroups");
var html2canvas = _html2canvas;
var SvgStyle = {
fill: 'none',
userSelect: 'none',
WebkitTouchCallout: 'none',
WebkitUserSelect: 'none',
KhtmlUserSelect: 'none',
MozUserSelect: 'none',
msUserSelect: 'none',
pointerEvents: 'none',
};
var defaultLegendHeight = 50;
var defaultLegendWidth = 100;
var Plot = function (props) {
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
/*
Actual plot that will handle Axis etc.
*/
var SVGref = React.useRef(null);
var handlers = React.useRef(new Map());
var wheelTimeout = React.useRef({ timeout: undefined, stopScroll: false });
var heightChange = React.useRef({ timeout: undefined, extraNeeded: 0, captureID: undefined });
var guid = React.useMemo(function () { return (0, helper_functions_1.CreateGuid)(); }, []);
var data = React.useRef(new Map());
var _m = __read(React.useState(""), 2), dataGuid = _m[0], setDataGuid = _m[1];
var _o = __read(React.useState(props.defaultTdomain), 2), tDomain = _o[0], setTdomain = _o[1];
var _p = __read(React.useState(0), 2), tOffset = _p[0], setToffset = _p[1];
var _q = __read(React.useState(1), 2), tScale = _q[0], setTscale = _q[1];
var _r = __read(React.useState(Array(GraphContext_1.AxisMap.size).fill([0, 0])), 2), yDomain = _r[0], setYdomain = _r[1];
var _s = __read(React.useState(Array(GraphContext_1.AxisMap.size).fill(0)), 2), yOffset = _s[0], setYoffset = _s[1];
var _t = __read(React.useState(Array(GraphContext_1.AxisMap.size).fill(1)), 2), yScale = _t[0], setYscale = _t[1];
// ToDo: This is hardset to two because it's tied to display, 'left' and 'right'
var _u = __read(React.useState(Array(2).fill(0)), 2), yHasData = _u[0], setYHasData = _u[1];
var _v = __read(React.useState('none'), 2), mouseMode = _v[0], setMouseMode = _v[1];
var _w = __read(React.useState(getDefaultMouseMode(props.defaultMouseMode, props.yDomain, props.zoom, props.xZoom, props.yZoom)), 2), selectedMode = _w[0], setSelectedMode = _w[1];
var _x = __read(React.useState(false), 2), mouseIn = _x[0], setMouseIn = _x[1];
var _y = __read(React.useState([0, 0]), 2), mousePosition = _y[0], setMousePosition = _y[1];
var _z = __read(React.useState([0, 0]), 2), mousePositionSnap = _z[0], setMousePositionSnap = _z[1];
var _0 = __read(React.useState([0, 0]), 2), mouseClick = _0[0], setMouseClick = _0[1];
var _1 = __read(React.useState("default"), 2), mouseStyle = _1[0], setMouseStyle = _1[1];
var moveRequested = React.useRef(false);
var _2 = __read(React.useState(false), 2), photoReady = _2[0], setPhotoReady = _2[1];
var _3 = __read(React.useState(10), 2), offsetTop = _3[0], setOffsetTop = _3[1];
var _4 = __read(React.useState(10), 2), offsetBottom = _4[0], setOffsetBottom = _4[1];
var _5 = __read(React.useState(5), 2), offsetLeft = _5[0], setOffsetLeft = _5[1];
var _6 = __read(React.useState(5), 2), offsetRight = _6[0], setOffsetRight = _6[1];
var _7 = __read(React.useState(0), 2), heightYFactor = _7[0], setHeightYFactor = _7[1];
var _8 = __read(React.useState(0), 2), heightXLabel = _8[0], setHeightXLabel = _8[1];
var _9 = __read(React.useState(0), 2), heightLeftYLabel = _9[0], setHeightLeftYLabel = _9[1];
var _10 = __read(React.useState(0), 2), heightRightYLabel = _10[0], setHeightRightYLabel = _10[1];
// States for Props to avoid change notification on ref change
var _11 = __read(React.useState(props.defaultTdomain), 2), defaultTdomain = _11[0], setDefaultTdomain = _11[1];
var _12 = __read(React.useState(typeCorrectDomain(props.defaultYdomain)), 2), defaultYdomain = _12[0], setDefaultYdomain = _12[1];
var _13 = __read(React.useState(0), 2), updateFlag = _13[0], setUpdateFlag = _13[1];
var _14 = __read(React.useState((_a = props.legendHeight) !== null && _a !== void 0 ? _a : defaultLegendHeight), 2), legendHeight = _14[0], setLegendHeight = _14[1];
var _15 = __read(React.useState((_b = props.legendWidth) !== null && _b !== void 0 ? _b : defaultLegendWidth), 2), legendWidth = _15[0], setLegendWidth = _15[1];
var _16 = __read(React.useState(props.height), 2), svgHeight = _16[0], setSVGheight = _16[1];
var _17 = __read(React.useState(props.width), 2), svgWidth = _17[0], setSVGwidth = _17[1];
var _18 = __read(React.useState(28), 2), menueWidth = _18[0], setMenueWidth = _18[1];
var _19 = __read(React.useState({ requester: "", command: "none" }), 2), command = _19[0], setCurrentCommand = _19[1];
var groupContext = React.useContext(PlotGroupContext_1.default);
var legendWidthToUse = React.useMemo(function () {
return groupContext.HasConsumer ? groupContext.LegendWidth : legendWidth;
}, [groupContext.HasConsumer, legendWidth, groupContext.LegendWidth]);
//NOTE: zoom-horizontal really is zooming on the y-axis and zoom-vertical is zooming on the x-axis
var showZoomButton = props.yDomain !== 'AutoValue' && ((_c = props.zoom) !== null && _c !== void 0 ? _c : true);
var showHorizontalZoomButton = props.yDomain !== 'AutoValue' && (((_d = props.zoom) !== null && _d !== void 0 ? _d : true) || ((_e = props.yZoom) !== null && _e !== void 0 ? _e : true));
var showVerticalZoomButton = (props.yDomain === 'AutoValue' || ((_f = props.zoom) !== null && _f !== void 0 ? _f : true)) || ((_g = props.xZoom) !== null && _g !== void 0 ? _g : true);
var showPan = props.pan === undefined || props.pan;
var showReset = showPan || showZoomButton || showHorizontalZoomButton || showVerticalZoomButton;
//Effect to push tDomain to parent
React.useEffect(function () {
if (props.onTDomainChange == null)
return;
var handle = setTimeout(function () {
if (props.onTDomainChange != null)
props.onTDomainChange([tDomain[0], tDomain[1]]);
}, 250);
return function () { return clearTimeout(handle); };
}, [tDomain[0], tDomain[1], props.onTDomainChange]);
//Effect to sync external tDomain changes
React.useEffect(function () {
if (props.tDomain == null)
return;
//assume that external tDomain is valid as they provided the bounds
setTdomain([props.tDomain[0], props.tDomain[1]]);
}, [(_h = props.tDomain) === null || _h === void 0 ? void 0 : _h[0], (_j = props.tDomain) === null || _j === void 0 ? void 0 : _j[1]]);
React.useEffect(function () {
if (!groupContext.HasConsumer)
return;
groupContext.RegisterLegendWidth(guid, legendWidth);
}, [legendWidth, groupContext.RegisterLegendWidth, groupContext.HasConsumer, guid]);
React.useEffect(function () {
if (!groupContext.HasConsumer)
return;
return function () {
groupContext.UnRegisterLegendWidth(guid);
};
}, [guid, groupContext.HasConsumer, groupContext.UnRegisterLegendWidth]);
var applyToYDomain = React.useCallback(function (predicate) {
var newDomain = __spreadArray([], __read(yDomain), false);
var apply = false;
newDomain.forEach(function (d, i, a) {
// Note: Apply MUST be after predicate, or it short-circuits it
apply = predicate(d, i, a) || apply;
});
if (apply) {
setYdomain(newDomain);
}
}, [yDomain]);
// Effect to Reset the legend width/height
React.useEffect(function () {
if (props.legendHeight !== undefined)
setLegendHeight(props.legendHeight);
}, [props.legendHeight]);
React.useEffect(function () {
if (props.legendWidth !== undefined)
setLegendWidth(props.legendWidth);
}, [props.legendWidth]);
// Recompute svg height
React.useEffect(function () {
setSVGheight(props.height - (props.legend === 'bottom' ? legendHeight : 0));
}, [props.height, props.legend, legendHeight]);
// recompute svg width
React.useEffect(function () {
setSVGwidth(props.width - (props.legend === 'right' ? legendWidthToUse : 0));
}, [props.width, props.legend, legendWidthToUse]);
// enforce Y limits
React.useEffect(function () {
var mutateDomain = function (domain, axis, allDomains) {
var hasApplied = false;
// Need to type correct our arguements
var propMin = typeCorrect(props.Ymin, axis);
var propMax = typeCorrect(props.Ymax, axis);
if (propMin !== undefined && domain[0] < propMin) {
allDomains[axis] = [propMin, domain[1]];
hasApplied = true;
}
if (propMax !== undefined && domain[1] > propMax) {
allDomains[axis] = [domain[0], propMax];
hasApplied = true;
}
return hasApplied;
};
applyToYDomain(mutateDomain);
}, [yDomain]);
//Effect to set defaultTDomain changes
React.useEffect(function () {
if (!(0, lodash_1.isEqual)(defaultTdomain, props.defaultTdomain))
setDefaultTdomain(props.defaultTdomain);
}, [props.defaultTdomain]);
//Effect to set defaultYDomain changes
React.useEffect(function () {
if (!(0, lodash_1.isEqual)(defaultYdomain, props.defaultYdomain))
setDefaultYdomain(typeCorrectDomain(props.defaultYdomain));
}, [props.defaultYdomain]);
// if default domains change, update NOTE: not using updateXDomain here to avoid circular dependency we assume the defaults are valid
React.useEffect(function () {
setTdomain(defaultTdomain);
}, [defaultTdomain]);
//Effect to set YDomain when defaultYDomain changes
React.useEffect(function () {
setYdomain(defaultYdomain);
}, [defaultYdomain]);
// Adjust top and bottom Offset
React.useEffect(function () {
var top = heightYFactor + 10;
var bottom = heightXLabel;
if (offsetTop !== top)
setOffsetTop(top);
if (offsetBottom !== bottom)
setOffsetBottom(bottom);
}, [heightXLabel, heightYFactor]);
// Adjust Left Offset
React.useEffect(function () {
var left = heightLeftYLabel + (props.menuLocation === 'left' ? (menueWidth + 2) : 10);
setOffsetLeft(left);
}, [heightLeftYLabel, props.menuLocation, menueWidth]);
// Adjust Right Offset
React.useEffect(function () {
var right = heightRightYLabel + ((props.menuLocation === 'right' || props.menuLocation === undefined) ? (menueWidth + 2) : 10);
setOffsetRight(right);
}, [heightRightYLabel, props.menuLocation, menueWidth]);
// Adjust Y domain defaults
React.useEffect(function () {
if (props.yDomain !== 'AutoValue' && props.yDomain !== 'HalfAutoValue')
return;
var dataReducerFunc = function (result, series, func, axis) {
// This part of the data may not belong to the axis we care about at the moment
var dataAxis = GraphContext_1.AxisMap.get(series.axis);
if (axis === dataAxis) {
var value = func([Number.MIN_SAFE_INTEGER, Number.MAX_SAFE_INTEGER]);
// Only add if it's a valid number
if (value !== undefined && !isNaN(value) && isFinite(value))
result.push(value);
}
return result;
};
var newDefaultDomain = defaultYdomain.map(function (yDomain, axis) {
var values = __spreadArray([], __read(data.current.values()), false);
var yMin = Math.min.apply(Math, __spreadArray([], __read(values.reduce(function (result, series) { return dataReducerFunc(result, series, series.getMin, axis); }, [])), false));
var yMax = Math.max.apply(Math, __spreadArray([], __read(values.reduce(function (result, series) { return dataReducerFunc(result, series, series.getMax, axis); }, [])), false));
if (!isNaN(yMin) && !isNaN(yMax) && isFinite(yMin) && isFinite(yMax)) {
if (props.yDomain === 'AutoValue')
return [yMin, yMax];
// If this condition is satisfied, it means our series is mostly positive range
else if (Math.abs(yMax) >= Math.abs(yMin))
return [0, yMax];
else
return [yMin, 0];
}
return [0, 1];
});
if (!_.isEqual(newDefaultDomain, defaultYdomain))
setDefaultYdomain(newDefaultDomain);
}, [dataGuid, props.yDomain]);
// Effect to Adjust which y axes have data
React.useEffect(function () {
var newHasData = Array(2);
var hasFunc = function (axis) { return __spreadArray([], __read(data.current.values()), false).some(function (series) { return GraphContext_1.AxisMap.get(axis) === GraphContext_1.AxisMap.get(series.axis); }); };
newHasData[0] = hasFunc('left');
newHasData[1] = hasFunc('right');
setYHasData(newHasData);
}, [dataGuid]);
// Adjust x axis
React.useEffect(function () {
var dT = tDomain[1] - tDomain[0];
var dTmin = tDomain[0];
if (dT === 0)
return;
if (props.XAxisType === 'log') {
dT = Math.log10(tDomain[1]) - Math.log10(tDomain[0]);
dTmin = Math.log10(tDomain[0]);
}
var scale = (svgWidth - offsetLeft - offsetRight) / dT;
setTscale(scale);
setToffset(offsetLeft - dTmin * scale);
}, [tDomain, offsetLeft, offsetRight, props.XAxisType, svgWidth]);
// Adjust y axis
React.useEffect(function () {
var e_1, _a;
var mutateFunction = function (scaleArray, offsetArray, axis) {
var dY = yDomain[axis][1] - yDomain[axis][0];
var scale = (svgHeight - offsetTop - offsetBottom) / (dY === 0 ? 0.00001 : dY);
scaleArray[axis] = -scale;
offsetArray[axis] = svgHeight - offsetBottom + yDomain[axis][0] * scale;
};
// Update every axis
var newScale = __spreadArray([], __read(yScale), false);
var newOffset = __spreadArray([], __read(yOffset), false);
try {
for (var _b = __values(GraphContext_1.AxisMap.values()), _c = _b.next(); !_c.done; _c = _b.next()) {
var axis = _c.value;
mutateFunction(newScale, newOffset, axis);
}
}
catch (e_1_1) { e_1 = { error: e_1_1 }; }
finally {
try {
if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
}
finally { if (e_1) throw e_1.error; }
}
setYscale(newScale);
setYoffset(newOffset);
}, [yDomain, offsetTop, offsetBottom, svgHeight]);
React.useEffect(function () { setUpdateFlag(function (x) { return x + 1; }); }, [tScale, tOffset, yScale, yOffset]);
// Change mouse cursor
React.useEffect(function () {
var newCursor;
if (props.cursorOverride == null) {
switch (selectedMode) {
case 'pan':
newCursor = 'grab';
break;
case 'select':
newCursor = 'pointer';
break;
default:
newCursor = 'crosshair';
}
}
else
newCursor = props.cursorOverride;
setMouseStyle(newCursor);
}, [selectedMode, props.cursorOverride]);
// Stop scrolling while zooming
React.useEffect(function () {
var cancelWheel = function (e) { if (wheelTimeout.current.stopScroll)
e.preventDefault(); };
document.body.addEventListener('wheel', cancelWheel, { passive: false });
return function () { return document.body.removeEventListener('wheel', cancelWheel); };
}, []);
// Execute Plot Capture and leave photo mode
React.useEffect(function () {
// ToDo: We can clean this up some and improve performance using html2canvas options (but the biggest hurdle is the legend, which we don't have a lot of options for...)
if (!photoReady)
return;
// we can't immediately complete the request, since some layout things may still be changing...
clearTimeout(heightChange.current.timeout);
// timeout to set if we don't see any more changes within 0.05 seconds
heightChange.current.timeout = setTimeout(function () {
var _a;
var id = (_a = heightChange.current.captureID) !== null && _a !== void 0 ? _a : guid;
var element = document.getElementById(id);
if (element == null) {
console.error("Could not find document element with id ".concat(id));
}
else {
html2canvas(element).then(function (canvas) {
document.body.appendChild(canvas);
var imageData = canvas.toDataURL("image/png").replace(/^data:image\/png/, "data:application/octet-stream");
var anchorElement = document.createElement("a");
anchorElement.href = imageData;
anchorElement.download = "".concat(id, ".png");
document.body.appendChild(anchorElement);
anchorElement.click();
// Removing children created/cleanup
window.URL.revokeObjectURL(imageData);
document.body.removeChild(anchorElement);
document.body.removeChild(canvas);
});
}
setPhotoReady(false);
if (props.onCaptureComplete !== undefined)
props.onCaptureComplete();
}, 50);
});
// Function to update x domain with tmin/tmax enforcement
var updateXDomain = React.useCallback(function (x) {
if (x[0] === tDomain[0] && x[1] === tDomain[1])
return;
if (props.Tmin !== undefined && x[0] < props.Tmin)
x[0] = props.Tmin;
if (props.Tmax !== undefined && x[1] > props.Tmax)
x[1] = props.Tmax;
if (x[0] < x[1])
setTdomain(x);
else
setTdomain([x[1], x[0]]);
}, [props.Tmin, props.Tmax, tDomain[0], tDomain[1]]);
// requests new legend height/width upto a defined maximum set by props
var requestLegendHeightChange = React.useCallback(function (newHeight) {
var _a;
var heightLimit = props.legend !== 'bottom' ? svgHeight : ((_a = props.legendHeight) !== null && _a !== void 0 ? _a : defaultLegendHeight);
if (!photoReady)
heightChange.current.extraNeeded = Math.max(newHeight - heightLimit, 0);
if (props.legend !== 'bottom')
return;
var limitedHeight = Math.min(newHeight, heightLimit);
if (legendHeight !== limitedHeight)
setLegendHeight(limitedHeight);
}, [props.legendHeight, legendHeight, props.legend, photoReady]);
var requestLegendWidthChange = React.useCallback(function (newWidth) {
var _a;
if (newWidth < 0)
return;
if (props.legend !== 'right')
return;
var limitedWidth = Math.min(newWidth, (_a = props.legendWidth) !== null && _a !== void 0 ? _a : defaultLegendWidth);
if (legendWidth !== limitedWidth)
setLegendWidth(limitedWidth);
}, [props.legendWidth, legendWidth, props.legend]);
// transforms from pixels into x value. result passed into onClick function
var xInvTransform = React.useCallback(function (p) {
var xT = (p - tOffset) / tScale;
if (props.XAxisType === 'log')
xT = Math.pow(10, (p - tOffset) / tScale);
return xT;
}, [tOffset, tScale, props.XAxisType]);
// transforms from pixels into y value. result passed into onClick function
var yInvTransform = React.useCallback(function (p, a) {
var axis = (typeof (a) !== 'number') ? GraphContext_1.AxisMap.get(a) : a;
return (p - yOffset[axis]) / yScale[axis];
}, [yOffset, yScale]);
var Reset = React.useCallback(function () {
updateXDomain(defaultTdomain);
setYdomain(defaultYdomain);
}, [defaultYdomain, defaultTdomain, updateXDomain]);
// new X transformation from x value into Pixels
var xTransform = React.useCallback(function (value) {
var xT = value * tScale + tOffset;
if (props.XAxisType === 'log') {
var v = (value === 0 ? tDomain[0] * 0.01 : value);
xT = Math.log10(v) * tScale + tOffset;
}
return xT;
}, [tScale, tOffset, props.XAxisType, tDomain]);
// new Y transformation from y value into Pixels
var yTransform = React.useCallback(function (value, a) {
var axis = (typeof (a) !== 'number') ? GraphContext_1.AxisMap.get(a) : a;
return value * yScale[axis] + yOffset[axis];
}, [yScale, yOffset]);
// applies offset and contraints to x Pixel value to get something that is plotable
var xApplyOffset = React.useCallback(function (value) {
if (value >= 0)
return Math.min(value + offsetLeft, svgWidth - offsetRight);
else
return Math.max(offsetLeft, svgWidth - offsetRight + value);
}, [offsetLeft, offsetRight, svgWidth]);
// applies offset and contraints to y Pixel value to get something that is plotable
var yApplyOffset = React.useCallback(function (value) {
if (value >= 0)
return Math.min(value + offsetTop, svgHeight - offsetBottom);
else
return Math.max(offsetTop, svgHeight - offsetBottom + value);
}, [offsetTop, offsetBottom, svgHeight]);
var setData = React.useCallback(function (key, d) {
setDataGuid((0, helper_functions_1.CreateGuid)());
if (d != null)
data.current.set(key, d);
else
data.current.delete(key);
}, []);
var addData = React.useCallback(function (d) {
var key = (0, helper_functions_1.CreateGuid)();
setData(key, d);
return key;
}, []);
var setLegend = React.useCallback(function (key, legend) {
var series = data.current.get(key);
if (series === undefined)
return;
series.legend = legend;
data.current.set(key, series);
}, []);
function snapMouseToClosestSeries(pixelPt) {
var xVal = xInvTransform(pixelPt.x);
var findClosestPoint = function (result, series) {
var pointArray = series.getPoints(xVal, 7);
if (pointArray === undefined)
return result;
var ptArrayResult = pointArray.reduce(function (result, pt) {
var point = [xTransform(pt[0]), yTransform(pt[1], GraphContext_1.AxisMap.get(series.axis))];
var newDistSq = Math.pow((point[0] - pixelPt.x), 2) + Math.pow((point[1] - pixelPt.y), 2);
if (result.distSq === undefined || newDistSq < result.distSq)
return { pt: { x: point[0], y: point[1] }, distSq: newDistSq };
return result;
}, { pt: { x: 0, y: 0 }, distSq: undefined });
if (ptArrayResult.distSq !== undefined && (result.distSq === undefined || ptArrayResult.distSq < result.distSq))
return ptArrayResult;
return result;
};
return __spreadArray([], __read(data.current.values()), false).reduce(function (result, series) { return findClosestPoint(result, series); }, { pt: { x: 0, y: 0 }, distSq: undefined }).pt;
}
var registerSelect = React.useCallback(function (handler) {
var key = (0, helper_functions_1.CreateGuid)();
handlers.current.set(key, handler);
return key;
}, []);
var removeSelect = React.useCallback(function (key) {
handlers.current.delete(key);
}, []);
var updateSelect = React.useCallback(function (key, handler) {
handlers.current.set(key, handler);
}, []);
var setSelection = React.useCallback(function (s) {
if (s === "reset")
Reset();
else if (s === "download") {
if (props.onDataInspect !== undefined)
props.onDataInspect(tDomain);
}
else if (s === "capture") {
setPhotoReady(true);
if (props.onCapture !== undefined)
heightChange.current.captureID = props.onCapture(heightChange.current.extraNeeded);
}
else
setSelectedMode(s);
}, [tDomain, Reset, props.onDataInspect]);
var getConstrainedYDomain = React.useCallback(function (newTDomain) {
var dataReducerFunc = function (result, series, func, axis) {
// This part of the data may not belong to the axis we care about at the moment
var dataAxis = GraphContext_1.AxisMap.get(series.axis);
if (axis === dataAxis) {
var value = func(newTDomain);
if (value !== undefined)
result.push(value);
}
return result;
};
return yDomain.map(function (oldDomain, axis) {
var yMin = Math.min.apply(Math, __spreadArray([], __read(__spreadArray([], __read(data.current.values()), false).reduce(function (result, series) { return dataReducerFunc(result, series, series.getMin, axis); }, [])), false));
var yMax = Math.max.apply(Math, __spreadArray([], __read(__spreadArray([], __read(data.current.values()), false).reduce(function (result, series) { return dataReducerFunc(result, series, series.getMax, axis); }, [])), false));
if (!isNaN(yMin) && !isNaN(yMax) && isFinite(yMin) && isFinite(yMax))
return [yMin, yMax];
return yDomain[axis];
});
}, [dataGuid, yDomain]);
function handleMouseWheel(evt) {
var _a;
if (!selectedMode.includes('zoom') || !mouseIn)
return;
// while wheel is moving, do not release the lock
clearTimeout(wheelTimeout.current.timeout);
wheelTimeout.current.stopScroll = true;
// flag indicating to lock page scrolling
wheelTimeout.current.timeout = setTimeout(function () {
wheelTimeout.current.stopScroll = false;
}, 200);
var multiplier = 1.25;
// event.deltaY positive is wheel down or out and negative is wheel up or in
if (evt.deltaY < 0)
multiplier = 0.75;
var isAutoY = props.yDomain === 'AutoValue' || props.yDomain === 'HalfAutoValue';
if (selectedMode !== 'zoom-horizontal') {
var x0 = xTransform(tDomain[0]);
var x1 = xTransform(tDomain[1]);
if (mousePosition[0] < offsetLeft)
x1 = multiplier * (x1 - x0) + x0;
else if (mousePosition[0] > (svgWidth - offsetRight))
x0 = x1 - multiplier * (x1 - x0);
else {
var Xcenter = mousePosition[0];
x0 = Xcenter - (Xcenter - x0) * multiplier;
x1 = Xcenter + (x1 - Xcenter) * multiplier;
}
if ((x1 - x0) > 10) {
var newTDomain = void 0;
if ((_a = props.limitZoom) !== null && _a !== void 0 ? _a : false)
newTDomain = [Math.max(defaultTdomain[0], xInvTransform(x0)), Math.min(defaultTdomain[1], xInvTransform(x1))];
else
newTDomain = [xInvTransform(x0), xInvTransform(x1)];
if (selectedMode === 'zoom-vertical' && isAutoY) {
var newYDomain = getConstrainedYDomain(newTDomain);
if (!_.isEqual(newYDomain, yDomain))
setYdomain(newYDomain);
}
updateXDomain(newTDomain);
}
}
if (selectedMode !== 'zoom-vertical') {
var newYDomain = yDomain.map(function (domain, axis, allDomains) {
var _a;
var y0 = yTransform(domain[0], axis);
var y1 = yTransform(domain[1], axis);
if (mousePosition[1] < offsetTop)
y1 = multiplier * (y1 - y0) + y0;
else if (mousePosition[1] > (svgHeight - offsetBottom))
y0 = y1 - multiplier * (y1 - y0);
else {
var Ycenter = mousePosition[1];
y0 = Ycenter - (Ycenter - y0) * multiplier;
y1 = Ycenter + (y1 - Ycenter) * multiplier;
}
if (Math.abs(y1 - y0) > 10) {
if ((_a = props.limitZoom) !== null && _a !== void 0 ? _a : false)
return [Math.max(defaultYdomain[axis][0], yInvTransform(y0, axis)), Math.min(defaultYdomain[axis][1], yInvTransform(y1, axis))];
return [yInvTransform(y0, axis), yInvTransform(y1, axis)];
}
return domain;
});
if (!_.isEqual(newYDomain, yDomain)) {
// todo: added contraint to t domain when mode is zoom-horizontal
setYdomain(newYDomain);
}
}
}
function handleMouseMove(evt) {
if (!moveRequested.current)
requestAnimationFrame(function () { return mouseMoveEvent(evt); });
moveRequested.current = true;
}
function mouseMoveEvent(evt) {
var _a;
moveRequested.current = false;
if (SVGref.current == null)
return;
var pt = SVGref.current.createSVGPoint();
pt.x = evt.clientX;
pt.y = evt.clientY;
var ptTransform = pt.matrixTransform(SVGref.current.getScreenCTM().inverse());
if (mouseMode === 'pan') {
var dP = mousePosition[0] - ptTransform.x;
var Plower = xTransform(tDomain[0]);
var Pupper = xTransform(tDomain[1]);
var Tmin = xInvTransform(Plower + dP);
var Tmax = xInvTransform(Pupper + dP);
if ((props.Tmin === undefined || Tmin > props.Tmin) &&
(props.Tmax === undefined || Tmax < props.Tmax))
updateXDomain([Tmin, Tmax]);
var zoomYAxis = function (domain, axis, allDomains) {
var dY = yInvTransform(mousePosition[1], axis) - yInvTransform(ptTransform.y, axis);
// Need to type correct our arguements
var propMin = typeCorrect(props.Ymin, axis);
var propMax = typeCorrect(props.Ymax, axis);
if ((propMin === undefined || domain[0] + dY > propMin) &&
(propMax === undefined || domain[1] + dY < propMax)) {
allDomains[axis] = [domain[0] + dY, domain[1] + dY];
return true;
}
return false;
};
applyToYDomain(zoomYAxis);
}
setMousePosition([ptTransform.x, ptTransform.y]);
// Here on mouse is snapped (if neccessary)
var ptFinal;
if ((_a = props.snapMouse) !== null && _a !== void 0 ? _a : false)
ptFinal = snapMouseToClosestSeries(ptTransform);
else
ptFinal = ptTransform;
setMousePositionSnap([ptFinal.x, ptFinal.y]);
if (handlers.current.size > 0)
handlers.current.forEach(function (v) { return (v.onMove !== undefined ? v.onMove(xInvTransform(v.allowSnapping ? ptFinal.x : ptTransform.x), yInvTransform(v.allowSnapping ? ptFinal.y : ptTransform.y, v.axis)) : null); });
}
function handleMouseDown(evt) {
var _a, _b, _c, _d;
if (SVGref.current == null)
return;
var pt = SVGref.current.createSVGPoint();
pt.x = evt.clientX;
pt.y = evt.clientY;
var ptTransform = pt.matrixTransform(SVGref.current.getScreenCTM().inverse());
setMouseClick([ptTransform.x, ptTransform.y]);
var zoomEnabled = (_a = props.zoom) !== null && _a !== void 0 ? _a : true;
var horizEnabled = (_b = props.yZoom) !== null && _b !== void 0 ? _b : true;
var vertEnabled = (_c = props.xZoom) !== null && _c !== void 0 ? _c : true;
if (selectedMode === 'zoom-horizontal' && horizEnabled) {
setMouseMode('zoom-horizontal');
}
else if (selectedMode === 'zoom-vertical' && vertEnabled) {
setMouseMode('zoom-vertical');
}
else if (selectedMode === 'zoom-rectangular' && zoomEnabled) {
setMouseMode('zoom-rectangular');
}
if (selectedMode === 'pan' && (props.pan === undefined || props.pan)) {
setMouseMode('pan');
setMouseStyle('grabbing');
}
// Todo: Review question: can we just use mousePosition and mousePositionSnap here?
// Here on mouse is snapped (if neccessary)
var ptFinal;
if ((_d = props.snapMouse) !== null && _d !== void 0 ? _d : false)
ptFinal = snapMouseToClosestSeries(ptTransform);
else
ptFinal = ptTransform;
if (selectedMode === 'select' && props.onSelect !== undefined)
props.onSelect(xInvTransform(ptFinal.x), __spreadArray([], __read(GraphContext_1.AxisMap.values()), false).map(function (axis) { return yInvTransform(ptFinal.y, axis); }), {
setTDomain: updateXDomain,
setYDomain: updateYDomain
});
if (handlers.current.size > 0 && selectedMode === 'select')
handlers.current.forEach(function (v) { return (v.onClick !== undefined ? v.onClick(xInvTransform(v.allowSnapping ? ptFinal.x : ptTransform.x), yInvTransform(v.allowSnapping ? ptFinal.y : ptTransform.y, v.axis)) : null); });
}
function handleMouseUp() {
if (selectedMode === 'pan' && (props.pan === undefined || props.pan))
setMouseStyle('grab');
if (mouseMode.includes('zoom')) {
if ((Math.abs(mousePosition[0] - mouseClick[0]) < 10) && (Math.abs(mousePosition[1] - mouseClick[1]) < 10)) {
setMouseMode('none');
return;
}
var isAutoY = props.yDomain === 'AutoValue' || props.yDomain === 'HalfAutoValue';
if (mouseMode !== 'zoom-horizontal') {
var t0 = Math.min(xInvTransform(mousePosition[0]), xInvTransform(mouseClick[0]));
var t1 = Math.max(xInvTransform(mousePosition[0]), xInvTransform(mouseClick[0]));
var newTDomain = [Math.max(tDomain[0], t0), Math.min(tDomain[1], t1)];
if (selectedMode === 'zoom-vertical') {
var newYDomain = getConstrainedYDomain(newTDomain);
if (!_.isEqual(newYDomain, yDomain) && isAutoY)
setYdomain(newYDomain);
}
updateXDomain(newTDomain);
}
if (mouseMode !== 'zoom-vertical') {
var newYDomain = yDomain.map(function (domain, axis, allDomains) {
var y0 = Math.min(yInvTransform(mousePosition[1], axis), yInvTransform(mouseClick[1], axis));
var y1 = Math.max(yInvTransform(mousePosition[1], axis), yInvTransform(mouseClick[1], axis));
return [Math.max(domain[0], y0), Math.min(domain[1], y1)];
});
if (!_.isEqual(newYDomain, yDomain)) {
// todo: added contraint to t domain when mode is zoom-horizontal
setYdomain(newYDomain);
}
}
}
setMouseMode('none');
if (handlers.current.size > 0 && selectedMode === 'select')
handlers.current.forEach(function (v) { return (v.onRelease !== undefined ? v.onRelease(xInvTransform(v.allowSnapping ? mousePositionSnap[0] : mousePosition[0]), yInvTransform(v.allowSnapping ? mousePositionSnap[1] : mousePosition[1], v.axis)) : null); });
}
function handleMouseOut(_) {
setMouseIn(false);
if (mouseMode === 'pan')
setMouseMode('none');
if (handlers.current.size > 0 && selectedMode === 'select')
handlers.current.forEach(function (v) { return (v.onPlotLeave !== undefined ? v.onPlotLeave(xInvTransform(v.allowSnapping ? mousePositionSnap[0] : mousePosition[0]), yInvTransform(v.allowSnapping ? mousePositionSnap[1] : mousePosition[1], v.axis)) : null); });
}
function handleMouseIn(_) {
setMouseIn(true);
}
function updateYDomain(y) {
var correctFunction = function (domain, axis, allDomains) {
if (y[axis][0] === domain[0] && y[axis][1] === domain[1])
return false;
if (y[0] < y[1])
allDomains[axis] = y[axis];
else
allDomains[axis] = [y[axis][1], y[axis][0]];
return true;
};
applyToYDomain(correctFunction);
}
return (React.createElement(GraphContext_1.ContextWrapper, { XDomain: tDomain, MousePosition: mousePosition, MousePositionSnap: mousePositionSnap, YDomain: yDomain, CurrentMode: selectedMode, MouseIn: mouseIn, UpdateFlag: updateFlag, Data: data, DataGuid: dataGuid, MassEnableCommand: command, XApplyPixelOffset: xApplyOffset, YApplyPixelOffset: yApplyOffset, XTransform: xTransform, YTransform: yTransform, XInvTransform: xInvTransform, YInvTransform: yInvTransform, SetXDomain: updateXDomain, SetYDomain: updateYDomain, AddData: addData, RemoveData: setData, UpdateData: setData, SetLegend: setLegend, RegisterSelect: registerSelect, RemoveSelect: removeSelect, UpdateSelect: updateSelect },
React.createElement("div", { id: guid, style: { height: props.height, width: props.width, position: 'relative' } },
React.createElement("div", { style: { height: svgHeight, width: svgWidth, position: 'absolute', cursor: mouseStyle }, onWheel: handleMouseWheel, onMouseMove: handleMouseMove, onMouseDown: handleMouseDown, onMouseUp: handleMouseUp, onMouseLeave: handleMouseOut, onMouseEnter: handleMouseIn },
React.createElement("svg", { ref: SVGref, width: svgWidth < 0 ? 0 : svgWidth, height: svgHeight < 0 ? 0 : svgHeight, style: SvgStyle, viewBox: "0 0 ".concat(svgWidth < 0 ? 0 : svgWidth, " ").concat(svgHeight < 0 ? 0 : svgHeight) },
props.showBorder !== undefined && props.showBorder ? React.createElement("path", { stroke: 'currentColor', d: "M ".concat(offsetLeft, " ").concat(offsetTop, " H ").concat(svgWidth - offsetRight, " V ").concat(svgHeight - offsetBottom, " H ").concat(offsetLeft, " Z") }) : null,
((_k = props.hideXAxis) !== null && _k !== void 0 ? _k : false) ? null :
(props.XAxisType === 'time' || props.XAxisType === undefined ?
React.createElement(TimeAxis_1.default, { label: props.Tlabel, offsetBottom: offsetBottom, offsetLeft: offsetLeft, offsetRight: offsetRight, width: svgWidth, height: svgHeight, setHeight: setHeightXLabel, heightAxis: heightXLabel, showLeftMostTick: !yHasData[0], showRightMostTick: !yHasData[1], showDate: props.showDateOnTimeAxis }) :
props.XAxisType === 'value' ? React.createElement(XValueAxis_1.default, { offsetBottom: offsetBottom, offsetLeft: offsetLeft, offsetRight: offsetRight, offsetTop: offsetTop, width: svgWidth, height: svgHeight, setHeight: setHeightXLabel, heightAxis: heightXLabel, label: props.Tlabel, showLeftMostTick: !yHasData[0], showRightMostTick: !yHasData[1], showGrid: props.showGrid }) :
React.createElement(LogAxis_1.default, { offsetTop: offsetTop, showGrid: props.showGrid, label: props.Tlabel, offsetBottom: offsetBottom, offsetLeft: offsetLeft, offsetRight: offsetRight, width: svgWidth, height: svgHeight, setHeight: setHeightXLabel, heightAxis: heightXLabel, showLeftMostTick: !yHasData[0], showRightMostTick: !yHasData[1] })),
((_l = props.hideYAxis) !== null && _l !== void 0 ? _l : false) ? null : (React.createElement(React.Fragment, null,
yHasData[0] ? (React.createElement(YValueAxis_1.default, { offsetRight: offsetRight, showGrid: props.showGrid, label: typeCorrect(props.Ylabel, 0), offsetTop: offsetTop, offsetLeft: offsetLeft, offsetBottom: offsetBottom, width: svgWidth, height: svgHeight, setWidthAxis: setHeightLeftYLabel, setHeightFactor: setHeightYFactor, axis: 'left', hAxis: heightLeftYLabel, hFactor: heightYFactor, useFactor: props.useMetricFactors === undefined ? true : props.useMetricFactors })) : null,
yHasData[1] ? (React.createElement(YValueAxis_1.default, { offsetRight: offsetRight, showGrid: props.showGrid, label: typeCorrect(props.Ylabel, 1), offsetTop: offsetTop, offsetLeft: offsetLeft, offsetBottom: offsetBottom, width: svgWidth, height: svgHeight, setWidthAxis: setHeightRightYLabel, setHeightFactor: setHeightYFactor, axis: 'right', hAxis: heightRightYLabel, hFactor: heightYFactor, useFactor: props.useMetricFactors === undefined ? true : props.useMetricFactors })) : null)),
React.createElement("defs", null,
React.createElement("clipPath", { id: "cp-" + guid },
React.createElement("path", { stroke: 'none', fill: 'none', d: " M ".concat(offsetLeft, ",").concat(offsetTop - 5, " H ").concat(svgWidth - offsetRight + 5, " V ").concat(svgHeight - offsetBottom, " H ").concat(offsetLeft, " Z") }))),
React.createElement("g", { clipPath: 'url(#cp-' + guid + ')' },
React.Children.map(props.children, function (element) {
if (!React.isValidElement(element))
return null;
if (element.type === Line_1.default || element.type === LineWithThreshold_1.default || element.type === Infobox_1.default ||
element.type === HorizontalMarker_1.default || element.type === VerticalMarker_1.default || element.type === SymbolicMarker_1.default
|| element.type === Circle_1.default || element.type === AggregatingCircles_1.default || element.type === HeatMapChart_1.default ||
element.type === Pill_1.default || element.type === HighlightBox_1.default || element.type === StreamingLine_1.default ||
element.type === LegendEntry_1.default || element.type === Bar_1.default || element.type === BarAggregate_1.default ||
element.type === CircleGroups_1.default)
return element;
return null;
}),
!photoReady && (props.showMouse === undefined || (props.showMouse !== 'none' && props.showMouse !== false)) ?
React.createElement("path", { stroke: 'currentColor', style: { strokeWidth: 2, opacity: mouseIn ? 0.8 : 0.0 }, d: (props.showMouse !== 'horizontal' ?
"M ".concat(mousePosition[0], " ").concat(offsetTop, " V ").concat(svgHeight - offsetBottom) :
"M ".concat(offsetLeft, " ").concat(mousePosition[1], " H ").concat(svgWidth - offsetRight)) })
: null,
(showZoomButton || showHorizontalZoomButton || showVerticalZoomButton) && mouseMode.includes('zoom') ?
React.createElement("rect", { fillOpacity: 0.8, fill: 'currentColor', x: mouseMode !== 'zoom-horizontal' ? Math.min(mouseClick[0], mousePosition[0]) : offsetLeft, y: mouseMode !== 'zoom-vertical' ? Math.min(mouseClick[1], mousePosition[1]) : offsetTop, width: mouseMode !== 'zoom-horizontal' ? Math.abs(mouseClick[0] - mousePosition[0]) : (svgWidth - offsetLeft - offsetRight), height: mouseMode !== 'zoom-vertical' ? Math.abs(mouseClick[1] - mousePosition[1]) : (svgHeight - offsetTop - offsetBottom) })
: null),
(photoReady || props.menuLocation === 'hide') ? React.createElement(React.Fragment, null) :
React.createElement(InteractiveButtons_1.default, { showPan: showPan, showZoom: showZoomButton, showHorizontalZoom: showHorizontalZoomButton, showVerticalZoom: showVerticalZoomButton, showReset: showReset, showSelect: props.onSelect !== undefined || handlers.current.size > 0, showDownload: props.onDataInspect !== undefined, showCapture: props.onCapture !== undefined, currentSelection: selectedMode, setSelection: setSelection, holdOpen: props.holdMenuOpen, heightAvaliable: svgHeight - 22, setWidth: setMenueWidth, x: (props.menuLocation === 'left' ? 14 : (svgWidth - 14 - menueWidth + 20)), y: 22, "data-html2canvas-ignore": "true" }, React.Children.map(props.children, function (element) {
if (!React.isValidElement(element))
return null;
if (element.type === Button_1.default)
return element;
return null;
})))),
props.legend !== undefined && props.legend !== 'hidden' ?
React.createElement(LegendWithContext_1.default, { location: props.legend, height: legendHeight, width: legendWidthToUse, graphWidth: svgWidth, graphHeight: svgHeight, RequestLegendWidth: requestLegendWidthChange, RequestLegendHeight: requestLegendHeightChange, SendMassCommand: setCurrentCommand, HideDisabled: photoReady })
: null)));
};
//Helper Functions
function getDefaultMouseMode(defaultMouseMode, yDomain, zoom, xZoom, yZoom) {
if (defaultMouseMode != null)
return defaultMouseMode;
if (yDomain === 'AutoValue')
return 'zoom-vertical';
if (zoom !== null && zoom !== void 0 ? zoom : true)
return 'zoom-rectangular';
// If zoom is false, fall back to whichever axis‐only zoom is enabled
if (xZoom !== null && xZoom !== void 0 ? xZoom : true)
return 'zoom-vertical';
if (yZoom !== null && yZoom !== void 0 ?