dygraphs
Version:
dygraphs is a fast, flexible open source JavaScript charting library.
794 lines (750 loc) • 104 kB
JavaScript
'use strict';
/**
* @license
* Copyright 2006 Dan Vanderkam (danvdk@gmail.com)
* MIT-licenced: https://opensource.org/license/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 */
Object.defineProperty(exports, "__esModule", {
value: true
});
exports["default"] = void 0;
var utils = _interopRequireWildcard(require("./dygraph-utils"));
var _dygraph = _interopRequireDefault(require("./dygraph"));
function _interopRequireDefault(e) { return e && e.__esModule ? e : { "default": e }; }
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function _interopRequireWildcard(e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, "default": e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (var _t in e) "default" !== _t && {}.hasOwnProperty.call(e, _t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, _t)) && (i.get || i.set) ? o(f, _t, i) : f[_t] = e[_t]); return f; })(e, t); }
/**
* @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 <canvas> 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 = exports["default"] = DygraphCanvasRenderer;
module.exports = exports.default;
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJPYmplY3QiLCJkZWZpbmVQcm9wZXJ0eSIsImV4cG9ydHMiLCJ2YWx1ZSIsInV0aWxzIiwiX2ludGVyb3BSZXF1aXJlV2lsZGNhcmQiLCJyZXF1aXJlIiwiX2R5Z3JhcGgiLCJfaW50ZXJvcFJlcXVpcmVEZWZhdWx0IiwiZSIsIl9fZXNNb2R1bGUiLCJ0IiwiV2Vha01hcCIsInIiLCJuIiwibyIsImkiLCJmIiwiX19wcm90b19fIiwiaGFzIiwiZ2V0Iiwic2V0IiwiX3QiLCJoYXNPd25Qcm9wZXJ0eSIsImNhbGwiLCJnZXRPd25Qcm9wZXJ0eURlc2NyaXB0b3IiLCJEeWdyYXBoQ2FudmFzUmVuZGVyZXIiLCJkeWdyYXBoIiwiZWxlbWVudCIsImVsZW1lbnRDb250ZXh0IiwibGF5b3V0IiwiZHlncmFwaF8iLCJoZWlnaHQiLCJoZWlnaHRfIiwid2lkdGgiLCJ3aWR0aF8iLCJpc0NhbnZhc1N1cHBvcnRlZCIsImFyZWEiLCJnZXRQbG90QXJlYSIsImN0eCIsImNhbnZhc19jdHhfIiwiYmVnaW5QYXRoIiwicmVjdCIsIngiLCJ5IiwidyIsImgiLCJjbGlwIiwiaGlkZGVuX2N0eF8iLCJwcm90b3R5cGUiLCJjbGVhciIsImNsZWFyUmVjdCIsInJlbmRlciIsIl91cGRhdGVQb2ludHMiLCJfcmVuZGVyTGluZUNoYXJ0IiwiX2dldEl0ZXJhdG9yUHJlZGljYXRlIiwiY29ubmVjdFNlcGFyYXRlZFBvaW50cyIsIl9wcmVkaWNhdGVUaGF0U2tpcHNFbXB0eVBvaW50cyIsImFycmF5IiwiaWR4IiwieXZhbCIsIl9kcmF3U3R5bGVkTGluZSIsImNvbG9yIiwic3Ryb2tlV2lkdGgiLCJzdHJva2VQYXR0ZXJuIiwiZHJhd1BvaW50cyIsImRyYXdQb2ludENhbGxiYWNrIiwicG9pbnRTaXplIiwiZyIsInN0ZXBQbG90IiwiZ2V0Qm9vbGVhbk9wdGlvbiIsInNldE5hbWUiLCJpc0FycmF5TGlrZSIsImRyYXdHYXBQb2ludHMiLCJwb2ludHMiLCJpdGVyIiwiY3JlYXRlSXRlcmF0b3IiLCJsZW5ndGgiLCJzdHJva2luZyIsImRyYXdpbmdDb250ZXh0Iiwic2F2ZSIsInNldExpbmVEYXNoIiwicG9pbnRzT25MaW5lIiwiX2RyYXdTZXJpZXMiLCJfZHJhd1BvaW50c09uTGluZSIsInJlc3RvcmUiLCJwcmV2Q2FudmFzWCIsInByZXZDYW52YXNZIiwibmV4dENhbnZhc1kiLCJpc0lzb2xhdGVkIiwicG9pbnQiLCJmaXJzdCIsInN0cm9rZVN0eWxlIiwibGluZVdpZHRoIiwiYXJyIiwiYXJyYXlfIiwibGltaXQiLCJlbmRfIiwicHJlZGljYXRlIiwicHJlZGljYXRlXyIsInN0YXJ0XyIsImNhbnZhc3kiLCJtb3ZlVG8iLCJsaW5lVG8iLCJjYW52YXN4IiwibmV4dElkeF8iLCJuZXh0IiwiaGFzTmV4dCIsInBlZWsiLCJpc05leHRDYW52YXNZTnVsbE9yTmFOIiwicHVzaCIsInN0cm9rZSIsImNiIiwic2V0cyIsImoiLCJvcHRfc2VyaWVzTmFtZSIsIm9wdF9jdHgiLCJzZXROYW1lcyIsImNvbG9ycyIsImNvbG9yc01hcF8iLCJwbG90dGVyX2F0dHIiLCJnZXRPcHRpb24iLCJwbG90dGVycyIsInNldFBsb3R0ZXJzIiwic2V0UGxvdHRlciIsInBsb3R0ZXIiLCJpc19sYXN0IiwicCIsImF4aXMiLCJheGlzUHJvcGVydGllc0ZvclNlcmllcyIsInBsb3RBcmVhIiwic2VyaWVzSW5kZXgiLCJzZXJpZXNDb3VudCIsInNpbmdsZVNlcmllc05hbWUiLCJhbGxTZXJpZXNQb2ludHMiLCJfUGxvdHRlcnMiLCJsaW5lUGxvdHRlciIsIl9saW5lUGxvdHRlciIsImZpbGxQbG90dGVyIiwiX2ZpbGxQbG90dGVyIiwiZXJyb3JQbG90dGVyIiwiX2Vycm9yUGxvdHRlciIsImJvcmRlcldpZHRoIiwiZ2V0TnVtZXJpY09wdGlvbiIsIkNpcmNsZXMiLCJERUZBVUxUIiwiZXJyb3JCYXJzIiwiZmlsbEdyYXBoIiwiY29uc29sZSIsIndhcm4iLCJmaWxsQWxwaGEiLCJuZXdZcyIsInByZXZYIiwiTmFOIiwicHJldlkiLCJwcmV2WXMiLCJyZ2IiLCJ0b1JHQl8iLCJlcnJfY29sb3IiLCJiIiwiZmlsbFN0eWxlIiwiaXNOdWxsVW5kZWZpbmVkT3JOYU4iLCJ1bmRlZmluZWQiLCJpc05hTiIsInlfYm90dG9tIiwieV90b3AiLCJjbG9zZVBhdGgiLCJmaWxsIiwiX2Zhc3RDYW52YXNQcm94eSIsImNvbnRleHQiLCJwZW5kaW5nQWN0aW9ucyIsImxhc3RSb3VuZGVkWCIsImxhc3RGbHVzaGVkWCIsIkxJTkVfVE8iLCJNT1ZFX1RPIiwiYWN0aW9uQ291bnQiLCJjb21wcmVzc0FjdGlvbnMiLCJvcHRfbG9zc2xlc3NPbmx5IiwiYWN0aW9uIiwicHJldkFjdGlvbiIsInNwbGljZSIsInN0YXJ0SWR4IiwibWluSWR4IiwibWF4SWR4IiwibWluQWN0aW9uIiwibWF4QWN0aW9uIiwiZmx1c2hBY3Rpb25zIiwib3B0X25vTG9zc3lDb21wcmVzc2lvbiIsImxlbiIsImFkZEFjdGlvbiIsInJ4IiwiTWF0aCIsInJvdW5kIiwiaGFzR2FwT25MZWZ0IiwiaGFzR2FwT25SaWdodCIsImhhc0dhcCIsIl9jb3VudCIsImdldExhYmVscyIsInNsaWNlIiwidmlzaWJpbGl0eSIsImFueVNlcmllc0ZpbGxlZCIsInNldENvdW50Iiwic3RhY2tlZEdyYXBoIiwiZ2V0Q29sb3JzIiwiYmFzZWxpbmUiLCJjdXJyQmFzZWxpbmUiLCJwcmV2U3RlcFBsb3QiLCJ0cmFjZUJhY2tQYXRoIiwiYmFzZWxpbmVYIiwiYmFzZWxpbmVZIiwicGF0aEJhY2siLCJwdCIsInNldElkeCIsImF4aXNZIiwibWlueXZhbCIsInlzY2FsZSIsImxhc3RfeCIsImlzX2ZpcnN0IiwiRHlncmFwaCIsIkZPUkNFX0ZBU1RfUFJPWFkiLCJpc09LIiwieV9zdGFja2VkIiwieHZhbCIsImxhc3RZIiwiX2RlZmF1bHQiLCJtb2R1bGUiLCJkZWZhdWx0Il0sInNvdXJjZXMiOlsiLi4vc3JjL2R5Z3JhcGgtY2FudmFzLmpzIl0sInNvdXJjZXNDb250ZW50IjpbIid1c2Ugc3RyaWN0JztcblxuLyoqXG4gKiBAbGljZW5zZVxuICogQ29weXJpZ2h0IDIwMDYgRGFuIFZhbmRlcmthbSAoZGFudmRrQGdtYWlsLmNvbSlcbiAqIE1JVC1saWNlbmNlZDogaHR0cHM6Ly9vcGVuc291cmNlLm9yZy9saWNlbnNlL01JVFxuICovXG5cbi8qKlxuICogQGZpbGVvdmVydmlldyBCYXNlZCBvbiBQbG90S2l0LkNhbnZhc1JlbmRlcmVyLCBidXQgbW9kaWZpZWQgdG8gbWVldCB0aGVcbiAqIG5lZWRzIG9mIGR5Z3JhcGhzLlxuICpcbiAqIEluIHBhcnRpY3VsYXIsIHN1cHBvcnQgZm9yOlxuICogLSBncmlkIG92ZXJsYXlzXG4gKiAtIGhpZ2gvbG93IGJhbmRzXG4gKiAtIGR5Z3JhcGhzIGF0dHJpYnV0ZSBzeXN0ZW1cbiAqL1xuXG4vKipcbiAqIFRoZSBEeWdyYXBoQ2FudmFzUmVuZGVyZXIgY2xhc3MgZG9lcyB0aGUgYWN0dWFsIHJlbmRlcmluZyBvZiB0aGUgY2hhcnQgb250b1xuICogYSBjYW52YXMuIEl0J3MgYmFzZWQgb24gUGxvdEtpdC5DYW52YXNSZW5kZXJlci5cbiAqIEBwYXJhbSB7T2JqZWN0fSBlbGVtZW50IFRoZSBjYW52YXMgdG8gYXR0YWNoIHRvXG4gKiBAcGFyYW0ge09iamVjdH0gZWxlbWVudENvbnRleHQgVGhlIDJkIGNvbnRleHQgb2YgdGhlIGNhbnZhcyAoaW5qZWN0ZWQgc28gaXRcbiAqIGNhbiBiZSBtb2NrZWQgZm9yIHRlc3RpbmcuKVxuICogQHBhcmFtIHtMYXlvdXR9IGxheW91dCBUaGUgRHlncmFwaExheW91dCBvYmplY3QgZm9yIHRoaXMgZ3JhcGguXG4gKiBAY29uc3RydWN0b3JcbiAqL1xuXG4vKmdsb2JhbCBEeWdyYXBoOmZhbHNlICovXG5cbmltcG9ydCAqIGFzIHV0aWxzIGZyb20gJy4vZHlncmFwaC11dGlscyc7XG5pbXBvcnQgRHlncmFwaCBmcm9tICcuL2R5Z3JhcGgnO1xuXG4vKipcbiAqIEBjb25zdHJ1Y3RvclxuICpcbiAqIFRoaXMgZ2V0cyBjYWxsZWQgd2hlbiB0aGVyZSBhcmUgXCJuZXcgcG9pbnRzXCIgdG8gY2hhcnQuIFRoaXMgaXMgZ2VuZXJhbGx5IHRoZVxuICogY2FzZSB3aGVuIHRoZSB1bmRlcmx5aW5nIGRhdGEgYmVpbmcgY2hhcnRlZCBoYXMgY2hhbmdlZC4gSXQgaXMgX25vdF8gY2FsbGVkXG4gKiBpbiB0aGUgY29tbW9uIGNhc2UgdGhhdCB0aGUgdXNlciBoYXMgem9vbWVkIG9yIGlzIHBhbm5pbmcgdGhlIHZpZXcuXG4gKlxuICogVGhlIGNoYXJ0IGNhbnZhcyBoYXMgYWxyZWFkeSBiZWVuIGNyZWF0ZWQgYnkgdGhlIER5Z3JhcGggb2JqZWN0LiBUaGVcbiAqIHJlbmRlcmVyIHNpbXBseSBnZXRzIGEgZHJhd2luZyBjb250ZXh0LlxuICpcbiAqIEBwYXJhbSB7RHlncmFwaH0gZHlncmFwaCBUaGUgY2hhcnQgdG8gd2hpY2ggdGhpcyByZW5kZXJlciBiZWxvbmdzLlxuICogQHBhcmFtIHtIVE1MQ2FudmFzRWxlbWVudH0gZWxlbWVudCBUaGUgJmx0O2NhbnZhcyZndDsgRE9NIGVsZW1lbnQgb24gd2hpY2ggdG8gZHJhdy5cbiAqIEBwYXJhbSB7Q2FudmFzUmVuZGVyaW5nQ29udGV4dDJEfSBlbGVtZW50Q29udGV4dCBUaGUgZHJhd2luZyBjb250ZXh0LlxuICogQHBhcmFtIHtEeWdyYXBoTGF5b3V0fSBsYXlvdXQgVGhlIGNoYXJ0J3MgRHlncmFwaExheW91dCBvYmplY3QuXG4gKlxuICogVE9ETyhkYW52ayk6IHJlbW92ZSB0aGUgZWxlbWVudENvbnRleHQgcHJvcGVydHkuXG4gKi9cbnZhciBEeWdyYXBoQ2FudmFzUmVuZGVyZXIgPSBmdW5jdGlvbihkeWdyYXBoLCBlbGVtZW50LCBlbGVtZW50Q29udGV4dCwgbGF5b3V0KSB7XG4gIHRoaXMuZHlncmFwaF8gPSBkeWdyYXBoO1xuXG4gIHRoaXMubGF5b3V0ID0gbGF5b3V0O1xuICB0aGlzLmVsZW1lbnQgPSBlbGVtZW50O1xuICB0aGlzLmVsZW1lbnRDb250ZXh0ID0gZWxlbWVudENvbnRleHQ7XG5cbiAgdGhpcy5oZWlnaHQgPSBkeWdyYXBoLmhlaWdodF87XG4gIHRoaXMud2lkdGggPSBkeWdyYXBoLndpZHRoXztcblxuICAvLyAtLS0gY2hlY2sgd2hldGhlciBldmVyeXRoaW5nIGlzIG9rIGJlZm9yZSB3ZSByZXR1cm5cbiAgaWYgKCF1dGlscy5pc0NhbnZhc1N1cHBvcnRlZCh0aGlzLmVsZW1lbnQpKSB7XG4gICAgdGhyb3cgXCJDYW52YXMgaXMgbm90IHN1cHBvcnRlZC5cIjtcbiAgfVxuXG4gIC8vIGludGVybmFsIHN0YXRlXG4gIHRoaXMuYXJlYSA9IGxheW91dC5nZXRQbG90QXJlYSgpO1xuXG4gIC8vIFNldCB1cCBhIGNsaXBwaW5nIGFyZWEgZm9yIHRoZSBjYW52YXMgKGFuZCB0aGUgaW50ZXJhY3Rpb24gY2FudmFzKS5cbiAgLy8gVGhpcyBlbnN1cmVzIHRoYXQgd2UgZG9uJ3Qgb3ZlcmRyYXcuXG4gIHZhciBjdHggPSB0aGlzLmR5Z3JhcGhfLmNhbnZhc19jdHhfO1xuICBjdHguYmVnaW5QYXRoKCk7XG4gIGN0eC5yZWN0KHRoaXMuYXJlYS54LCB0aGlzLmFyZWEueSwgdGhpcy5hcmVhLncsIHRoaXMuYXJlYS5oKTtcbiAgY3R4LmNsaXAoKTtcblxuICBjdHggPSB0aGlzLmR5Z3JhcGhfLmhpZGRlbl9jdHhfO1xuICBjdHguYmVnaW5QYXRoKCk7XG4gIGN0eC5yZWN0KHRoaXMuYXJlYS54LCB0aGlzLmFyZWEueSwgdGhpcy5hcmVhLncsIHRoaXMuYXJlYS5oKTtcbiAgY3R4LmNsaXAoKTtcbn07XG5cbi8qKlxuICogQ2xlYXJzIG91dCBhbGwgY2hhcnQgY29udGVudCBhbmQgRE9NIGVsZW1lbnRzLlxuICogVGhpcyBpcyBjYWxsZWQgaW1tZWRpYXRlbHkgYmVmb3JlIHJlbmRlcigpIG9uIGV2ZXJ5IGZyYW1lLCBpbmNsdWRpbmdcbiAqIGR1cmluZyB6b29tcyBhbmQgcGFucy5cbiAqIEBwcml2YXRlXG4gKi9cbkR5Z3JhcGhDYW52YXNSZW5kZXJlci5wcm90b3R5cGUuY2xlYXIgPSBmdW5jdGlvbigpIHtcbiAgdGhpcy5lbGVtZW50Q29udGV4dC5jbGVhclJlY3QoMCwgMCwgdGhpcy53aWR0aCwgdGhpcy5oZWlnaHQpO1xufTtcblxuLyoqXG4gKiBUaGlzIG1ldGhvZCBpcyByZXNwb25zaWJsZSBmb3IgZHJhd2luZyBldmVyeXRoaW5nIG9uIHRoZSBjaGFydCwgaW5jbHVkaW5nXG4gKiBsaW5lcywgaGlnaC9sb3cgYmFuZHMsIGZpbGxzIGFuZCBheGVzLlxuICogSXQgaXMgY2FsbGVkIGltbWVkaWF0ZWx5IGFmdGVyIGNsZWFyKCkgb24gZXZlcnkgZnJhbWUsIGluY2x1ZGluZyBkdXJpbmcgcGFuc1xuICogYW5kIHpvb21zLlxuICogQHByaXZhdGVcbiAqL1xuRHlncmFwaENhbnZhc1JlbmRlcmVyLnByb3RvdHlwZS5yZW5kZXIgPSBmdW5jdGlvbigpIHtcbiAgLy8gYXR0YWNoZXMgcG9pbnQuY2FudmFze3gseX1cbiAgdGhpcy5fdXBkYXRlUG9pbnRzKCk7XG5cbiAgLy8gYWN0dWFsbHkgZHJhd3MgdGhlIGNoYXJ0LlxuICB0aGlzLl9yZW5kZXJMaW5lQ2hhcnQoKTtcbn07XG5cbi8qKlxuICogUmV0dXJucyBhIHByZWRpY2F0ZSB0byBiZSB1c2VkIHdpdGggYW4gaXRlcmF0b3IsIHdoaWNoIHdpbGxcbiAqIGl0ZXJhdGUgb3ZlciBwb2ludHMgYXBwcm9wcmlhdGVseSwgZGVwZW5kaW5nIG9uIHdoZXRoZXJcbiAqIGNvbm5lY3RTZXBhcmF0ZWRQb2ludHMgaXMgdHJ1ZS4gV2hlbiBpdCdzIGZhbHNlLCB0aGUgcHJlZGljYXRlIHdpbGxcbiAqIHNraXAgb3ZlciBwb2ludHMgd2l0aCBtaXNzaW5nIHlWYWxzLlxuICovXG5EeWdyYXBoQ2FudmFzUmVuZGVyZXIuX2dldEl0ZXJhdG9yUHJlZGljYXRlID0gZnVuY3Rpb24oY29ubmVjdFNlcGFyYXRlZFBvaW50cykge1xuICByZXR1cm4gY29ubmVjdFNlcGFyYXRlZFBvaW50cyA/XG4gICAgICBEeWdyYXBoQ2FudmFzUmVuZGVyZXIuX3ByZWRpY2F0ZVRoYXRTa2lwc0VtcHR5UG9pbnRzIDpcbiAgICAgIG51bGw7XG59O1xuXG5EeWdyYXBoQ2FudmFzUmVuZGVyZXIuX3ByZWRpY2F0ZVRoYXRTa2lwc0VtcHR5UG9pbnRzID1cbiAgICBmdW5jdGlvbihhcnJheSwgaWR4KSB7XG4gIHJldHVybiBhcnJheVtpZHhdLnl2YWwgIT09IG51bGw7XG59O1xuXG4vKipcbiAqIERyYXdzIGEgbGluZSB3aXRoIHRoZSBzdHlsZXMgcGFzc2VkIGluIGFuZCBjYWxscyBhbGwgdGhlIGRyYXdQb2ludENhbGxiYWNrcy5cbiAqIEBwYXJhbSB7T2JqZWN0fSBlIFRoZSBkaWN0aW9uYXJ5IHBhc3NlZCB0byB0aGUgcGxvdHRlciBmdW5jdGlvbi5cbiAqIEBwcml2YXRlXG4gKi9cbkR5Z3JhcGhDYW52YXNSZW5kZXJlci5fZHJhd1N0eWxlZExpbmUgPSBmdW5jdGlvbihlLFxuICAgIGNvbG9yLCBzdHJva2VXaWR0aCwgc3Ryb2tlUGF0dGVybiwgZHJhd1BvaW50cyxcbiAgICBkcmF3UG9pbnRDYWxsYmFjaywgcG9pbnRTaXplKSB7XG4gIHZhciBnID0gZS5keWdyYXBoO1xuICAvLyBUT0RPKGtvbmlnc2JlcmcpOiBDb21wdXRlIGF0dHJpYnV0ZXMgb3V0c2lkZSB0aGlzIG1ldGhvZCBjYWxsLlxuICB2YXIgc3RlcFBsb3QgPSBnLmdldEJvb2xlYW5PcHRpb24oXCJzdGVwUGxvdFwiLCBlLnNldE5hbWUpO1xuXG4gIGlmICghdXRpbHMuaXNBcnJheUxpa2Uoc3Ryb2tlUGF0dGVybikpIHtcbiAgICBzdHJva2VQYXR0ZXJuID0gbnVsbDtcbiAgfVxuXG4gIHZhciBkcmF3R2FwUG9pbnRzID0gZy5nZXRCb29sZWFuT3B0aW9uKCdkcmF3R2FwRWRnZVBvaW50cycsIGUuc2V0TmFtZSk7XG5cbiAgdmFyIHBvaW50cyA9IGUucG9pbnRzO1xuICB2YXIgc2V0TmFtZSA9IGUuc2V0TmFtZTtcbiAgdmFyIGl0ZXIgPSB1dGlscy5jcmVhdGVJdGVyYXRvcihwb2ludHMsIDAsIHBvaW50cy5sZW5ndGgsXG4gICAgICBEeWdyYXBoQ2FudmFzUmVuZGVyZXIuX2dldEl0ZXJhdG9yUHJlZGljYXRlKFxuICAgICAgICAgIGcuZ2V0Qm9vbGVhbk9wdGlvbihcImNvbm5lY3RTZXBhcmF0ZWRQb2ludHNcIiwgc2V0TmFtZSkpKTtcblxuICB2YXIgc3Ryb2tpbmcgPSBzdHJva2VQYXR0ZXJuICYmIChzdHJva2VQYXR0ZXJuLmxlbmd0aCA+PSAyKTtcblxuICB2YXIgY3R4ID0gZS5kcmF3aW5nQ29udGV4dDtcbiAgY3R4LnNhdmUoKTtcbiAgaWYgKHN0cm9raW5nKSB7XG4gICAgaWYgKGN0eC5zZXRMaW5lRGFzaCkgY3R4LnNldExpbmVEYXNoKHN0cm9rZVBhdHRlcm4pO1xuICB9XG5cbiAgdmFyIHBvaW50c09uTGluZSA9IER5Z3JhcGhDYW52YXNSZW5kZXJlci5fZHJhd1NlcmllcyhcbiAgICAgIGUsIGl0ZXIsIHN0cm9rZVdpZHRoLCBwb2ludFNpemUsIGRyYXdQb2ludHMsIGRyYXdHYXBQb2ludHMsIHN0ZXBQbG90LCBjb2xvcik7XG4gIER5Z3JhcGhDYW52YXNSZW5kZXJlci5fZHJhd1BvaW50c09uTGluZShcbiAgICAgIGUsIHBvaW50c09uTGluZSwgZHJhd1BvaW50Q2FsbGJhY2ssIGNvbG9yLCBwb2ludFNpemUpO1xuXG4gIGlmIChzdHJva2luZykge1xuICAgIGlmIChjdHguc2V0TGluZURhc2gpIGN0eC5zZXRMaW5lRGFzaChbXSk7XG4gIH1cblxuICBjdHgucmVzdG9yZSgpO1xufTtcblxuLyoqXG4gKiBUaGlzIGRvZXMgdGhlIGFjdHVhbCBkcmF3aW5nIG9mIGxpbmVzIG9uIHRoZSBjYW52YXMsIGZvciBqdXN0IG9uZSBzZXJpZXMuXG4gKiBSZXR1cm5zIGEgbGlzdCBvZiBbY2FudmFzeCwgY2FudmFzeV0gcGFpcnMgZm9yIHBvaW50cyBmb3Igd2hpY2ggYVxuICogZHJhd1BvaW50Q2FsbGJhY2sgc2hvdWxkIGJlIGZpcmVkLiAgVGhlc2UgaW5jbHVkZSBpc29sYXRlZCBwb2ludHMsIG9yIGFsbFxuICogcG9pbnRzIGlmIGRyYXdQb2ludHM9dHJ1ZS5cbiAqIEBwYXJhbSB7T2JqZWN0fSBlIFRoZSBkaWN0aW9uYXJ5IHBhc3NlZCB0byB0aGUgcGxvdHRlciBmdW5jdGlvbi5cbiAqIEBwcml2YXRlXG4gKi9cbkR5Z3JhcGhDYW52YXNSZW5kZXJlci5fZHJhd1NlcmllcyA9IGZ1bmN0aW9uKGUsXG4gICAgaXRlciwgc3Ryb2tlV2lkdGgsIHBvaW50U2l6ZSwgZHJhd1BvaW50cywgZHJhd0dhcFBvaW50cywgc3RlcFBsb3QsIGNvbG9yKSB7XG5cbiAgdmFyIHByZXZDYW52YXNYID0gbnVsbDtcbiAgdmFyIHByZXZDYW52YXNZID0gbnVsbDtcbiAgdmFyIG5leHRDYW52YXNZID0gbnVsbDtcbiAgdmFyIGlzSXNvbGF0ZWQ7IC8vIHRydWUgaWYgdGhpcyBwb2ludCBpcyBpc29sYXRlZCAobm8gbGluZSBzZWdtZW50cylcbiAgdmFyIHBvaW50OyAvLyB0aGUgcG9pbnQgYmVpbmcgcHJvY2Vzc2VkIGluIHRoZSB3aGlsZSBsb29wXG4gIHZhciBwb2ludHNPbkxpbmUgPSBbXTsgLy8gQXJyYXkgb2YgW2NhbnZhc3gsIGNhbnZhc3ldIHBhaXJzLlxuICB2YXIgZmlyc3QgPSB0cnVlOyAvLyB0aGUgZmlyc3QgY3ljbGUgdGhyb3VnaCB0aGUgd2hpbGUgbG9vcFxuXG4gIHZhciBjdHggPSBlLmRyYXdpbmdDb250ZXh0O1xuICBjdHguYmVnaW5QYXRoKCk7XG4gIGN0eC5zdHJva2VTdHlsZSA9IGNvbG9yO1xuICBjdHgubGluZVdpZHRoID0gc3Ryb2tlV2lkdGg7XG5cbiAgLy8gTk9URTogd2UgYnJlYWsgdGhlIGl0ZXJhdG9yJ3MgZW5jYXBzdWxhdGlvbiBoZXJlIGZvciBhYm91dCBhIDI1JSBzcGVlZHVwLlxuICB2YXIgYXJyID0gaXRlci5hcnJheV87XG4gIHZhciBsaW1pdCA9IGl0ZXIuZW5kXztcbiAgdmFyIHByZWRpY2F0ZSA9IGl0ZXIucHJlZGljYXRlXztcblxuICBmb3IgKHZhciBpID0gaXRlci5zdGFydF87IGkgPCBsaW1pdDsgaSsrKSB7XG4gICAgcG9pbnQgPSBhcnJbaV07XG4gICAgaWYgKHByZWRpY2F0ZSkge1xuICAgICAgd2hpbGUgKGkgPCBsaW1pdCAmJiAhcHJlZGljYXRlKGFyciwgaSkpIHtcbiAgICAgICAgaSsrO1xuICAgICAgfVxuICAgICAgaWYgKGkgPT0gbGltaXQpIGJyZWFrO1xuICAgICAgcG9pbnQgPSBhcnJbaV07XG4gICAgfVxuXG4gICAgLy8gRklYTUU6IFRoZSAnY2FudmFzeSAhPSBjYW52YXN5JyB0ZXN0IGhlcmUgY2F0Y2hlcyBOYU4gdmFsdWVzIGJ1dCB0aGUgdGVzdFxuICAgIC8vIGRvZXNuJ3QgY2F0Y2ggSW5maW5pdHkgdmFsdWVzLiBDb3VsZCBjaGFuZ2UgdGhpcyB0b1xuICAgIC8vICFpc0Zpbml0ZShwb2ludC5jYW52YXN5KSwgYnV0IEkgYXNzdW1lIGl0IGF2b2lkcyBpc05hTiBmb3IgcGVyZm9ybWFuY2U/XG4gICAgaWYgKHBvaW50LmNhbnZhc3kgPT09IG51bGwgfHwgcG9pbnQuY2FudmFzeSAhPSBwb2ludC5jYW52YXN5KSB7XG4gICAgICBpZiAoc3RlcFBsb3QgJiYgcHJldkNhbnZhc1ggIT09IG51bGwpIHtcbiAgICAgICAgLy8gRHJhdyBhIGhvcml6b250YWwgbGluZSB0byB0aGUgc3RhcnQgb2YgdGhlIG1pc3NpbmcgZGF0YVxuICAgICAgICBjdHgubW92ZVRvKHByZXZDYW52YXNYLCBwcmV2Q2FudmFzWSk7XG4gICAgICAgIGN0eC5saW5lVG8ocG9pbnQuY2FudmFzeCwgcHJldkNhbnZhc1kpO1xuICAgICAgfVxuICAgICAgcHJldkNhbnZhc1ggPSBwcmV2Q2FudmFzWSA9IG51bGw7XG4gICAgfSBlbHNlIHtcbiAgICAgIGlzSXNvbGF0ZWQgPSBmYWxzZTtcbiAgICAgIGlmIChkcmF3R2FwUG9pbnRzIHx8IHByZXZDYW52YXNYID09PSBudWxsKSB7XG4gICAgICAgIGl0ZXIubmV4dElkeF8gPSBpO1xuICAgICAgICBpdGVyLm5leHQoKTtcbiAgICAgICAgbmV4dENhbnZhc1kgPSBpdGVyLmhhc05leHQgPyBpdGVyLnBlZWsuY2FudmFzeSA6IG51bGw7XG5cbiAgICAgICAgdmFyIGlzTmV4dENhbnZhc1lOdWxsT3JOYU4gPSBuZXh0Q2FudmFzWSA9PT0gbnVsbCB8fFxuICAgICAgICAgICAgbmV4dENhbnZhc1kgIT0gbmV4dENhbnZhc1k7XG4gICAgICAgIGlzSXNvbGF0ZWQgPSAocHJldkNhbnZhc1ggPT09IG51bGwgJiYgaXNOZXh0Q2FudmFzWU51bGxPck5hTik7XG4gICAgICAgIGlmIChkcmF3R2FwUG9pbnRzKSB7XG4gICAgICAgICAgLy8gQWxzbyBjb25zaWRlciBhIHBvaW50IHRvIGJlIFwiaXNvbGF0ZWRcIiBpZiBpdCdzIGFkamFjZW50IHRvIGFcbiAgICAgICAgICAvLyBudWxsIHBvaW50LCBleGNsdWRpbmcgdGhlIGdyYXBoIGVkZ2VzLlxuICAgICAgICAgIGlmICgoIWZpcnN0ICYmIHByZXZDYW52YXNYID09PSBudWxsKSB8fFxuICAgICAgICAgICAgICAoaXRlci5oYXNOZXh0ICYmIGlzTmV4dENhbnZhc1lOdWxsT3JOYU4pKSB7XG4gICAgICAgICAgICBpc0lzb2xhdGVkID0gdHJ1ZTtcbiAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgIH1cblxuICAgICAgaWYgKHByZXZDYW52YXNYICE9PSBudWxsKSB7XG4gICAgICAgIGlmIChzdHJva2VXaWR0aCkge1xuICAgICAgICAgIGlmIChzdGVwUGxvdCkge1xuICAgICAgICAgICAgY3R4Lm1vdmVUbyhwcmV2Q2FudmFzWCwgcHJldkNhbnZhc1kpO1xuICAgICAgICAgICAgY3R4LmxpbmVUbyhwb2ludC5jYW52YXN4LCBwcmV2Q2FudmFzWSk7XG4gICAgICAgICAgfVxuXG4gICAgICAgICAgY3R4LmxpbmVUbyhwb2ludC5jYW52YXN4LCBwb2ludC5jYW52YXN5KTtcbiAgICAgICAgfVxuICAgICAgfSBlbHNlIHtcbiAgICAgICAgY3R4Lm1vdmVUbyhwb2ludC5jYW52YXN4LCBwb2ludC5jYW52YXN5KTtcbiAgICAgIH1cbiAgICAgIGlmIChkcmF3UG9pbnRzIHx8IGlzSXNvbGF0ZWQpIHtcbiAgICAgICAgcG9pbnRzT25MaW5lLnB1c2goW3BvaW50LmNhbnZhc3gsIHBvaW50LmNhbnZhc3ksIHBvaW50LmlkeF0pO1xuICAgICAgfVxuICAgICAgcHJldkNhbnZhc1ggPSBwb2ludC5jYW52YXN4O1xuICAgICAgcHJldkNhbnZhc1kgPSBwb2ludC5jYW52YXN5O1xuICAgIH1cbiAgICBmaXJzdCA9IGZhbHNlO1xuICB9XG4gIGN0eC5zdHJva2UoKTtcbiAgcmV0dXJuIHBvaW50c09uTGluZTtcbn07XG5cbi8qKlxuICogVGhpcyBmaXJlcyB0aGUgZHJhd1BvaW50Q2FsbGJhY2sgZnVuY3Rpb25zLCB3aGljaCBkcmF3IGRvdHMgb24gdGhlIHBvaW50cyBieVxuICogZGVmYXVsdC4gVGhpcyBnZXRzIHVzZWQgd2hlbiB0aGUgXCJkcmF3UG9pbnRzXCIgb3B0aW9uIGlzIHNldCwgb3Igd2hlbiB0aGVyZVxuICogYXJlIGlzb2xhdGVkIHBvaW50cy5cbiAqIEBwYXJhbSB7T2JqZWN0fSBlIFRoZSBkaWN0aW9uYXJ5IHBhc3NlZCB0byB0aGUgcGxvdHRlciBmdW5jdGlvbi5cbiAqIEBwcml2YXRlXG4gKi9cbkR5Z3JhcGhDYW52YXNSZW5kZXJlci5fZHJhd1BvaW50c09uTGluZSA9IGZ1bmN0aW9uKFxuICAgIGUsIHBvaW50c09uTGluZSwgZHJhd1BvaW50Q2FsbGJhY2ssIGNvbG9yLCBwb2ludFNpemUpIHtcbiAgdmFyIGN0eCA9IGUuZHJhd2luZ0NvbnRleHQ7XG4gIGZvciAodmFyIGlkeCA9IDA7IGlkeCA8IHBvaW50c09uTGluZS5sZW5ndGg7IGlkeCsrKSB7XG4gICAgdmFyIGNiID0gcG9pbnRzT25MaW5lW2lkeF07XG4gICAgY3R4LnNhdmUoKTtcbiAgICBkcmF3UG9pbnRDYWxsYmFjay5jYWxsKGUuZHlncmFwaCxcbiAgICAgICAgZS5keWdyYXBoLCBlLnNldE5hbWUsIGN0eCwgY2JbMF0sIGNiWzFdLCBjb2xvciwgcG9pbnRTaXplLCBjYlsyXSk7XG4gICAgY3R4LnJlc3RvcmUoKTtcbiAgfVxufTtcblxuLyoqXG4gKiBBdHRhY2hlcyBjYW52YXMgY29vcmRpbmF0ZXMgdG8gdGhlIHBvaW50cyBhcnJheS5cbiAqIEBwcml2YXRlXG4gKi9cbkR5Z3JhcGhDYW52YXNSZW5kZXJlci5wcm90b3R5cGUuX3VwZGF0ZVBvaW50cyA9IGZ1bmN0aW9uKCkge1xuICAvLyBVcGRhdGUgUG9pbnRzXG4gIC8vIFRPRE8oZGFudmspOiBoZXJlXG4gIC8vXG4gIC8vIFRPRE8oYmhzKTogdGhpcyBsb29wIGlzIGEgaG90LXNwb3QgZm9yIGhpZ2gtcG9pbnQtY291bnQgY2hhcnRzLiBUaGVzZVxuICAvLyB0cmFuc2Zvcm1hdGlvbnMgY2FuIGJlIHB1c2hlZCBpbnRvIHRoZSBjYW52YXMgdmlhIGxpbmVhciB0cmFuc2Zvcm1hdGlvblxuICAvLyBtYXRyaWNlcy5cbiAgLy8gTk9URShkYW52ayk6IHRoaXMgaXMgdHJpY2tpZXIgdGhhbiBpdCBzb3VuZHMgYXQgZmlyc3QuIFRoZSB0cmFuc2Zvcm1hdGlvblxuICAvLyBuZWVkcyB0byBiZSBkb25lIGJlZm9yZSB0aGUgLm1vdmVUbygpIGFuZCAubGluZVRvKCkgY2FsbHMsIGJ1dCBtdXN0IGJlXG4gIC8vIHVuZG9uZSBiZWZvcmUgdGhlIC5zdHJva2UoKSBjYWxsIHRvIGVuc3VyZSB0aGF0IHRoZSBzdHJva2Ugd2lkdGggaXNcbiAgLy8gdW5hZmZlY3RlZC4gIEFuIGFsdGVybmF0aXZlIGlzIHRvIHJlZHVjZSB0aGUgc3Ryb2tlIHdpZHRoIGluIHRoZVxuICAvLyB0cmFuc2Zvcm1lZCBjb29yZGluYXRlIHNwYWNlLCBidXQgeW91IGNhbid0IHNwZWNpZnkgZGlmZmVyZW50IHZhbHVlcyBmb3JcbiAgLy8gZWFjaCBkaW1lbnNpb24gKGFzIHlvdSBjYW4gd2l0aCAuc2NhbGUoKSkuIFRoZSBzcGVlZHVwIGhlcmUgaXMgfjEyJS5cbiAgdmFyIHNldHMgPSB0aGlzLmxheW91dC5wb2ludHM7XG4gIGZvciAodmFyIGkgPSBzZXRzLmxlbmd0aDsgaS0tOykge1xuICAgIHZhciBwb2ludHMgPSBzZXRzW2ldO1xuICAgIGZvciAodmFyIGogPSBwb2ludHMubGVuZ3RoOyBqLS07KSB7XG4gICAgICB2YXIgcG9pbnQgPSBwb2ludHNbal07XG4gICAgICBwb2ludC5jYW52YXN4ID0gdGhpcy5hcmVhLncgKiBwb2ludC54ICsgdGhpcy5hcmVhLng7XG4gICAgICBwb2ludC5jYW52YXN5ID0gdGhpcy5hcmVhLmggKiBwb2ludC55ICsgdGhpcy5hcmVhLnk7XG4gICAgfVxuICB9XG59O1xuXG4vKipcbiAqIEFkZCBjYW52YXMgQWN0dWFsbHkgZHJhdyB0aGUgbGluZXMgY2hhcnQsIGluY2x1ZGluZyBoaWdoL2xvdyBiYW5kcy5cbiAqXG4gKiBUaGlzIGZ1bmN0aW9uIGNhbiBvbmx5IGJlIGNhbGxlZCBpZiBEeWdyYXBoTGF5b3V0J3MgcG9pbnRzIGFycmF5IGhhcyBiZWVuXG4gKiB1cGRhdGVkIHdpdGggY2FudmFze3gseX0gYXR0cmlidXRlcywgaS5lLiBieVxuICogRHlncmFwaENhbnZhc1JlbmRlcmVyLl91cGRhdGVQb2ludHMuXG4gKlxuICogQHBhcmFtIHtzdHJpbmc9fSBvcHRfc2VyaWVzTmFtZSB3aGVuIHNwZWNpZmllZCwgb25seSB0aGF0IHNlcmllcyB3aWxsXG4gKiAgICAgYmUgZHJhd24uIChUaGlzIGlzIHVzZWQgZm9yIGV4cGVkaXRlZCByZWRyYXdpbmcgd2l0aCBoaWdobGlnaHRTZXJpZXNPcHRzKVxuICogQHBhcmFtIHtDYW52YXNSZW5kZXJpbmdDb250ZXh0MkR9IG9wdF9jdHggd2hlbiBzcGVjaWZpZWQsIHRoZSBkcmF3aW5nXG4gKiAgICAgY29udGV4dC4gIEhvd2V2ZXIsIGxpbmVzIGFyZSB0eXBpY2FsbHkgZHJhd24gb24gdGhlIG9iamVjdCdzXG4gKiAgICAgZWxlbWVudENvbnRleHQuXG4gKiBAcHJpdmF0ZVxuICovXG5EeWdyYXBoQ2FudmFzUmVuZGVyZXIucHJvdG90eXBlLl9yZW5kZXJMaW5lQ2hhcnQgPSBmdW5jdGlvbihvcHRfc2VyaWVzTmFtZSwgb3B0X2N0eCkge1xuICB2YXIgY3R4ID0gb3B0X2N0eCB8fCB0aGlzLmVsZW1lbnRDb250ZXh0O1xuICB2YXIgaTtcblxuICB2YXIgc2V0cyA9IHRoaXMubGF5b3V0LnBvaW50cztcbiAgdmFyIHNldE5hbWVzID0gdGhpcy5sYXlvdXQuc2V0TmFtZXM7XG4gIHZhciBzZXROYW1lO1xuXG4gIHRoaXMuY29sb3JzID0gdGhpcy5keWdyYXBoXy5jb2xvcnNNYXBfO1xuXG4gIC8vIERldGVybWluZSB3aGljaCBzZXJpZXMgaGF2ZSBzcGVjaWFsaXplZCBwbG90dGVycy5cbiAgdmFyIHBsb3R0ZXJfYXR0ciA9IHRoaXMuZHlncmFwaF8uZ2V0T3B0aW9uKFwicGxvdHRlclwiKTtcbiAgdmFyIHBsb3R0ZXJzID0gcGxvdHRlcl9hdHRyO1xuICBpZiAoIXV0aWxzLmlzQXJyYXlMaWtlKHBsb3R0ZXJzKSkge1xuICAgIHBsb3R0ZXJzID0gW3Bsb3R0ZXJzXTtcbiAgfVxuXG4gIHZhciBzZXRQbG90dGVycyA9IHt9OyAgLy8gc2VyaWVzIG5hbWUgLT4gcGxvdHRlciBmbi5cbiAgZm9yIChpID0gMDsgaSA8IHNldE5hbWVzLmxlbmd0aDsgaSsrKSB7XG4gICAgc2V0TmFtZSA9IHNldE5hbWVzW2ldO1xuICAgIHZhciBzZXRQbG90dGVyID0gdGhpcy5keWdyYXBoXy5nZXRPcHRpb24oXCJwbG90dGVyXCIsIHNldE5hbWUpO1xuICAgIGlmIChzZXRQbG90dGVyID09IHBsb3R0ZXJfYXR0cikgY29udGludWU7ICAvLyBub3Qgc3BlY2lhbGl6ZWQuXG5cbiAgICBzZXRQbG90dGVyc1tzZXROYW1lXSA9IHNldFBsb3R0ZXI7XG4gIH1cblxuICBmb3IgKGkgPSAwOyBpIDwgcGxvdHRlcnMubGVuZ3RoOyBpKyspIHtcbiAgICB2YXIgcGxvdHRlciA9IHBsb3R0ZXJzW2ldO1xuICAgIHZhciBpc19sYXN0ID0gKGkgPT0gcGxvdHRlcnMubGVuZ3RoIC0gMSk7XG5cbiAgICBmb3IgKHZhciBqID0gMDsgaiA8IHNldHMubGVuZ3RoOyBqKyspIHtcbiAgICAgIHNldE5hbWUgPSBzZXROYW1lc1tqXTtcbiAgICAgIGlmIChvcHRfc2VyaWVzTmFtZSAmJiBzZXROYW1lICE9IG9wdF9zZXJpZXNOYW1lKSBjb250aW51ZTtcblxuICAgICAgdmFyIHBvaW50cyA9IHNldHNbal07XG5cbiAgICAgIC8vIE9ubHkgdGhyb3cgaW4gdGhlIHNwZWNpYWxpemVkIHBsb3R0ZXJzIG9uIHRoZSBsYXN0IGl0ZXJhdGlvbi5cbiAgICAgIHZhciBwID0gcGxvdHRlcjtcbiAgICAgIGlmIChzZXROYW1lIGluIHNldFBsb3R0ZXJzKSB7XG4gICAgICAgIGlmIChpc19sYXN0KSB7XG4gICAgICAgICAgcCA9IHNldFBsb3R0ZXJzW3NldE5hbWVdO1xuICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgIC8vIERvbid0IHVzZSB0aGUgc3RhbmRhcmQgcGxvdHRlcnMgaW4gdGhpcyBjYXNlLlxuICAgICAgICAgIGNvbnRpbnVlO1xuICAgICAgICB9XG4gICAgICB9XG5cbiAgICAgIHZhciBjb2xvciA9IHRoaXMuY29sb3JzW3NldE5hbWVdO1xuICAgICAgdmFyIHN0cm9rZVdpZHRoID0gdGhpcy5keWdyYXBoXy5nZXRPcHRpb24oXCJzdHJva2VXaWR0aFwiLCBzZXROYW1lKTtcblxuICAgICAgY3R4LnNhdmUoKTtcbiAgICAgIGN0eC5zdHJva2VTdHlsZSA9IGNvbG9yO1xuICAgICAgY3R4LmxpbmVXaWR0aCA9IHN0cm9rZVdpZHRoO1xuICAgICAgcCh7XG4gICAgICAgIHBvaW50czogcG9pbnRzLFxuICAgICAgICBzZXROYW1lOiBzZXROYW1lLFxuICAgICAgICBkcmF3aW5nQ29udGV4dDogY3R4LFxuICAgICAgICBjb2xvcjogY29sb3IsXG4gICAgICAgIHN0cm9rZVdpZHRoOiBzdHJva2VXaWR0aCxcbiAgICAgICAgZHlncmFwaDogdGhpcy5keWdyYXBoXyxcbiAgICAgICAgYXhpczogdGhpcy5keWdyYXBoXy5heGlzUHJvcGVydGllc0ZvclNlcmllcyhzZXROYW1lKSxcbiAgICAgICAgcGxvdEFyZWE6IHRoaXMuYXJlYSxcbiAgICAgICAgc2VyaWVzSW5kZXg6IGosXG4gICAgICAgIHNlcmllc0NvdW50OiBzZXRzLmxlbmd0aCxcbiAgICAgICAgc2luZ2xlU2VyaWVzTmFtZTogb3B0X3Nlcmllc05hbWUsXG4gICAgICAgIGFsbFNlcmllc1BvaW50czogc2V0c1xuICAgICAgfSk7XG4gICAgICBjdHgucmVzdG9yZSgpO1xuICAgIH1cbiAgfVxufTtcblxuLyoqXG4gKiBTdGFuZGFyZCBwbG90dGVycy4gVGhlc2UgbWF5IGJlIHVzZWQgYnkgY2xpZW50cyB2aWEgRHlncmFwaC5QbG90dGVycy5cbiAqIFNlZSBjb21tZW50cyB0aGVyZSBmb3IgbW9yZSBkZXRhaWxzLlxuICovXG5EeWdyYXBoQ2FudmFzUmVuZGVyZXIuX1Bsb3R0ZXJzID0ge1xuICBsaW5lUGxvdHRlcjogZnVuY3Rpb24oZSkge1xuICAgIER5Z3JhcGhDYW52YXNSZW5kZXJlci5fbGluZVBsb3R0ZXIoZSk7XG4gIH0sXG5cbiAgZmlsbFBsb3R0ZXI6IGZ1bmN0aW9uKGUpIHtcbiAgICBEeWdyYXBoQ2FudmFzUmVuZGVyZXIuX2ZpbGxQbG90dGVyKGUpO1xuICB9LFxuXG4gIGVycm9yUGxvdHRlcjogZnVuY3Rpb24oZSkge1xuICAgIER5Z3Jhc