UNPKG

@gpa-gemstone/react-graph

Version:
856 lines 110 kB
"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 ?