UNPKG

dygraphs

Version:

dygraphs is a fast, flexible open source JavaScript charting library.

796 lines (752 loc) 102 kB
/** * @license * Copyright 2006 Dan Vanderkam (danvdk@gmail.com) * MIT-licenced: https://opensource.org/licenses/MIT */ /** * @fileoverview Based on PlotKit.CanvasRenderer, but modified to meet the * needs of dygraphs. * * In particular, support for: * - grid overlays * - high/low bands * - dygraphs attribute system */ /** * The DygraphCanvasRenderer class does the actual rendering of the chart onto * a canvas. It's based on PlotKit.CanvasRenderer. * @param {Object} element The canvas to attach to * @param {Object} elementContext The 2d context of the canvas (injected so it * can be mocked for testing.) * @param {Layout} layout The DygraphLayout object for this graph. * @constructor */ /*global Dygraph:false */ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports["default"] = void 0; var utils = _interopRequireWildcard(require("./dygraph-utils")); var _dygraph = _interopRequireDefault(require("./dygraph")); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); } function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { "default": obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj["default"] = obj; if (cache) { cache.set(obj, newObj); } return newObj; } /** * @constructor * * This gets called when there are "new points" to chart. This is generally the * case when the underlying data being charted has changed. It is _not_ called * in the common case that the user has zoomed or is panning the view. * * The chart canvas has already been created by the Dygraph object. The * renderer simply gets a drawing context. * * @param {Dygraph} dygraph The chart to which this renderer belongs. * @param {HTMLCanvasElement} element The &lt;canvas&gt; DOM element on which to draw. * @param {CanvasRenderingContext2D} elementContext The drawing context. * @param {DygraphLayout} layout The chart's DygraphLayout object. * * TODO(danvk): remove the elementContext property. */ var DygraphCanvasRenderer = function DygraphCanvasRenderer(dygraph, element, elementContext, layout) { this.dygraph_ = dygraph; this.layout = layout; this.element = element; this.elementContext = elementContext; this.height = dygraph.height_; this.width = dygraph.width_; // --- check whether everything is ok before we return if (!utils.isCanvasSupported(this.element)) { throw "Canvas is not supported."; } // internal state this.area = layout.getPlotArea(); // Set up a clipping area for the canvas (and the interaction canvas). // This ensures that we don't overdraw. var ctx = this.dygraph_.canvas_ctx_; ctx.beginPath(); ctx.rect(this.area.x, this.area.y, this.area.w, this.area.h); ctx.clip(); ctx = this.dygraph_.hidden_ctx_; ctx.beginPath(); ctx.rect(this.area.x, this.area.y, this.area.w, this.area.h); ctx.clip(); }; /** * Clears out all chart content and DOM elements. * This is called immediately before render() on every frame, including * during zooms and pans. * @private */ DygraphCanvasRenderer.prototype.clear = function () { this.elementContext.clearRect(0, 0, this.width, this.height); }; /** * This method is responsible for drawing everything on the chart, including * lines, high/low bands, fills and axes. * It is called immediately after clear() on every frame, including during pans * and zooms. * @private */ DygraphCanvasRenderer.prototype.render = function () { // attaches point.canvas{x,y} this._updatePoints(); // actually draws the chart. this._renderLineChart(); }; /** * Returns a predicate to be used with an iterator, which will * iterate over points appropriately, depending on whether * connectSeparatedPoints is true. When it's false, the predicate will * skip over points with missing yVals. */ DygraphCanvasRenderer._getIteratorPredicate = function (connectSeparatedPoints) { return connectSeparatedPoints ? DygraphCanvasRenderer._predicateThatSkipsEmptyPoints : null; }; DygraphCanvasRenderer._predicateThatSkipsEmptyPoints = function (array, idx) { return array[idx].yval !== null; }; /** * Draws a line with the styles passed in and calls all the drawPointCallbacks. * @param {Object} e The dictionary passed to the plotter function. * @private */ DygraphCanvasRenderer._drawStyledLine = function (e, color, strokeWidth, strokePattern, drawPoints, drawPointCallback, pointSize) { var g = e.dygraph; // TODO(konigsberg): Compute attributes outside this method call. var stepPlot = g.getBooleanOption("stepPlot", e.setName); if (!utils.isArrayLike(strokePattern)) { strokePattern = null; } var drawGapPoints = g.getBooleanOption('drawGapEdgePoints', e.setName); var points = e.points; var setName = e.setName; var iter = utils.createIterator(points, 0, points.length, DygraphCanvasRenderer._getIteratorPredicate(g.getBooleanOption("connectSeparatedPoints", setName))); var stroking = strokePattern && strokePattern.length >= 2; var ctx = e.drawingContext; ctx.save(); if (stroking) { if (ctx.setLineDash) ctx.setLineDash(strokePattern); } var pointsOnLine = DygraphCanvasRenderer._drawSeries(e, iter, strokeWidth, pointSize, drawPoints, drawGapPoints, stepPlot, color); DygraphCanvasRenderer._drawPointsOnLine(e, pointsOnLine, drawPointCallback, color, pointSize); if (stroking) { if (ctx.setLineDash) ctx.setLineDash([]); } ctx.restore(); }; /** * This does the actual drawing of lines on the canvas, for just one series. * Returns a list of [canvasx, canvasy] pairs for points for which a * drawPointCallback should be fired. These include isolated points, or all * points if drawPoints=true. * @param {Object} e The dictionary passed to the plotter function. * @private */ DygraphCanvasRenderer._drawSeries = function (e, iter, strokeWidth, pointSize, drawPoints, drawGapPoints, stepPlot, color) { var prevCanvasX = null; var prevCanvasY = null; var nextCanvasY = null; var isIsolated; // true if this point is isolated (no line segments) var point; // the point being processed in the while loop var pointsOnLine = []; // Array of [canvasx, canvasy] pairs. var first = true; // the first cycle through the while loop var ctx = e.drawingContext; ctx.beginPath(); ctx.strokeStyle = color; ctx.lineWidth = strokeWidth; // NOTE: we break the iterator's encapsulation here for about a 25% speedup. var arr = iter.array_; var limit = iter.end_; var predicate = iter.predicate_; for (var i = iter.start_; i < limit; i++) { point = arr[i]; if (predicate) { while (i < limit && !predicate(arr, i)) { i++; } if (i == limit) break; point = arr[i]; } // FIXME: The 'canvasy != canvasy' test here catches NaN values but the test // doesn't catch Infinity values. Could change this to // !isFinite(point.canvasy), but I assume it avoids isNaN for performance? if (point.canvasy === null || point.canvasy != point.canvasy) { if (stepPlot && prevCanvasX !== null) { // Draw a horizontal line to the start of the missing data ctx.moveTo(prevCanvasX, prevCanvasY); ctx.lineTo(point.canvasx, prevCanvasY); } prevCanvasX = prevCanvasY = null; } else { isIsolated = false; if (drawGapPoints || prevCanvasX === null) { iter.nextIdx_ = i; iter.next(); nextCanvasY = iter.hasNext ? iter.peek.canvasy : null; var isNextCanvasYNullOrNaN = nextCanvasY === null || nextCanvasY != nextCanvasY; isIsolated = prevCanvasX === null && isNextCanvasYNullOrNaN; if (drawGapPoints) { // Also consider a point to be "isolated" if it's adjacent to a // null point, excluding the graph edges. if (!first && prevCanvasX === null || iter.hasNext && isNextCanvasYNullOrNaN) { isIsolated = true; } } } if (prevCanvasX !== null) { if (strokeWidth) { if (stepPlot) { ctx.moveTo(prevCanvasX, prevCanvasY); ctx.lineTo(point.canvasx, prevCanvasY); } ctx.lineTo(point.canvasx, point.canvasy); } } else { ctx.moveTo(point.canvasx, point.canvasy); } if (drawPoints || isIsolated) { pointsOnLine.push([point.canvasx, point.canvasy, point.idx]); } prevCanvasX = point.canvasx; prevCanvasY = point.canvasy; } first = false; } ctx.stroke(); return pointsOnLine; }; /** * This fires the drawPointCallback functions, which draw dots on the points by * default. This gets used when the "drawPoints" option is set, or when there * are isolated points. * @param {Object} e The dictionary passed to the plotter function. * @private */ DygraphCanvasRenderer._drawPointsOnLine = function (e, pointsOnLine, drawPointCallback, color, pointSize) { var ctx = e.drawingContext; for (var idx = 0; idx < pointsOnLine.length; idx++) { var cb = pointsOnLine[idx]; ctx.save(); drawPointCallback.call(e.dygraph, e.dygraph, e.setName, ctx, cb[0], cb[1], color, pointSize, cb[2]); ctx.restore(); } }; /** * Attaches canvas coordinates to the points array. * @private */ DygraphCanvasRenderer.prototype._updatePoints = function () { // Update Points // TODO(danvk): here // // TODO(bhs): this loop is a hot-spot for high-point-count charts. These // transformations can be pushed into the canvas via linear transformation // matrices. // NOTE(danvk): this is trickier than it sounds at first. The transformation // needs to be done before the .moveTo() and .lineTo() calls, but must be // undone before the .stroke() call to ensure that the stroke width is // unaffected. An alternative is to reduce the stroke width in the // transformed coordinate space, but you can't specify different values for // each dimension (as you can with .scale()). The speedup here is ~12%. var sets = this.layout.points; for (var i = sets.length; i--;) { var points = sets[i]; for (var j = points.length; j--;) { var point = points[j]; point.canvasx = this.area.w * point.x + this.area.x; point.canvasy = this.area.h * point.y + this.area.y; } } }; /** * Add canvas Actually draw the lines chart, including high/low bands. * * This function can only be called if DygraphLayout's points array has been * updated with canvas{x,y} attributes, i.e. by * DygraphCanvasRenderer._updatePoints. * * @param {string=} opt_seriesName when specified, only that series will * be drawn. (This is used for expedited redrawing with highlightSeriesOpts) * @param {CanvasRenderingContext2D} opt_ctx when specified, the drawing * context. However, lines are typically drawn on the object's * elementContext. * @private */ DygraphCanvasRenderer.prototype._renderLineChart = function (opt_seriesName, opt_ctx) { var ctx = opt_ctx || this.elementContext; var i; var sets = this.layout.points; var setNames = this.layout.setNames; var setName; this.colors = this.dygraph_.colorsMap_; // Determine which series have specialized plotters. var plotter_attr = this.dygraph_.getOption("plotter"); var plotters = plotter_attr; if (!utils.isArrayLike(plotters)) { plotters = [plotters]; } var setPlotters = {}; // series name -> plotter fn. for (i = 0; i < setNames.length; i++) { setName = setNames[i]; var setPlotter = this.dygraph_.getOption("plotter", setName); if (setPlotter == plotter_attr) continue; // not specialized. setPlotters[setName] = setPlotter; } for (i = 0; i < plotters.length; i++) { var plotter = plotters[i]; var is_last = i == plotters.length - 1; for (var j = 0; j < sets.length; j++) { setName = setNames[j]; if (opt_seriesName && setName != opt_seriesName) continue; var points = sets[j]; // Only throw in the specialized plotters on the last iteration. var p = plotter; if (setName in setPlotters) { if (is_last) { p = setPlotters[setName]; } else { // Don't use the standard plotters in this case. continue; } } var color = this.colors[setName]; var strokeWidth = this.dygraph_.getOption("strokeWidth", setName); ctx.save(); ctx.strokeStyle = color; ctx.lineWidth = strokeWidth; p({ points: points, setName: setName, drawingContext: ctx, color: color, strokeWidth: strokeWidth, dygraph: this.dygraph_, axis: this.dygraph_.axisPropertiesForSeries(setName), plotArea: this.area, seriesIndex: j, seriesCount: sets.length, singleSeriesName: opt_seriesName, allSeriesPoints: sets }); ctx.restore(); } } }; /** * Standard plotters. These may be used by clients via Dygraph.Plotters. * See comments there for more details. */ DygraphCanvasRenderer._Plotters = { linePlotter: function linePlotter(e) { DygraphCanvasRenderer._linePlotter(e); }, fillPlotter: function fillPlotter(e) { DygraphCanvasRenderer._fillPlotter(e); }, errorPlotter: function errorPlotter(e) { DygraphCanvasRenderer._errorPlotter(e); } }; /** * Plotter which draws the central lines for a series. * @private */ DygraphCanvasRenderer._linePlotter = function (e) { var g = e.dygraph; var setName = e.setName; var strokeWidth = e.strokeWidth; // TODO(danvk): Check if there's any performance impact of just calling // getOption() inside of _drawStyledLine. Passing in so many parameters makes // this code a bit nasty. var borderWidth = g.getNumericOption("strokeBorderWidth", setName); var drawPointCallback = g.getOption("drawPointCallback", setName) || utils.Circles.DEFAULT; var strokePattern = g.getOption("strokePattern", setName); var drawPoints = g.getBooleanOption("drawPoints", setName); var pointSize = g.getNumericOption("pointSize", setName); if (borderWidth && strokeWidth) { DygraphCanvasRenderer._drawStyledLine(e, g.getOption("strokeBorderColor", setName), strokeWidth + 2 * borderWidth, strokePattern, drawPoints, drawPointCallback, pointSize); } DygraphCanvasRenderer._drawStyledLine(e, e.color, strokeWidth, strokePattern, drawPoints, drawPointCallback, pointSize); }; /** * Draws the shaded high/low bands (confidence intervals) for each series. * This happens before the center lines are drawn, since the center lines * need to be drawn on top of the high/low bands for all series. * @private */ DygraphCanvasRenderer._errorPlotter = function (e) { var g = e.dygraph; var setName = e.setName; var errorBars = g.getBooleanOption("errorBars") || g.getBooleanOption("customBars"); if (!errorBars) return; var fillGraph = g.getBooleanOption("fillGraph", setName); if (fillGraph) { console.warn("Can't use fillGraph option with customBars or errorBars option"); } var ctx = e.drawingContext; var color = e.color; var fillAlpha = g.getNumericOption('fillAlpha', setName); var stepPlot = g.getBooleanOption("stepPlot", setName); var points = e.points; var iter = utils.createIterator(points, 0, points.length, DygraphCanvasRenderer._getIteratorPredicate(g.getBooleanOption("connectSeparatedPoints", setName))); var newYs; // setup graphics context var prevX = NaN; var prevY = NaN; var prevYs = [-1, -1]; // should be same color as the lines but only 15% opaque. var rgb = utils.toRGB_(color); var err_color = 'rgba(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ',' + fillAlpha + ')'; ctx.fillStyle = err_color; ctx.beginPath(); var isNullUndefinedOrNaN = function isNullUndefinedOrNaN(x) { return x === null || x === undefined || isNaN(x); }; while (iter.hasNext) { var point = iter.next(); if (!stepPlot && isNullUndefinedOrNaN(point.y) || stepPlot && !isNaN(prevY) && isNullUndefinedOrNaN(prevY)) { prevX = NaN; continue; } newYs = [point.y_bottom, point.y_top]; if (stepPlot) { prevY = point.y; } // The documentation specifically disallows nulls inside the point arrays, // but in case it happens we should do something sensible. if (isNaN(newYs[0])) newYs[0] = point.y; if (isNaN(newYs[1])) newYs[1] = point.y; newYs[0] = e.plotArea.h * newYs[0] + e.plotArea.y; newYs[1] = e.plotArea.h * newYs[1] + e.plotArea.y; if (!isNaN(prevX)) { if (stepPlot) { ctx.moveTo(prevX, prevYs[0]); ctx.lineTo(point.canvasx, prevYs[0]); ctx.lineTo(point.canvasx, prevYs[1]); } else { ctx.moveTo(prevX, prevYs[0]); ctx.lineTo(point.canvasx, newYs[0]); ctx.lineTo(point.canvasx, newYs[1]); } ctx.lineTo(prevX, prevYs[1]); ctx.closePath(); } prevYs = newYs; prevX = point.canvasx; } ctx.fill(); }; /** * Proxy for CanvasRenderingContext2D which drops moveTo/lineTo calls which are * superfluous. It accumulates all movements which haven't changed the x-value * and only applies the two with the most extreme y-values. * * Calls to lineTo/moveTo must have non-decreasing x-values. */ DygraphCanvasRenderer._fastCanvasProxy = function (context) { var pendingActions = []; // array of [type, x, y] tuples var lastRoundedX = null; var lastFlushedX = null; var LINE_TO = 1, MOVE_TO = 2; var actionCount = 0; // number of moveTos and lineTos passed to context. // Drop superfluous motions // Assumes all pendingActions have the same (rounded) x-value. var compressActions = function compressActions(opt_losslessOnly) { if (pendingActions.length <= 1) return; // Lossless compression: drop inconsequential moveTos. for (var i = pendingActions.length - 1; i > 0; i--) { var action = pendingActions[i]; if (action[0] == MOVE_TO) { var prevAction = pendingActions[i - 1]; if (prevAction[1] == action[1] && prevAction[2] == action[2]) { pendingActions.splice(i, 1); } } } // Lossless compression: ... drop consecutive moveTos ... for /* incremented internally */ (var i = 0; i < pendingActions.length - 1;) { var action = pendingActions[i]; if (action[0] == MOVE_TO && pendingActions[i + 1][0] == MOVE_TO) { pendingActions.splice(i, 1); } else { i++; } } // Lossy compression: ... drop all but the extreme y-values ... if (pendingActions.length > 2 && !opt_losslessOnly) { // keep an initial moveTo, but drop all others. var startIdx = 0; if (pendingActions[0][0] == MOVE_TO) startIdx++; var minIdx = null, maxIdx = null; for (var i = startIdx; i < pendingActions.length; i++) { var action = pendingActions[i]; if (action[0] != LINE_TO) continue; if (minIdx === null && maxIdx === null) { minIdx = i; maxIdx = i; } else { var y = action[2]; if (y < pendingActions[minIdx][2]) { minIdx = i; } else if (y > pendingActions[maxIdx][2]) { maxIdx = i; } } } var minAction = pendingActions[minIdx], maxAction = pendingActions[maxIdx]; pendingActions.splice(startIdx, pendingActions.length - startIdx); if (minIdx < maxIdx) { pendingActions.push(minAction); pendingActions.push(maxAction); } else if (minIdx > maxIdx) { pendingActions.push(maxAction); pendingActions.push(minAction); } else { pendingActions.push(minAction); } } }; var flushActions = function flushActions(opt_noLossyCompression) { compressActions(opt_noLossyCompression); for (var i = 0, len = pendingActions.length; i < len; i++) { var action = pendingActions[i]; if (action[0] == LINE_TO) { context.lineTo(action[1], action[2]); } else if (action[0] == MOVE_TO) { context.moveTo(action[1], action[2]); } } if (pendingActions.length) { lastFlushedX = pendingActions[pendingActions.length - 1][1]; } actionCount += pendingActions.length; pendingActions = []; }; var addAction = function addAction(action, x, y) { var rx = Math.round(x); if (lastRoundedX === null || rx != lastRoundedX) { // if there are large gaps on the x-axis, it's essential to keep the // first and last point as well. var hasGapOnLeft = lastRoundedX - lastFlushedX > 1, hasGapOnRight = rx - lastRoundedX > 1, hasGap = hasGapOnLeft || hasGapOnRight; flushActions(hasGap); lastRoundedX = rx; } pendingActions.push([action, x, y]); }; return { moveTo: function moveTo(x, y) { addAction(MOVE_TO, x, y); }, lineTo: function lineTo(x, y) { addAction(LINE_TO, x, y); }, // for major operations like stroke/fill, we skip compression to ensure // that there are no artifacts at the right edge. stroke: function stroke() { flushActions(true); context.stroke(); }, fill: function fill() { flushActions(true); context.fill(); }, beginPath: function beginPath() { flushActions(true); context.beginPath(); }, closePath: function closePath() { flushActions(true); context.closePath(); }, _count: function _count() { return actionCount; } }; }; /** * Draws the shaded regions when "fillGraph" is set. * Not to be confused with high/low bands (historically misnamed errorBars). * * For stacked charts, it's more convenient to handle all the series * simultaneously. So this plotter plots all the points on the first series * it's asked to draw, then ignores all the other series. * * @private */ DygraphCanvasRenderer._fillPlotter = function (e) { // Skip if we're drawing a single series for interactive highlight overlay. if (e.singleSeriesName) return; // We'll handle all the series at once, not one-by-one. if (e.seriesIndex !== 0) return; var g = e.dygraph; var setNames = g.getLabels().slice(1); // remove x-axis // getLabels() includes names for invisible series, which are not included in // allSeriesPoints. We remove those to make the two match. // TODO(danvk): provide a simpler way to get this information. for (var i = setNames.length; i >= 0; i--) { if (!g.visibility()[i]) setNames.splice(i, 1); } var anySeriesFilled = function () { for (var i = 0; i < setNames.length; i++) { if (g.getBooleanOption("fillGraph", setNames[i])) return true; } return false; }(); if (!anySeriesFilled) return; var area = e.plotArea; var sets = e.allSeriesPoints; var setCount = sets.length; var stackedGraph = g.getBooleanOption("stackedGraph"); var colors = g.getColors(); // For stacked graphs, track the baseline for filling. // // The filled areas below graph lines are trapezoids with two // vertical edges. The top edge is the line segment being drawn, and // the baseline is the bottom edge. Each baseline corresponds to the // top line segment from the previous stacked line. In the case of // step plots, the trapezoids are rectangles. var baseline = {}; var currBaseline; var prevStepPlot; // for different line drawing modes (line/step) per series // Helper function to trace a line back along the baseline. var traceBackPath = function traceBackPath(ctx, baselineX, baselineY, pathBack) { ctx.lineTo(baselineX, baselineY); if (stackedGraph) { for (var i = pathBack.length - 1; i >= 0; i--) { var pt = pathBack[i]; ctx.lineTo(pt[0], pt[1]); } } }; // process sets in reverse order (needed for stacked graphs) for (var setIdx = setCount - 1; setIdx >= 0; setIdx--) { var ctx = e.drawingContext; var setName = setNames[setIdx]; if (!g.getBooleanOption('fillGraph', setName)) continue; var fillAlpha = g.getNumericOption('fillAlpha', setName); var stepPlot = g.getBooleanOption('stepPlot', setName); var color = colors[setIdx]; var axis = g.axisPropertiesForSeries(setName); var axisY = 1.0 + axis.minyval * axis.yscale; if (axisY < 0.0) axisY = 0.0;else if (axisY > 1.0) axisY = 1.0; axisY = area.h * axisY + area.y; var points = sets[setIdx]; var iter = utils.createIterator(points, 0, points.length, DygraphCanvasRenderer._getIteratorPredicate(g.getBooleanOption("connectSeparatedPoints", setName))); // setup graphics context var prevX = NaN; var prevYs = [-1, -1]; var newYs; // should be same color as the lines but only 15% opaque. var rgb = utils.toRGB_(color); var err_color = 'rgba(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ',' + fillAlpha + ')'; ctx.fillStyle = err_color; ctx.beginPath(); var last_x, is_first = true; // If the point density is high enough, dropping segments on their way to // the canvas justifies the overhead of doing so. if (points.length > 2 * g.width_ || _dygraph["default"].FORCE_FAST_PROXY) { ctx = DygraphCanvasRenderer._fastCanvasProxy(ctx); } // For filled charts, we draw points from left to right, then back along // the x-axis to complete a shape for filling. // For stacked plots, this "back path" is a more complex shape. This array // stores the [x, y] values needed to trace that shape. var pathBack = []; // TODO(danvk): there are a lot of options at play in this loop. // The logic would be much clearer if some (e.g. stackGraph and // stepPlot) were split off into separate sub-plotters. var point; while (iter.hasNext) { point = iter.next(); if (!utils.isOK(point.y) && !stepPlot) { traceBackPath(ctx, prevX, prevYs[1], pathBack); pathBack = []; prevX = NaN; if (point.y_stacked !== null && !isNaN(point.y_stacked)) { baseline[point.canvasx] = area.h * point.y_stacked + area.y; } continue; } if (stackedGraph) { if (!is_first && last_x == point.xval) { continue; } else { is_first = false; last_x = point.xval; } currBaseline = baseline[point.canvasx]; var lastY; if (currBaseline === undefined) { lastY = axisY; } else { if (prevStepPlot) { lastY = currBaseline[0]; } else { lastY = currBaseline; } } newYs = [point.canvasy, lastY]; if (stepPlot) { // Step plots must keep track of the top and bottom of // the baseline at each point. if (prevYs[0] === -1) { baseline[point.canvasx] = [point.canvasy, axisY]; } else { baseline[point.canvasx] = [point.canvasy, prevYs[0]]; } } else { baseline[point.canvasx] = point.canvasy; } } else { if (isNaN(point.canvasy) && stepPlot) { newYs = [area.y + area.h, axisY]; } else { newYs = [point.canvasy, axisY]; } } if (!isNaN(prevX)) { // Move to top fill point if (stepPlot) { ctx.lineTo(point.canvasx, prevYs[0]); ctx.lineTo(point.canvasx, newYs[0]); } else { ctx.lineTo(point.canvasx, newYs[0]); } // Record the baseline for the reverse path. if (stackedGraph) { pathBack.push([prevX, prevYs[1]]); if (prevStepPlot && currBaseline) { // Draw to the bottom of the baseline pathBack.push([point.canvasx, currBaseline[1]]); } else { pathBack.push([point.canvasx, newYs[1]]); } } } else { ctx.moveTo(point.canvasx, newYs[1]); ctx.lineTo(point.canvasx, newYs[0]); } prevYs = newYs; prevX = point.canvasx; } prevStepPlot = stepPlot; if (newYs && point) { traceBackPath(ctx, point.canvasx, newYs[1], pathBack); pathBack = []; } ctx.fill(); } }; var _default = DygraphCanvasRenderer; exports["default"] = _default; module.exports = exports.default; //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJEeWdyYXBoQ2FudmFzUmVuZGVyZXIiLCJkeWdyYXBoIiwiZWxlbWVudCIsImVsZW1lbnRDb250ZXh0IiwibGF5b3V0IiwiZHlncmFwaF8iLCJoZWlnaHQiLCJoZWlnaHRfIiwid2lkdGgiLCJ3aWR0aF8iLCJ1dGlscyIsImlzQ2FudmFzU3VwcG9ydGVkIiwiYXJlYSIsImdldFBsb3RBcmVhIiwiY3R4IiwiY2FudmFzX2N0eF8iLCJiZWdpblBhdGgiLCJyZWN0IiwieCIsInkiLCJ3IiwiaCIsImNsaXAiLCJoaWRkZW5fY3R4XyIsInByb3RvdHlwZSIsImNsZWFyIiwiY2xlYXJSZWN0IiwicmVuZGVyIiwiX3VwZGF0ZVBvaW50cyIsIl9yZW5kZXJMaW5lQ2hhcnQiLCJfZ2V0SXRlcmF0b3JQcmVkaWNhdGUiLCJjb25uZWN0U2VwYXJhdGVkUG9pbnRzIiwiX3ByZWRpY2F0ZVRoYXRTa2lwc0VtcHR5UG9pbnRzIiwiYXJyYXkiLCJpZHgiLCJ5dmFsIiwiX2RyYXdTdHlsZWRMaW5lIiwiZSIsImNvbG9yIiwic3Ryb2tlV2lkdGgiLCJzdHJva2VQYXR0ZXJuIiwiZHJhd1BvaW50cyIsImRyYXdQb2ludENhbGxiYWNrIiwicG9pbnRTaXplIiwiZyIsInN0ZXBQbG90IiwiZ2V0Qm9vbGVhbk9wdGlvbiIsInNldE5hbWUiLCJpc0FycmF5TGlrZSIsImRyYXdHYXBQb2ludHMiLCJwb2ludHMiLCJpdGVyIiwiY3JlYXRlSXRlcmF0b3IiLCJsZW5ndGgiLCJzdHJva2luZyIsImRyYXdpbmdDb250ZXh0Iiwic2F2ZSIsInNldExpbmVEYXNoIiwicG9pbnRzT25MaW5lIiwiX2RyYXdTZXJpZXMiLCJfZHJhd1BvaW50c09uTGluZSIsInJlc3RvcmUiLCJwcmV2Q2FudmFzWCIsInByZXZDYW52YXNZIiwibmV4dENhbnZhc1kiLCJpc0lzb2xhdGVkIiwicG9pbnQiLCJmaXJzdCIsInN0cm9rZVN0eWxlIiwibGluZVdpZHRoIiwiYXJyIiwiYXJyYXlfIiwibGltaXQiLCJlbmRfIiwicHJlZGljYXRlIiwicHJlZGljYXRlXyIsImkiLCJzdGFydF8iLCJjYW52YXN5IiwibW92ZVRvIiwibGluZVRvIiwiY2FudmFzeCIsIm5leHRJZHhfIiwibmV4dCIsImhhc05leHQiLCJwZWVrIiwiaXNOZXh0Q2FudmFzWU51bGxPck5hTiIsInB1c2giLCJzdHJva2UiLCJjYiIsImNhbGwiLCJzZXRzIiwiaiIsIm9wdF9zZXJpZXNOYW1lIiwib3B0X2N0eCIsInNldE5hbWVzIiwiY29sb3JzIiwiY29sb3JzTWFwXyIsInBsb3R0ZXJfYXR0ciIsImdldE9wdGlvbiIsInBsb3R0ZXJzIiwic2V0UGxvdHRlcnMiLCJzZXRQbG90dGVyIiwicGxvdHRlciIsImlzX2xhc3QiLCJwIiwiYXhpcyIsImF4aXNQcm9wZXJ0aWVzRm9yU2VyaWVzIiwicGxvdEFyZWEiLCJzZXJpZXNJbmRleCIsInNlcmllc0NvdW50Iiwic2luZ2xlU2VyaWVzTmFtZSIsImFsbFNlcmllc1BvaW50cyIsIl9QbG90dGVycyIsImxpbmVQbG90dGVyIiwiX2xpbmVQbG90dGVyIiwiZmlsbFBsb3R0ZXIiLCJfZmlsbFBsb3R0ZXIiLCJlcnJvclBsb3R0ZXIiLCJfZXJyb3JQbG90dGVyIiwiYm9yZGVyV2lkdGgiLCJnZXROdW1lcmljT3B0aW9uIiwiQ2lyY2xlcyIsIkRFRkFVTFQiLCJlcnJvckJhcnMiLCJmaWxsR3JhcGgiLCJjb25zb2xlIiwid2FybiIsImZpbGxBbHBoYSIsIm5ld1lzIiwicHJldlgiLCJOYU4iLCJwcmV2WSIsInByZXZZcyIsInJnYiIsInRvUkdCXyIsImVycl9jb2xvciIsInIiLCJiIiwiZmlsbFN0eWxlIiwiaXNOdWxsVW5kZWZpbmVkT3JOYU4iLCJ1bmRlZmluZWQiLCJpc05hTiIsInlfYm90dG9tIiwieV90b3AiLCJjbG9zZVBhdGgiLCJmaWxsIiwiX2Zhc3RDYW52YXNQcm94eSIsImNvbnRleHQiLCJwZW5kaW5nQWN0aW9ucyIsImxhc3RSb3VuZGVkWCIsImxhc3RGbHVzaGVkWCIsIkxJTkVfVE8iLCJNT1ZFX1RPIiwiYWN0aW9uQ291bnQiLCJjb21wcmVzc0FjdGlvbnMiLCJvcHRfbG9zc2xlc3NPbmx5IiwiYWN0aW9uIiwicHJldkFjdGlvbiIsInNwbGljZSIsInN0YXJ0SWR4IiwibWluSWR4IiwibWF4SWR4IiwibWluQWN0aW9uIiwibWF4QWN0aW9uIiwiZmx1c2hBY3Rpb25zIiwib3B0X25vTG9zc3lDb21wcmVzc2lvbiIsImxlbiIsImFkZEFjdGlvbiIsInJ4IiwiTWF0aCIsInJvdW5kIiwiaGFzR2FwT25MZWZ0IiwiaGFzR2FwT25SaWdodCIsImhhc0dhcCIsIl9jb3VudCIsImdldExhYmVscyIsInNsaWNlIiwidmlzaWJpbGl0eSIsImFueVNlcmllc0ZpbGxlZCIsInNldENvdW50Iiwic3RhY2tlZEdyYXBoIiwiZ2V0Q29sb3JzIiwiYmFzZWxpbmUiLCJjdXJyQmFzZWxpbmUiLCJwcmV2U3RlcFBsb3QiLCJ0cmFjZUJhY2tQYXRoIiwiYmFzZWxpbmVYIiwiYmFzZWxpbmVZIiwicGF0aEJhY2siLCJwdCIsInNldElkeCIsImF4aXNZIiwibWlueXZhbCIsInlzY2FsZSIsImxhc3RfeCIsImlzX2ZpcnN0IiwiRHlncmFwaCIsIkZPUkNFX0ZBU1RfUFJPWFkiLCJpc09LIiwieV9zdGFja2VkIiwieHZhbCIsImxhc3RZIl0sInNvdXJjZXMiOlsiLi4vc3JjL2R5Z3JhcGgtY2FudmFzLmpzIl0sInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogQGxpY2Vuc2VcbiAqIENvcHlyaWdodCAyMDA2IERhbiBWYW5kZXJrYW0gKGRhbnZka0BnbWFpbC5jb20pXG4gKiBNSVQtbGljZW5jZWQ6IGh0dHBzOi8vb3BlbnNvdXJjZS5vcmcvbGljZW5zZXMvTUlUXG4gKi9cblxuLyoqXG4gKiBAZmlsZW92ZXJ2aWV3IEJhc2VkIG9uIFBsb3RLaXQuQ2FudmFzUmVuZGVyZXIsIGJ1dCBtb2RpZmllZCB0byBtZWV0IHRoZVxuICogbmVlZHMgb2YgZHlncmFwaHMuXG4gKlxuICogSW4gcGFydGljdWxhciwgc3VwcG9ydCBmb3I6XG4gKiAtIGdyaWQgb3ZlcmxheXNcbiAqIC0gaGlnaC9sb3cgYmFuZHNcbiAqIC0gZHlncmFwaHMgYXR0cmlidXRlIHN5c3RlbVxuICovXG5cbi8qKlxuICogVGhlIER5Z3JhcGhDYW52YXNSZW5kZXJlciBjbGFzcyBkb2VzIHRoZSBhY3R1YWwgcmVuZGVyaW5nIG9mIHRoZSBjaGFydCBvbnRvXG4gKiBhIGNhbnZhcy4gSXQncyBiYXNlZCBvbiBQbG90S2l0LkNhbnZhc1JlbmRlcmVyLlxuICogQHBhcmFtIHtPYmplY3R9IGVsZW1lbnQgVGhlIGNhbnZhcyB0byBhdHRhY2ggdG9cbiAqIEBwYXJhbSB7T2JqZWN0fSBlbGVtZW50Q29udGV4dCBUaGUgMmQgY29udGV4dCBvZiB0aGUgY2FudmFzIChpbmplY3RlZCBzbyBpdFxuICogY2FuIGJlIG1vY2tlZCBmb3IgdGVzdGluZy4pXG4gKiBAcGFyYW0ge0xheW91dH0gbGF5b3V0IFRoZSBEeWdyYXBoTGF5b3V0IG9iamVjdCBmb3IgdGhpcyBncmFwaC5cbiAqIEBjb25zdHJ1Y3RvclxuICovXG5cbi8qZ2xvYmFsIER5Z3JhcGg6ZmFsc2UgKi9cblwidXNlIHN0cmljdFwiO1xuXG5pbXBvcnQgKiBhcyB1dGlscyBmcm9tICcuL2R5Z3JhcGgtdXRpbHMnO1xuaW1wb3J0IER5Z3JhcGggZnJvbSAnLi9keWdyYXBoJztcblxuLyoqXG4gKiBAY29uc3RydWN0b3JcbiAqXG4gKiBUaGlzIGdldHMgY2FsbGVkIHdoZW4gdGhlcmUgYXJlIFwibmV3IHBvaW50c1wiIHRvIGNoYXJ0LiBUaGlzIGlzIGdlbmVyYWxseSB0aGVcbiAqIGNhc2Ugd2hlbiB0aGUgdW5kZXJseWluZyBkYXRhIGJlaW5nIGNoYXJ0ZWQgaGFzIGNoYW5nZWQuIEl0IGlzIF9ub3RfIGNhbGxlZFxuICogaW4gdGhlIGNvbW1vbiBjYXNlIHRoYXQgdGhlIHVzZXIgaGFzIHpvb21lZCBvciBpcyBwYW5uaW5nIHRoZSB2aWV3LlxuICpcbiAqIFRoZSBjaGFydCBjYW52YXMgaGFzIGFscmVhZHkgYmVlbiBjcmVhdGVkIGJ5IHRoZSBEeWdyYXBoIG9iamVjdC4gVGhlXG4gKiByZW5kZXJlciBzaW1wbHkgZ2V0cyBhIGRyYXdpbmcgY29udGV4dC5cbiAqXG4gKiBAcGFyYW0ge0R5Z3JhcGh9IGR5Z3JhcGggVGhlIGNoYXJ0IHRvIHdoaWNoIHRoaXMgcmVuZGVyZXIgYmVsb25ncy5cbiAqIEBwYXJhbSB7SFRNTENhbnZhc0VsZW1lbnR9IGVsZW1lbnQgVGhlICZsdDtjYW52YXMmZ3Q7IERPTSBlbGVtZW50IG9uIHdoaWNoIHRvIGRyYXcuXG4gKiBAcGFyYW0ge0NhbnZhc1JlbmRlcmluZ0NvbnRleHQyRH0gZWxlbWVudENvbnRleHQgVGhlIGRyYXdpbmcgY29udGV4dC5cbiAqIEBwYXJhbSB7RHlncmFwaExheW91dH0gbGF5b3V0IFRoZSBjaGFydCdzIER5Z3JhcGhMYXlvdXQgb2JqZWN0LlxuICpcbiAqIFRPRE8oZGFudmspOiByZW1vdmUgdGhlIGVsZW1lbnRDb250ZXh0IHByb3BlcnR5LlxuICovXG52YXIgRHlncmFwaENhbnZhc1JlbmRlcmVyID0gZnVuY3Rpb24oZHlncmFwaCwgZWxlbWVudCwgZWxlbWVudENvbnRleHQsIGxheW91dCkge1xuICB0aGlzLmR5Z3JhcGhfID0gZHlncmFwaDtcblxuICB0aGlzLmxheW91dCA9IGxheW91dDtcbiAgdGhpcy5lbGVtZW50ID0gZWxlbWVudDtcbiAgdGhpcy5lbGVtZW50Q29udGV4dCA9IGVsZW1lbnRDb250ZXh0O1xuXG4gIHRoaXMuaGVpZ2h0ID0gZHlncmFwaC5oZWlnaHRfO1xuICB0aGlzLndpZHRoID0gZHlncmFwaC53aWR0aF87XG5cbiAgLy8gLS0tIGNoZWNrIHdoZXRoZXIgZXZlcnl0aGluZyBpcyBvayBiZWZvcmUgd2UgcmV0dXJuXG4gIGlmICghdXRpbHMuaXNDYW52YXNTdXBwb3J0ZWQodGhpcy5lbGVtZW50KSkge1xuICAgIHRocm93IFwiQ2FudmFzIGlzIG5vdCBzdXBwb3J0ZWQuXCI7XG4gIH1cblxuICAvLyBpbnRlcm5hbCBzdGF0ZVxuICB0aGlzLmFyZWEgPSBsYXlvdXQuZ2V0UGxvdEFyZWEoKTtcblxuICAvLyBTZXQgdXAgYSBjbGlwcGluZyBhcmVhIGZvciB0aGUgY2FudmFzIChhbmQgdGhlIGludGVyYWN0aW9uIGNhbnZhcykuXG4gIC8vIFRoaXMgZW5zdXJlcyB0aGF0IHdlIGRvbid0IG92ZXJkcmF3LlxuICB2YXIgY3R4ID0gdGhpcy5keWdyYXBoXy5jYW52YXNfY3R4XztcbiAgY3R4LmJlZ2luUGF0aCgpO1xuICBjdHgucmVjdCh0aGlzLmFyZWEueCwgdGhpcy5hcmVhLnksIHRoaXMuYXJlYS53LCB0aGlzLmFyZWEuaCk7XG4gIGN0eC5jbGlwKCk7XG5cbiAgY3R4ID0gdGhpcy5keWdyYXBoXy5oaWRkZW5fY3R4XztcbiAgY3R4LmJlZ2luUGF0aCgpO1xuICBjdHgucmVjdCh0aGlzLmFyZWEueCwgdGhpcy5hcmVhLnksIHRoaXMuYXJlYS53LCB0aGlzLmFyZWEuaCk7XG4gIGN0eC5jbGlwKCk7XG59O1xuXG4vKipcbiAqIENsZWFycyBvdXQgYWxsIGNoYXJ0IGNvbnRlbnQgYW5kIERPTSBlbGVtZW50cy5cbiAqIFRoaXMgaXMgY2FsbGVkIGltbWVkaWF0ZWx5IGJlZm9yZSByZW5kZXIoKSBvbiBldmVyeSBmcmFtZSwgaW5jbHVkaW5nXG4gKiBkdXJpbmcgem9vbXMgYW5kIHBhbnMuXG4gKiBAcHJpdmF0ZVxuICovXG5EeWdyYXBoQ2FudmFzUmVuZGVyZXIucHJvdG90eXBlLmNsZWFyID0gZnVuY3Rpb24oKSB7XG4gIHRoaXMuZWxlbWVudENvbnRleHQuY2xlYXJSZWN0KDAsIDAsIHRoaXMud2lkdGgsIHRoaXMuaGVpZ2h0KTtcbn07XG5cbi8qKlxuICogVGhpcyBtZXRob2QgaXMgcmVzcG9uc2libGUgZm9yIGRyYXdpbmcgZXZlcnl0aGluZyBvbiB0aGUgY2hhcnQsIGluY2x1ZGluZ1xuICogbGluZXMsIGhpZ2gvbG93IGJhbmRzLCBmaWxscyBhbmQgYXhlcy5cbiAqIEl0IGlzIGNhbGxlZCBpbW1lZGlhdGVseSBhZnRlciBjbGVhcigpIG9uIGV2ZXJ5IGZyYW1lLCBpbmNsdWRpbmcgZHVyaW5nIHBhbnNcbiAqIGFuZCB6b29tcy5cbiAqIEBwcml2YXRlXG4gKi9cbkR5Z3JhcGhDYW52YXNSZW5kZXJlci5wcm90b3R5cGUucmVuZGVyID0gZnVuY3Rpb24oKSB7XG4gIC8vIGF0dGFjaGVzIHBvaW50LmNhbnZhc3t4LHl9XG4gIHRoaXMuX3VwZGF0ZVBvaW50cygpO1xuXG4gIC8vIGFjdHVhbGx5IGRyYXdzIHRoZSBjaGFydC5cbiAgdGhpcy5fcmVuZGVyTGluZUNoYXJ0KCk7XG59O1xuXG4vKipcbiAqIFJldHVybnMgYSBwcmVkaWNhdGUgdG8gYmUgdXNlZCB3aXRoIGFuIGl0ZXJhdG9yLCB3aGljaCB3aWxsXG4gKiBpdGVyYXRlIG92ZXIgcG9pbnRzIGFwcHJvcHJpYXRlbHksIGRlcGVuZGluZyBvbiB3aGV0aGVyXG4gKiBjb25uZWN0U2VwYXJhdGVkUG9pbnRzIGlzIHRydWUuIFdoZW4gaXQncyBmYWxzZSwgdGhlIHByZWRpY2F0ZSB3aWxsXG4gKiBza2lwIG92ZXIgcG9pbnRzIHdpdGggbWlzc2luZyB5VmFscy5cbiAqL1xuRHlncmFwaENhbnZhc1JlbmRlcmVyLl9nZXRJdGVyYXRvclByZWRpY2F0ZSA9IGZ1bmN0aW9uKGNvbm5lY3RTZXBhcmF0ZWRQb2ludHMpIHtcbiAgcmV0dXJuIGNvbm5lY3RTZXBhcmF0ZWRQb2ludHMgP1xuICAgICAgRHlncmFwaENhbnZhc1JlbmRlcmVyLl9wcmVkaWNhdGVUaGF0U2tpcHNFbXB0eVBvaW50cyA6XG4gICAgICBudWxsO1xufTtcblxuRHlncmFwaENhbnZhc1JlbmRlcmVyLl9wcmVkaWNhdGVUaGF0U2tpcHNFbXB0eVBvaW50cyA9XG4gICAgZnVuY3Rpb24oYXJyYXksIGlkeCkge1xuICByZXR1cm4gYXJyYXlbaWR4XS55dmFsICE9PSBudWxsO1xufTtcblxuLyoqXG4gKiBEcmF3cyBhIGxpbmUgd2l0aCB0aGUgc3R5bGVzIHBhc3NlZCBpbiBhbmQgY2FsbHMgYWxsIHRoZSBkcmF3UG9pbnRDYWxsYmFja3MuXG4gKiBAcGFyYW0ge09iamVjdH0gZSBUaGUgZGljdGlvbmFyeSBwYXNzZWQgdG8gdGhlIHBsb3R0ZXIgZnVuY3Rpb24uXG4gKiBAcHJpdmF0ZVxuICovXG5EeWdyYXBoQ2FudmFzUmVuZGVyZXIuX2RyYXdTdHlsZWRMaW5lID0gZnVuY3Rpb24oZSxcbiAgICBjb2xvciwgc3Ryb2tlV2lkdGgsIHN0cm9rZVBhdHRlcm4sIGRyYXdQb2ludHMsXG4gICAgZHJhd1BvaW50Q2FsbGJhY2ssIHBvaW50U2l6ZSkge1xuICB2YXIgZyA9IGUuZHlncmFwaDtcbiAgLy8gVE9ETyhrb25pZ3NiZXJnKTogQ29tcHV0ZSBhdHRyaWJ1dGVzIG91dHNpZGUgdGhpcyBtZXRob2QgY2FsbC5cbiAgdmFyIHN0ZXBQbG90ID0gZy5nZXRCb29sZWFuT3B0aW9uKFwic3RlcFBsb3RcIiwgZS5zZXROYW1lKTtcblxuICBpZiAoIXV0aWxzLmlzQXJyYXlMaWtlKHN0cm9rZVBhdHRlcm4pKSB7XG4gICAgc3Ryb2tlUGF0dGVybiA9IG51bGw7XG4gIH1cblxuICB2YXIgZHJhd0dhcFBvaW50cyA9IGcuZ2V0Qm9vbGVhbk9wdGlvbignZHJhd0dhcEVkZ2VQb2ludHMnLCBlLnNldE5hbWUpO1xuXG4gIHZhciBwb2ludHMgPSBlLnBvaW50cztcbiAgdmFyIHNldE5hbWUgPSBlLnNldE5hbWU7XG4gIHZhciBpdGVyID0gdXRpbHMuY3JlYXRlSXRlcmF0b3IocG9pbnRzLCAwLCBwb2ludHMubGVuZ3RoLFxuICAgICAgRHlncmFwaENhbnZhc1JlbmRlcmVyLl9nZXRJdGVyYXRvclByZWRpY2F0ZShcbiAgICAgICAgICBnLmdldEJvb2xlYW5PcHRpb24oXCJjb25uZWN0U2VwYXJhdGVkUG9pbnRzXCIsIHNldE5hbWUpKSk7XG5cbiAgdmFyIHN0cm9raW5nID0gc3Ryb2tlUGF0dGVybiAmJiAoc3Ryb2tlUGF0dGVybi5sZW5ndGggPj0gMik7XG5cbiAgdmFyIGN0eCA9IGUuZHJhd2luZ0NvbnRleHQ7XG4gIGN0eC5zYXZlKCk7XG4gIGlmIChzdHJva2luZykge1xuICAgIGlmIChjdHguc2V0TGluZURhc2gpIGN0eC5zZXRMaW5lRGFzaChzdHJva2VQYXR0ZXJuKTtcbiAgfVxuXG4gIHZhciBwb2ludHNPbkxpbmUgPSBEeWdyYXBoQ2FudmFzUmVuZGVyZXIuX2RyYXdTZXJpZXMoXG4gICAgICBlLCBpdGVyLCBzdHJva2VXaWR0aCwgcG9pbnRTaXplLCBkcmF3UG9pbnRzLCBkcmF3R2FwUG9pbnRzLCBzdGVwUGxvdCwgY29sb3IpO1xuICBEeWdyYXBoQ2FudmFzUmVuZGVyZXIuX2RyYXdQb2ludHNPbkxpbmUoXG4gICAgICBlLCBwb2ludHNPbkxpbmUsIGRyYXdQb2ludENhbGxiYWNrLCBjb2xvciwgcG9pbnRTaXplKTtcblxuICBpZiAoc3Ryb2tpbmcpIHtcbiAgICBpZiAoY3R4LnNldExpbmVEYXNoKSBjdHguc2V0TGluZURhc2goW10pO1xuICB9XG5cbiAgY3R4LnJlc3RvcmUoKTtcbn07XG5cbi8qKlxuICogVGhpcyBkb2VzIHRoZSBhY3R1YWwgZHJhd2luZyBvZiBsaW5lcyBvbiB0aGUgY2FudmFzLCBmb3IganVzdCBvbmUgc2VyaWVzLlxuICogUmV0dXJucyBhIGxpc3Qgb2YgW2NhbnZhc3gsIGNhbnZhc3ldIHBhaXJzIGZvciBwb2ludHMgZm9yIHdoaWNoIGFcbiAqIGRyYXdQb2ludENhbGxiYWNrIHNob3VsZCBiZSBmaXJlZC4gIFRoZXNlIGluY2x1ZGUgaXNvbGF0ZWQgcG9pbnRzLCBvciBhbGxcbiAqIHBvaW50cyBpZiBkcmF3UG9pbnRzPXRydWUuXG4gKiBAcGFyYW0ge09iamVjdH0gZSBUaGUgZGljdGlvbmFyeSBwYXNzZWQgdG8gdGhlIHBsb3R0ZXIgZnVuY3Rpb24uXG4gKiBAcHJpdmF0ZVxuICovXG5EeWdyYXBoQ2FudmFzUmVuZGVyZXIuX2RyYXdTZXJpZXMgPSBmdW5jdGlvbihlLFxuICAgIGl0ZXIsIHN0cm9rZVdpZHRoLCBwb2ludFNpemUsIGRyYXdQb2ludHMsIGRyYXdHYXBQb2ludHMsIHN0ZXBQbG90LCBjb2xvcikge1xuXG4gIHZhciBwcmV2Q2FudmFzWCA9IG51bGw7XG4gIHZhciBwcmV2Q2FudmFzWSA9IG51bGw7XG4gIHZhciBuZXh0Q2FudmFzWSA9IG51bGw7XG4gIHZhciBpc0lzb2xhdGVkOyAvLyB0cnVlIGlmIHRoaXMgcG9pbnQgaXMgaXNvbGF0ZWQgKG5vIGxpbmUgc2VnbWVudHMpXG4gIHZhciBwb2ludDsgLy8gdGhlIHBvaW50IGJlaW5nIHByb2Nlc3NlZCBpbiB0aGUgd2hpbGUgbG9vcFxuICB2YXIgcG9pbnRzT25MaW5lID0gW107IC8vIEFycmF5IG9mIFtjYW52YXN4LCBjYW52YXN5XSBwYWlycy5cbiAgdmFyIGZpcnN0ID0gdHJ1ZTsgLy8gdGhlIGZpcnN0IGN5Y2xlIHRocm91Z2ggdGhlIHdoaWxlIGxvb3BcblxuICB2YXIgY3R4ID0gZS5kcmF3aW5nQ29udGV4dDtcbiAgY3R4LmJlZ2luUGF0aCgpO1xuICBjdHguc3Ryb2tlU3R5bGUgPSBjb2xvcjtcbiAgY3R4LmxpbmVXaWR0aCA9IHN0cm9rZVdpZHRoO1xuXG4gIC8vIE5PVEU6IHdlIGJyZWFrIHRoZSBpdGVyYXRvcidzIGVuY2Fwc3VsYXRpb24gaGVyZSBmb3IgYWJvdXQgYSAyNSUgc3BlZWR1cC5cbiAgdmFyIGFyciA9IGl0ZXIuYXJyYXlfO1xuICB2YXIgbGltaXQgPSBpdGVyLmVuZF87XG4gIHZhciBwcmVkaWNhdGUgPSBpdGVyLnByZWRpY2F0ZV87XG5cbiAgZm9yICh2YXIgaSA9IGl0ZXIuc3RhcnRfOyBpIDwgbGltaXQ7IGkrKykge1xuICAgIHBvaW50ID0gYXJyW2ldO1xuICAgIGlmIChwcmVkaWNhdGUpIHtcbiAgICAgIHdoaWxlIChpIDwgbGltaXQgJiYgIXByZWRpY2F0ZShhcnIsIGkpKSB7XG4gICAgICAgIGkrKztcbiAgICAgIH1cbiAgICAgIGlmIChpID09IGxpbWl0KSBicmVhaztcbiAgICAgIHBvaW50ID0gYXJyW2ldO1xuICAgIH1cblxuICAgIC8vIEZJWE1FOiBUaGUgJ2NhbnZhc3kgIT0gY2FudmFzeScgdGVzdCBoZXJlIGNhdGNoZXMgTmFOIHZhbHVlcyBidXQgdGhlIHRlc3RcbiAgICAvLyBkb2Vzbid0IGNhdGNoIEluZmluaXR5IHZhbHVlcy4gQ291bGQgY2hhbmdlIHRoaXMgdG9cbiAgICAvLyAhaXNGaW5pdGUocG9pbnQuY2FudmFzeSksIGJ1dCBJIGFzc3VtZSBpdCBhdm9pZHMgaXNOYU4gZm9yIHBlcmZvcm1hbmNlP1xuICAgIGlmIChwb2ludC5jYW52YXN5ID09PSBudWxsIHx8IHBvaW50LmNhbnZhc3kgIT0gcG9pbnQuY2FudmFzeSkge1xuICAgICAgaWYgKHN0ZXBQbG90ICYmIHByZXZDYW52YXNYICE9PSBudWxsKSB7XG4gICAgICAgIC8vIERyYXcgYSBob3Jpem9udGFsIGxpbmUgdG8gdGhlIHN0YXJ0IG9mIHRoZSBtaXNzaW5nIGRhdGFcbiAgICAgICAgY3R4Lm1vdmVUbyhwcmV2Q2FudmFzWCwgcHJldkNhbnZhc1kpO1xuICAgICAgICBjdHgubGluZVRvKHBvaW50LmNhbnZhc3gsIHByZXZDYW52YXNZKTtcbiAgICAgIH1cbiAgICAgIHByZXZDYW52YXNYID0gcHJldkNhbnZhc1kgPSBudWxsO1xuICAgIH0gZWxzZSB7XG4gICAgICBpc0lzb2xhdGVkID0gZmFsc2U7XG4gICAgICBpZiAoZHJhd0dhcFBvaW50cyB8fCBwcmV2Q2FudmFzWCA9PT0gbnVsbCkge1xuICAgICAgICBpdGVyLm5leHRJZHhfID0gaTtcbiAgICAgICAgaXRlci5uZXh0KCk7XG4gICAgICAgIG5leHRDYW52YXNZID0gaXRlci5oYXNOZXh0ID8gaXRlci5wZWVrLmNhbnZhc3kgOiBudWxsO1xuXG4gICAgICAgIHZhciBpc05leHRDYW52YXNZTnVsbE9yTmFOID0gbmV4dENhbnZhc1kgPT09IG51bGwgfHxcbiAgICAgICAgICAgIG5leHRDYW52YXNZICE9IG5leHRDYW52YXNZO1xuICAgICAgICBpc0lzb2xhdGVkID0gKHByZXZDYW52YXNYID09PSBudWxsICYmIGlzTmV4dENhbnZhc1lOdWxsT3JOYU4pO1xuICAgICAgICBpZiAoZHJhd0dhcFBvaW50cykge1xuICAgICAgICAgIC8vIEFsc28gY29uc2lkZXIgYSBwb2ludCB0byBiZSBcImlzb2xhdGVkXCIgaWYgaXQncyBhZGphY2VudCB0byBhXG4gICAgICAgICAgLy8gbnVsbCBwb2ludCwgZXhjbHVkaW5nIHRoZSBncmFwaCBlZGdlcy5cbiAgICAgICAgICBpZiAoKCFmaXJzdCAmJiBwcmV2Q2FudmFzWCA9PT0gbnVsbCkgfHxcbiAgICAgICAgICAgICAgKGl0ZXIuaGFzTmV4dCAmJiBpc05leHRDYW52YXNZTnVsbE9yTmFOKSkge1xuICAgICAgICAgICAgaXNJc29sYXRlZCA9IHRydWU7XG4gICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICB9XG5cbiAgICAgIGlmIChwcmV2Q2FudmFzWCAhPT0gbnVsbCkge1xuICAgICAgICBpZiAoc3Ryb2tlV2lkdGgpIHtcbiAgICAgICAgICBpZiAoc3RlcFBsb3QpIHtcbiAgICAgICAgICAgIGN0eC5tb3ZlVG8ocHJldkNhbnZhc1gsIHByZXZDYW52YXNZKTtcbiAgICAgICAgICAgIGN0eC5saW5lVG8ocG9pbnQuY2FudmFzeCwgcHJldkNhbnZhc1kpO1xuICAgICAgICAgIH1cblxuICAgICAgICAgIGN0eC5saW5lVG8ocG9pbnQuY2FudmFzeCwgcG9pbnQuY2FudmFzeSk7XG4gICAgICAgIH1cbiAgICAgIH0gZWxzZSB7XG4gICAgICAgIGN0eC5tb3ZlVG8ocG9pbnQuY2FudmFzeCwgcG9pbnQuY2FudmFzeSk7XG4gICAgICB9XG4gICAgICBpZiAoZHJhd1BvaW50cyB8fCBpc0lzb2xhdGVkKSB7XG4gICAgICAgIHBvaW50c09uTGluZS5wdXNoKFtwb2ludC5jYW52YXN4LCBwb2ludC5jYW52YXN5LCBwb2ludC5pZHhdKTtcbiAgICAgIH1cbiAgICAgIHByZXZDYW52YXNYID0gcG9pbnQuY2FudmFzeDtcbiAgICAgIHByZXZDYW52YXNZID0gcG9pbnQuY2FudmFzeTtcbiAgICB9XG4gICAgZmlyc3QgPSBmYWxzZTtcbiAgfVxuICBjdHguc3Ryb2tlKCk7XG4gIHJldHVybiBwb2ludHNPbkxpbmU7XG59O1xuXG4vKipcbiAqIFRoaXMgZmlyZXMgdGhlIGRyYXdQb2ludENhbGxiYWNrIGZ1bmN0aW9ucywgd2hpY2ggZHJhdyBkb3RzIG9uIHRoZSBwb2ludHMgYnlcbiAqIGRlZmF1bHQuIFRoaXMgZ2V0cyB1c2VkIHdoZW4gdGhlIFwiZHJhd1BvaW50c1wiIG9wdGlvbiBpcyBzZXQsIG9yIHdoZW4gdGhlcmVcbiAqIGFyZSBpc29sYXRlZCBwb2ludHMuXG4gKiBAcGFyYW0ge09iamVjdH0gZSBUaGUgZGljdGlvbmFyeSBwYXNzZWQgdG8gdGhlIHBsb3R0ZXIgZnVuY3Rpb24uXG4gKiBAcHJpdmF0ZVxuICovXG5EeWdyYXBoQ2FudmFzUmVuZGVyZXIuX2RyYXdQb2ludHNPbkxpbmUgPSBmdW5jdGlvbihcbiAgICBlLCBwb2ludHNPbkxpbmUsIGRyYXdQb2ludENhbGxiYWNrLCBjb2xvciwgcG9pbnRTaXplKSB7XG4gIHZhciBjdHggPSBlLmRyYXdpbmdDb250ZXh0O1xuICBmb3IgKHZhciBpZHggPSAwOyBpZHggPCBwb2ludHNPbkxpbmUubGVuZ3RoOyBpZHgrKykge1xuICAgIHZhciBjYiA9IHBvaW50c09uTGluZVtpZHhdO1xuICAgIGN0eC5zYXZlKCk7XG4gICAgZHJhd1BvaW50Q2FsbGJhY2suY2FsbChlLmR5Z3JhcGgsXG4gICAgICAgIGUuZHlncmFwaCwgZS5zZXROYW1lLCBjdHgsIGNiWzBdLCBjYlsxXSwgY29sb3IsIHBvaW50U2l6ZSwgY2JbMl0pO1xuICAgIGN0eC5yZXN0b3JlKCk7XG4gIH1cbn07XG5cbi8qKlxuICogQXR0YWNoZXMgY2FudmFzIGNvb3JkaW5hdGVzIHRvIHRoZSBwb2ludHMgYXJyYXkuXG4gKiBAcHJpdmF0ZVxuICovXG5EeWdyYXBoQ2FudmFzUmVuZGVyZXIucHJvdG90eXBlLl91cGRhdGVQb2ludHMgPSBmdW5jdGlvbigpIHtcbiAgLy8gVXBkYXRlIFBvaW50c1xuICAvLyBUT0RPKGRhbnZrKTogaGVyZVxuICAvL1xuICAvLyBUT0RPKGJocyk6IHRoaXMgbG9vcCBpcyBhIGhvdC1zcG90IGZvciBoaWdoLXBvaW50LWNvdW50IGNoYXJ0cy4gVGhlc2VcbiAgLy8gdHJhbnNmb3JtYXRpb25zIGNhbiBiZSBwdXNoZWQgaW50byB0aGUgY2FudmFzIHZpYSBsaW5lYXIgdHJhbnNmb3JtYXRpb25cbiAgLy8gbWF0cmljZXMuXG4gIC8vIE5PVEUoZGFudmspOiB0aGlzIGlzIHRyaWNraWVyIHRoYW4gaXQgc291bmRzIGF0IGZpcnN0LiBUaGUgdHJhbnNmb3JtYXRpb25cbiAgLy8gbmVlZHMgdG8gYmUgZG9uZSBiZWZvcmUgdGhlIC5tb3ZlVG8oKSBhbmQgLmxpbmVUbygpIGNhbGxzLCBidXQgbXVzdCBiZVxuICAvLyB1bmRvbmUgYmVmb3JlIHRoZSAuc3Ryb2tlKCkgY2FsbCB0byBlbnN1cmUgdGhhdCB0aGUgc3Ryb2tlIHdpZHRoIGlzXG4gIC8vIHVuYWZmZWN0ZWQuICBBbiBhbHRlcm5hdGl2ZSBpcyB0byByZWR1Y2UgdGhlIHN0cm9rZSB3aWR0aCBpbiB0aGVcbiAgLy8gdHJhbnNmb3JtZWQgY29vcmRpbmF0ZSBzcGFjZSwgYnV0IHlvdSBjYW4ndCBzcGVjaWZ5IGRpZmZlcmVudCB2YWx1ZXMgZm9yXG4gIC8vIGVhY2ggZGltZW5zaW9uIChhcyB5b3UgY2FuIHdpdGggLnNjYWxlKCkpLiBUaGUgc3BlZWR1cCBoZXJlIGlzIH4xMiUuXG4gIHZhciBzZXRzID0gdGhpcy5sYXlvdXQucG9pbnRzO1xuICBmb3IgKHZhciBpID0gc2V0cy5sZW5ndGg7IGktLTspIHtcbiAgICB2YXIgcG9pbnRzID0gc2V0c1tpXTtcbiAgICBmb3IgKHZhciBqID0gcG9pbnRzLmxlbmd0aDsgai0tOykge1xuICAgICAgdmFyIHBvaW50ID0gcG9pbnRzW2pdO1xuICAgICAgcG9pbnQuY2FudmFzeCA9IHRoaXMuYXJlYS53ICogcG9pbnQueCArIHRoaXMuYXJlYS54O1xuICAgICAgcG9pbnQuY2FudmFzeSA9IHRoaXMuYXJlYS5oICogcG9pbnQueSArIHRoaXMuYXJlYS55O1xuICAgIH1cbiAgfVxufTtcblxuLyoqXG4gKiBBZGQgY2FudmFzIEFjdHVhbGx5IGRyYXcgdGhlIGxpbmVzIGNoYXJ0LCBpbmNsdWRpbmcgaGlnaC9sb3cgYmFuZHMuXG4gKlxuICogVGhpcyBmdW5jdGlvbiBjYW4gb25seSBiZSBjYWxsZWQgaWYgRHlncmFwaExheW91dCdzIHBvaW50cyBhcnJheSBoYXMgYmVlblxuICogdXBkYXRlZCB3aXRoIGNhbnZhc3t4LHl9IGF0dHJpYnV0ZXMsIGkuZS4gYnlcbiAqIER5Z3JhcGhDYW52YXNSZW5kZXJlci5fdXBkYXRlUG9pbnRzLlxuICpcbiAqIEBwYXJhbSB7c3RyaW5nPX0gb3B0X3Nlcmllc05hbWUgd2hlbiBzcGVjaWZpZWQsIG9ubHkgdGhhdCBzZXJpZXMgd2lsbFxuICogICAgIGJlIGRyYXduLiAoVGhpcyBpcyB1c2VkIGZvciBleHBlZGl0ZWQgcmVkcmF3aW5nIHdpdGggaGlnaGxpZ2h0U2VyaWVzT3B0cylcbiAqIEBwYXJhbSB7Q2FudmFzUmVuZGVyaW5nQ29udGV4dDJEfSBvcHRfY3R4IHdoZW4gc3BlY2lmaWVkLCB0aGUgZHJhd2luZ1xuICogICAgIGNvbnRleHQuICBIb3dldmVyLCBsaW5lcyBhcmUgdHlwaWNhbGx5IGRyYXduIG9uIHRoZSBvYmplY3Qnc1xuICogICAgIGVsZW1lbnRDb250ZXh0LlxuICogQHByaXZhdGVcbiAqL1xuRHlncmFwaENhbnZhc1JlbmRlcmVyLnByb3RvdHlwZS5fcmVuZGVyTGluZUNoYXJ0ID0gZnVuY3Rpb24ob3B0X3Nlcmllc05hbWUsIG9wdF9jdHgpIHtcbiAgdmFyIGN0eCA9IG9wdF9jdHggfHwgdGhpcy5lbGVtZW50Q29udGV4dDtcbiAgdmFyIGk7XG5cbiAgdmFyIHNldHMgPSB0aGlzLmxheW91dC5wb2ludHM7XG4gIHZhciBzZXROYW1lcyA9IHRoaXMubGF5b3V0LnNldE5hbWVzO1xuICB2YXIgc2V0TmFtZTtcblxuICB0aGlzLmNvbG9ycyA9IHRoaXMuZHlncmFwaF8uY29sb3JzTWFwXztcblxuICAvLyBEZXRlcm1pbmUgd2hpY2ggc2VyaWVzIGhhdmUgc3BlY2lhbGl6ZWQgcGxvdHRlcnMuXG4gIHZhciBwbG90dGVyX2F0dHIgPSB0aGlzLmR5Z3JhcGhfLmdldE9wdGlvbihcInBsb3R0ZXJcIik7XG4gIHZhciBwbG90dGVycyA9IHBsb3R0ZXJfYXR0cjtcbiAgaWYgKCF1dGlscy5pc0FycmF5TGlrZShwbG90dGVycykpIHtcbiAgICBwbG90dGVycyA9IFtwbG90dGVyc107XG4gIH1cblxuICB2YXIgc2V0UGxvdHRlcnMgPSB7fTsgIC8vIHNlcmllcyBuYW1lIC0+IHBsb3R0ZXIgZm4uXG4gIGZvciAoaSA9IDA7IGkgPCBzZXROYW1lcy5sZW5ndGg7IGkrKykge1xuICAgIHNldE5hbWUgPSBzZXROYW1lc1tpXTtcbiAgICB2YXIgc2V0UGxvdHRlciA9IHRoaXMuZHlncmFwaF8uZ2V0T3B0aW9uKFwicGxvdHRlclwiLCBzZXROYW1lKTtcbiAgICBpZiAoc2V0UGxvdHRlciA9PSBwbG90dGVyX2F0dHIpIGNvbnRpbnVlOyAgLy8gbm90IHNwZWNpYWxpemVkLlxuXG4gICAgc2V0UGxvdHRlcnNbc2V0TmFtZV0gPSBzZXRQbG90dGVyO1xuICB9XG5cbiAgZm9yIChpID0gMDsgaSA8IHBsb3R0ZXJzLmxlbmd0aDsgaSsrKSB7XG4gICAgdmFyIHBsb3R0ZXIgPSBwbG90dGVyc1tpXTtcbiAgICB2YXIgaXNfbGFzdCA9IChpID09IHBsb3R0ZXJzLmxlbmd0aCAtIDEpO1xuXG4gICAgZm9yICh2YXIgaiA9IDA7IGogPCBzZXRzLmxlbmd0aDsgaisrKSB7XG4gICAgICBzZXROYW1lID0gc2V0TmFtZXNbal07XG4gICAgICBpZiAob3B0X3Nlcmllc05hbWUgJiYgc2V0TmFtZSAhPSBvcHRfc2VyaWVzTmFtZSkgY29udGludWU7XG5cbiAgICAgIHZhciBwb2ludHMgPSBzZXRzW2pdO1xuXG4gICAgICAvLyBPbmx5IHRocm93IGluIHRoZSBzcGVjaWFsaXplZCBwbG90dGVycyBvbiB0aGUgbGFzdCBpdGVyYXRpb24uXG4gICAgICB2YXIgcCA9IHBsb3R0ZXI7XG4gICAgICBpZiAoc2V0TmFtZSBpbiBzZXRQbG90dGVycykge1xuICAgICAgICBpZiAoaXNfbGFzdCkge1xuICAgICAgICAgIHAgPSBzZXRQbG90dGVyc1tzZXROYW1lXTtcbiAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAvLyBEb24ndCB1c2UgdGhlIHN0YW5kYXJkIHBsb3R0ZXJzIGluIHRoaXMgY2FzZS5cbiAgICAgICAgICBjb250aW51ZTtcbiAgICAgICAgfVxuICAgICAgfVxuXG4gICAgICB2YXIgY29sb3IgPSB0aGlzLmNvbG9yc1tzZXROYW1lXTtcbiAgICAgIHZhciBzdHJva2VXaWR0aCA9IHRoaXMuZHlncmFwaF8uZ2V0T3B0aW9uKFwic3Ryb2tlV2lkdGhcIiwgc2V0TmFtZSk7XG5cbiAgICAgIGN0eC5zYXZlKCk7XG4gICAgICBjdHguc3Ryb2tlU3R5bGUgPSBjb2xvcjtcbiAgICAgIGN0eC5saW5lV2lkdGggPSBzdHJva2VXaWR0aDtcbiAgICAgIHAoe1xuICAgICAgICBwb2ludHM6IHBvaW50cyxcbiAgICAgICAgc2V0TmFtZTogc2V0TmFtZSxcbiAgICAgICAgZHJhd2luZ0NvbnRleHQ6IGN0eCxcbiAgICAgICAgY29sb3I6IGNvbG9yLFxuICAgICAgICBzdHJva2VXaWR0aDogc3Ryb2tlV2lkdGgsXG4gICAgICAgIGR5Z3JhcGg6IHRoaXMuZHlncmFwaF8sXG4gICAgICAgIGF4aXM6IHRoaXMuZHlncmFwaF8uYXhpc1Byb3BlcnRpZXNGb3JTZXJpZXMoc2V0TmFtZSksXG4gICAgICAgIHBsb3RBcmVhOiB0aGlzLmFyZWEsXG4gICAgICAgIHNlcmllc0luZGV4OiBqLFxuICAgICAgICBzZXJpZXNDb3VudDogc2V0cy5sZW5ndGgsXG4gICAgICAgIHNpbmdsZVNlcmllc05hbWU6IG9wdF9zZXJpZXNOYW1lLFxuICAgICAgICBhbGxTZXJpZXNQb2ludHM6IHNldHNcbiAgICAgIH0pO1xuICAgICAgY3R4LnJlc3RvcmUoKTtcbiAgICB9XG4gIH1cbn07XG5cbi8qKlxuICogU3RhbmRhcmQgcGxvdHRlcnMuIFRoZXNlIG1heSBiZSB1c2VkIGJ5IGNsaWVudHMgdmlhIER5Z3JhcGguUGxvdHRlcnMuXG4gKiBTZWUgY29tbWVudHMgdGhlcmUgZm9yIG1vcmUgZGV0YWlscy5cbiAqL1xuRHlncmFwaENhbnZhc1JlbmRlcmVyLl9QbG90dGVycyA9IHtcbiAgbGluZVBsb3R0ZXI6IGZ1bmN0aW9uKGUpIHtcbiAgICBEeWdyYXBoQ2FudmFzUmVuZGVyZXIuX2xpbmVQbG90dGVyKGUpO1xuICB