@qogni/dygraphs
Version:
dygraphs is a fast, flexible open source JavaScript charting library.
814 lines (770 loc) • 107 kB
JavaScript
/**
* @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(e) { return e && e.__esModule ? e : { "default": e }; }
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { "default": e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n["default"] = e, t && t.set(e, n), n; }
/**
* @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 gapThreshold = e.dygraph.getOption("gapThreshold", e.setName);
var gapThresholdFunc = function gapThresholdFunc(prevPoint, curPoint) {
return false;
};
if (gapThreshold) {
if (typeof gapThreshold == 'number') {
gapThresholdFunc = function gapThresholdFunc(prevPoint, point) {
return point.xval - prevPoint.xval >= gapThreshold;
};
} else if (typeof gapThreshold == 'function') {
gapThresholdFunc = gapThreshold;
}
}
var pointsOnLine = DygraphCanvasRenderer._drawSeries(e, iter, strokeWidth, pointSize, drawPoints, drawGapPoints, stepPlot, color, gapThresholdFunc);
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, gapThresholdFunc) {
var prevPoint = null;
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);
}
if (gapThresholdFunc(prevPoint, point)) {
ctx.moveTo(point.canvasx, point.canvasy);
} else {
ctx.lineTo(point.canvasx, point.canvasy);
}
}
} else {
ctx.moveTo(point.canvasx, point.canvasy);
}
if (drawPoints || isIsolated) {
pointsOnLine.push([point.canvasx, point.canvasy, point.idx]);
}
prevPoint = point;
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,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJPYmplY3QiLCJkZWZpbmVQcm9wZXJ0eSIsImV4cG9ydHMiLCJ2YWx1ZSIsInV0aWxzIiwiX2ludGVyb3BSZXF1aXJlV2lsZGNhcmQiLCJyZXF1aXJlIiwiX2R5Z3JhcGgiLCJfaW50ZXJvcFJlcXVpcmVEZWZhdWx0IiwiZSIsIl9fZXNNb2R1bGUiLCJfZ2V0UmVxdWlyZVdpbGRjYXJkQ2FjaGUiLCJXZWFrTWFwIiwiciIsInQiLCJoYXMiLCJnZXQiLCJuIiwiX19wcm90b19fIiwiYSIsImdldE93blByb3BlcnR5RGVzY3JpcHRvciIsInUiLCJoYXNPd25Qcm9wZXJ0eSIsImNhbGwiLCJpIiwic2V0IiwiRHlncmFwaENhbnZhc1JlbmRlcmVyIiwiZHlncmFwaCIsImVsZW1lbnQiLCJlbGVtZW50Q29udGV4dCIsImxheW91dCIsImR5Z3JhcGhfIiwiaGVpZ2h0IiwiaGVpZ2h0XyIsIndpZHRoIiwid2lkdGhfIiwiaXNDYW52YXNTdXBwb3J0ZWQiLCJhcmVhIiwiZ2V0UGxvdEFyZWEiLCJjdHgiLCJjYW52YXNfY3R4XyIsImJlZ2luUGF0aCIsInJlY3QiLCJ4IiwieSIsInciLCJoIiwiY2xpcCIsImhpZGRlbl9jdHhfIiwicHJvdG90eXBlIiwiY2xlYXIiLCJjbGVhclJlY3QiLCJyZW5kZXIiLCJfdXBkYXRlUG9pbnRzIiwiX3JlbmRlckxpbmVDaGFydCIsIl9nZXRJdGVyYXRvclByZWRpY2F0ZSIsImNvbm5lY3RTZXBhcmF0ZWRQb2ludHMiLCJfcHJlZGljYXRlVGhhdFNraXBzRW1wdHlQb2ludHMiLCJhcnJheSIsImlkeCIsInl2YWwiLCJfZHJhd1N0eWxlZExpbmUiLCJjb2xvciIsInN0cm9rZVdpZHRoIiwic3Ryb2tlUGF0dGVybiIsImRyYXdQb2ludHMiLCJkcmF3UG9pbnRDYWxsYmFjayIsInBvaW50U2l6ZSIsImciLCJzdGVwUGxvdCIsImdldEJvb2xlYW5PcHRpb24iLCJzZXROYW1lIiwiaXNBcnJheUxpa2UiLCJkcmF3R2FwUG9pbnRzIiwicG9pbnRzIiwiaXRlciIsImNyZWF0ZUl0ZXJhdG9yIiwibGVuZ3RoIiwic3Ryb2tpbmciLCJkcmF3aW5nQ29udGV4dCIsInNhdmUiLCJzZXRMaW5lRGFzaCIsImdhcFRocmVzaG9sZCIsImdldE9wdGlvbiIsImdhcFRocmVzaG9sZEZ1bmMiLCJwcmV2UG9pbnQiLCJjdXJQb2ludCIsInBvaW50IiwieHZhbCIsInBvaW50c09uTGluZSIsIl9kcmF3U2VyaWVzIiwiX2RyYXdQb2ludHNPbkxpbmUiLCJyZXN0b3JlIiwicHJldkNhbnZhc1giLCJwcmV2Q2FudmFzWSIsIm5leHRDYW52YXNZIiwiaXNJc29sYXRlZCIsImZpcnN0Iiwic3Ryb2tlU3R5bGUiLCJsaW5lV2lkdGgiLCJhcnIiLCJhcnJheV8iLCJsaW1pdCIsImVuZF8iLCJwcmVkaWNhdGUiLCJwcmVkaWNhdGVfIiwic3RhcnRfIiwiY2FudmFzeSIsIm1vdmVUbyIsImxpbmVUbyIsImNhbnZhc3giLCJuZXh0SWR4XyIsIm5leHQiLCJoYXNOZXh0IiwicGVlayIsImlzTmV4dENhbnZhc1lOdWxsT3JOYU4iLCJwdXNoIiwic3Ryb2tlIiwiY2IiLCJzZXRzIiwiaiIsIm9wdF9zZXJpZXNOYW1lIiwib3B0X2N0eCIsInNldE5hbWVzIiwiY29sb3JzIiwiY29sb3JzTWFwXyIsInBsb3R0ZXJfYXR0ciIsInBsb3R0ZXJzIiwic2V0UGxvdHRlcnMiLCJzZXRQbG90dGVyIiwicGxvdHRlciIsImlzX2xhc3QiLCJwIiwiYXhpcyIsImF4aXNQcm9wZXJ0aWVzRm9yU2VyaWVzIiwicGxvdEFyZWEiLCJzZXJpZXNJbmRleCIsInNlcmllc0NvdW50Iiwic2luZ2xlU2VyaWVzTmFtZSIsImFsbFNlcmllc1BvaW50cyIsIl9QbG90dGVycyIsImxpbmVQbG90dGVyIiwiX2xpbmVQbG90dGVyIiwiZmlsbFBsb3R0ZXIiLCJfZmlsbFBsb3R0ZXIiLCJlcnJvclBsb3R0ZXIiLCJfZXJyb3JQbG90dGVyIiwiYm9yZGVyV2lkdGgiLCJnZXROdW1lcmljT3B0aW9uIiwiQ2lyY2xlcyIsIkRFRkFVTFQiLCJlcnJvckJhcnMiLCJmaWxsR3JhcGgiLCJjb25zb2xlIiwid2FybiIsImZpbGxBbHBoYSIsIm5ld1lzIiwicHJldlgiLCJOYU4iLCJwcmV2WSIsInByZXZZcyIsInJnYiIsInRvUkdCXyIsImVycl9jb2xvciIsImIiLCJmaWxsU3R5bGUiLCJpc051bGxVbmRlZmluZWRPck5hTiIsInVuZGVmaW5lZCIsImlzTmFOIiwieV9ib3R0b20iLCJ5X3RvcCIsImNsb3NlUGF0aCIsImZpbGwiLCJfZmFzdENhbnZhc1Byb3h5IiwiY29udGV4dCIsInBlbmRpbmdBY3Rpb25zIiwibGFzdFJvdW5kZWRYIiwibGFzdEZsdXNoZWRYIiwiTElORV9UTyIsIk1PVkVfVE8iLCJhY3Rpb25Db3VudCIsImNvbXByZXNzQWN0aW9ucyIsIm9wdF9sb3NzbGVzc09ubHkiLCJhY3Rpb24iLCJwcmV2QWN0aW9uIiwic3BsaWNlIiwic3RhcnRJZHgiLCJtaW5JZHgiLCJtYXhJZHgiLCJtaW5BY3Rpb24iLCJtYXhBY3Rpb24iLCJmbHVzaEFjdGlvbnMiLCJvcHRfbm9Mb3NzeUNvbXByZXNzaW9uIiwibGVuIiwiYWRkQWN0aW9uIiwicngiLCJNYXRoIiwicm91bmQiLCJoYXNHYXBPbkxlZnQiLCJoYXNHYXBPblJpZ2h0IiwiaGFzR2FwIiwiX2NvdW50IiwiZ2V0TGFiZWxzIiwic2xpY2UiLCJ2aXNpYmlsaXR5IiwiYW55U2VyaWVzRmlsbGVkIiwic2V0Q291bnQiLCJzdGFja2VkR3JhcGgiLCJnZXRDb2xvcnMiLCJiYXNlbGluZSIsImN1cnJCYXNlbGluZSIsInByZXZTdGVwUGxvdCIsInRyYWNlQmFja1BhdGgiLCJiYXNlbGluZVgiLCJiYXNlbGluZVkiLCJwYXRoQmFjayIsInB0Iiwic2V0SWR4IiwiYXhpc1kiLCJtaW55dmFsIiwieXNjYWxlIiwibGFzdF94IiwiaXNfZmlyc3QiLCJEeWdyYXBoIiwiRk9SQ0VfRkFTVF9QUk9YWSIsImlzT0siLCJ5X3N0YWNrZWQiLCJsYXN0WSIsIl9kZWZhdWx0IiwibW9kdWxlIiwiZGVmYXVsdCJdLCJzb3VyY2VzIjpbIi4uL3NyYy9keWdyYXBoLWNhbnZhcy5qcyJdLCJzb3VyY2VzQ29udGVudCI6WyIvKipcbiAqIEBsaWNlbnNlXG4gKiBDb3B5cmlnaHQgMjAwNiBEYW4gVmFuZGVya2FtIChkYW52ZGtAZ21haWwuY29tKVxuICogTUlULWxpY2VuY2VkOiBodHRwczovL29wZW5zb3VyY2Uub3JnL2xpY2Vuc2VzL01JVFxuICovXG5cbi8qKlxuICogQGZpbGVvdmVydmlldyBCYXNlZCBvbiBQbG90S2l0LkNhbnZhc1JlbmRlcmVyLCBidXQgbW9kaWZpZWQgdG8gbWVldCB0aGVcbiAqIG5lZWRzIG9mIGR5Z3JhcGhzLlxuICpcbiAqIEluIHBhcnRpY3VsYXIsIHN1cHBvcnQgZm9yOlxuICogLSBncmlkIG92ZXJsYXlzXG4gKiAtIGhpZ2gvbG93IGJhbmRzXG4gKiAtIGR5Z3JhcGhzIGF0dHJpYnV0ZSBzeXN0ZW1cbiAqL1xuXG4vKipcbiAqIFRoZSBEeWdyYXBoQ2FudmFzUmVuZGVyZXIgY2xhc3MgZG9lcyB0aGUgYWN0dWFsIHJlbmRlcmluZyBvZiB0aGUgY2hhcnQgb250b1xuICogYSBjYW52YXMuIEl0J3MgYmFzZWQgb24gUGxvdEtpdC5DYW52YXNSZW5kZXJlci5cbiAqIEBwYXJhbSB7T2JqZWN0fSBlbGVtZW50IFRoZSBjYW52YXMgdG8gYXR0YWNoIHRvXG4gKiBAcGFyYW0ge09iamVjdH0gZWxlbWVudENvbnRleHQgVGhlIDJkIGNvbnRleHQgb2YgdGhlIGNhbnZhcyAoaW5qZWN0ZWQgc28gaXRcbiAqIGNhbiBiZSBtb2NrZWQgZm9yIHRlc3RpbmcuKVxuICogQHBhcmFtIHtMYXlvdXR9IGxheW91dCBUaGUgRHlncmFwaExheW91dCBvYmplY3QgZm9yIHRoaXMgZ3JhcGguXG4gKiBAY29uc3RydWN0b3JcbiAqL1xuXG4vKmdsb2JhbCBEeWdyYXBoOmZhbHNlICovXG5cInVzZSBzdHJpY3RcIjtcblxuaW1wb3J0ICogYXMgdXRpbHMgZnJvbSAnLi9keWdyYXBoLXV0aWxzJztcbmltcG9ydCBEeWdyYXBoIGZyb20gJy4vZHlncmFwaCc7XG5cbi8qKlxuICogQGNvbnN0cnVjdG9yXG4gKlxuICogVGhpcyBnZXRzIGNhbGxlZCB3aGVuIHRoZXJlIGFyZSBcIm5ldyBwb2ludHNcIiB0byBjaGFydC4gVGhpcyBpcyBnZW5lcmFsbHkgdGhlXG4gKiBjYXNlIHdoZW4gdGhlIHVuZGVybHlpbmcgZGF0YSBiZWluZyBjaGFydGVkIGhhcyBjaGFuZ2VkLiBJdCBpcyBfbm90XyBjYWxsZWRcbiAqIGluIHRoZSBjb21tb24gY2FzZSB0aGF0IHRoZSB1c2VyIGhhcyB6b29tZWQgb3IgaXMgcGFubmluZyB0aGUgdmlldy5cbiAqXG4gKiBUaGUgY2hhcnQgY2FudmFzIGhhcyBhbHJlYWR5IGJlZW4gY3JlYXRlZCBieSB0aGUgRHlncmFwaCBvYmplY3QuIFRoZVxuICogcmVuZGVyZXIgc2ltcGx5IGdldHMgYSBkcmF3aW5nIGNvbnRleHQuXG4gKlxuICogQHBhcmFtIHtEeWdyYXBofSBkeWdyYXBoIFRoZSBjaGFydCB0byB3aGljaCB0aGlzIHJlbmRlcmVyIGJlbG9uZ3MuXG4gKiBAcGFyYW0ge0hUTUxDYW52YXNFbGVtZW50fSBlbGVtZW50IFRoZSAmbHQ7Y2FudmFzJmd0OyBET00gZWxlbWVudCBvbiB3aGljaCB0byBkcmF3LlxuICogQHBhcmFtIHtDYW52YXNSZW5kZXJpbmdDb250ZXh0MkR9IGVsZW1lbnRDb250ZXh0IFRoZSBkcmF3aW5nIGNvbnRleHQuXG4gKiBAcGFyYW0ge0R5Z3JhcGhMYXlvdXR9IGxheW91dCBUaGUgY2hhcnQncyBEeWdyYXBoTGF5b3V0IG9iamVjdC5cbiAqXG4gKiBUT0RPKGRhbnZrKTogcmVtb3ZlIHRoZSBlbGVtZW50Q29udGV4dCBwcm9wZXJ0eS5cbiAqL1xudmFyIER5Z3JhcGhDYW52YXNSZW5kZXJlciA9IGZ1bmN0aW9uKGR5Z3JhcGgsIGVsZW1lbnQsIGVsZW1lbnRDb250ZXh0LCBsYXlvdXQpIHtcbiAgdGhpcy5keWdyYXBoXyA9IGR5Z3JhcGg7XG5cbiAgdGhpcy5sYXlvdXQgPSBsYXlvdXQ7XG4gIHRoaXMuZWxlbWVudCA9IGVsZW1lbnQ7XG4gIHRoaXMuZWxlbWVudENvbnRleHQgPSBlbGVtZW50Q29udGV4dDtcblxuICB0aGlzLmhlaWdodCA9IGR5Z3JhcGguaGVpZ2h0XztcbiAgdGhpcy53aWR0aCA9IGR5Z3JhcGgud2lkdGhfO1xuXG4gIC8vIC0tLSBjaGVjayB3aGV0aGVyIGV2ZXJ5dGhpbmcgaXMgb2sgYmVmb3JlIHdlIHJldHVyblxuICBpZiAoIXV0aWxzLmlzQ2FudmFzU3VwcG9ydGVkKHRoaXMuZWxlbWVudCkpIHtcbiAgICB0aHJvdyBcIkNhbnZhcyBpcyBub3Qgc3VwcG9ydGVkLlwiO1xuICB9XG5cbiAgLy8gaW50ZXJuYWwgc3RhdGVcbiAgdGhpcy5hcmVhID0gbGF5b3V0LmdldFBsb3RBcmVhKCk7XG5cbiAgLy8gU2V0IHVwIGEgY2xpcHBpbmcgYXJlYSBmb3IgdGhlIGNhbnZhcyAoYW5kIHRoZSBpbnRlcmFjdGlvbiBjYW52YXMpLlxuICAvLyBUaGlzIGVuc3VyZXMgdGhhdCB3ZSBkb24ndCBvdmVyZHJhdy5cbiAgdmFyIGN0eCA9IHRoaXMuZHlncmFwaF8uY2FudmFzX2N0eF87XG4gIGN0eC5iZWdpblBhdGgoKTtcbiAgY3R4LnJlY3QodGhpcy5hcmVhLngsIHRoaXMuYXJlYS55LCB0aGlzLmFyZWEudywgdGhpcy5hcmVhLmgpO1xuICBjdHguY2xpcCgpO1xuXG4gIGN0eCA9IHRoaXMuZHlncmFwaF8uaGlkZGVuX2N0eF87XG4gIGN0eC5iZWdpblBhdGgoKTtcbiAgY3R4LnJlY3QodGhpcy5hcmVhLngsIHRoaXMuYXJlYS55LCB0aGlzLmFyZWEudywgdGhpcy5hcmVhLmgpO1xuICBjdHguY2xpcCgpO1xufTtcblxuLyoqXG4gKiBDbGVhcnMgb3V0IGFsbCBjaGFydCBjb250ZW50IGFuZCBET00gZWxlbWVudHMuXG4gKiBUaGlzIGlzIGNhbGxlZCBpbW1lZGlhdGVseSBiZWZvcmUgcmVuZGVyKCkgb24gZXZlcnkgZnJhbWUsIGluY2x1ZGluZ1xuICogZHVyaW5nIHpvb21zIGFuZCBwYW5zLlxuICogQHByaXZhdGVcbiAqL1xuRHlncmFwaENhbnZhc1JlbmRlcmVyLnByb3RvdHlwZS5jbGVhciA9IGZ1bmN0aW9uKCkge1xuICB0aGlzLmVsZW1lbnRDb250ZXh0LmNsZWFyUmVjdCgwLCAwLCB0aGlzLndpZHRoLCB0aGlzLmhlaWdodCk7XG59O1xuXG4vKipcbiAqIFRoaXMgbWV0aG9kIGlzIHJlc3BvbnNpYmxlIGZvciBkcmF3aW5nIGV2ZXJ5dGhpbmcgb24gdGhlIGNoYXJ0LCBpbmNsdWRpbmdcbiAqIGxpbmVzLCBoaWdoL2xvdyBiYW5kcywgZmlsbHMgYW5kIGF4ZXMuXG4gKiBJdCBpcyBjYWxsZWQgaW1tZWRpYXRlbHkgYWZ0ZXIgY2xlYXIoKSBvbiBldmVyeSBmcmFtZSwgaW5jbHVkaW5nIGR1cmluZyBwYW5zXG4gKiBhbmQgem9vbXMuXG4gKiBAcHJpdmF0ZVxuICovXG5EeWdyYXBoQ2FudmFzUmVuZGVyZXIucHJvdG90eXBlLnJlbmRlciA9IGZ1bmN0aW9uKCkge1xuICAvLyBhdHRhY2hlcyBwb2ludC5jYW52YXN7eCx5fVxuICB0aGlzLl91cGRhdGVQb2ludHMoKTtcblxuICAvLyBhY3R1YWxseSBkcmF3cyB0aGUgY2hhcnQuXG4gIHRoaXMuX3JlbmRlckxpbmVDaGFydCgpO1xufTtcblxuLyoqXG4gKiBSZXR1cm5zIGEgcHJlZGljYXRlIHRvIGJlIHVzZWQgd2l0aCBhbiBpdGVyYXRvciwgd2hpY2ggd2lsbFxuICogaXRlcmF0ZSBvdmVyIHBvaW50cyBhcHByb3ByaWF0ZWx5LCBkZXBlbmRpbmcgb24gd2hldGhlclxuICogY29ubmVjdFNlcGFyYXRlZFBvaW50cyBpcyB0cnVlLiBXaGVuIGl0J3MgZmFsc2UsIHRoZSBwcmVkaWNhdGUgd2lsbFxuICogc2tpcCBvdmVyIHBvaW50cyB3aXRoIG1pc3NpbmcgeVZhbHMuXG4gKi9cbkR5Z3JhcGhDYW52YXNSZW5kZXJlci5fZ2V0SXRlcmF0b3JQcmVkaWNhdGUgPSBmdW5jdGlvbihjb25uZWN0U2VwYXJhdGVkUG9pbnRzKSB7XG4gIHJldHVybiBjb25uZWN0U2VwYXJhdGVkUG9pbnRzID9cbiAgICAgIER5Z3JhcGhDYW52YXNSZW5kZXJlci5fcHJlZGljYXRlVGhhdFNraXBzRW1wdHlQb2ludHMgOlxuICAgICAgbnVsbDtcbn07XG5cbkR5Z3JhcGhDYW52YXNSZW5kZXJlci5fcHJlZGljYXRlVGhhdFNraXBzRW1wdHlQb2ludHMgPVxuICAgIGZ1bmN0aW9uKGFycmF5LCBpZHgpIHtcbiAgcmV0dXJuIGFycmF5W2lkeF0ueXZhbCAhPT0gbnVsbDtcbn07XG5cbi8qKlxuICogRHJhd3MgYSBsaW5lIHdpdGggdGhlIHN0eWxlcyBwYXNzZWQgaW4gYW5kIGNhbGxzIGFsbCB0aGUgZHJhd1BvaW50Q2FsbGJhY2tzLlxuICogQHBhcmFtIHtPYmplY3R9IGUgVGhlIGRpY3Rpb25hcnkgcGFzc2VkIHRvIHRoZSBwbG90dGVyIGZ1bmN0aW9uLlxuICogQHByaXZhdGVcbiAqL1xuRHlncmFwaENhbnZhc1JlbmRlcmVyLl9kcmF3U3R5bGVkTGluZSA9IGZ1bmN0aW9uKGUsXG4gICAgY29sb3IsIHN0cm9rZVdpZHRoLCBzdHJva2VQYXR0ZXJuLCBkcmF3UG9pbnRzLFxuICAgIGRyYXdQb2ludENhbGxiYWNrLCBwb2ludFNpemUpIHtcbiAgdmFyIGcgPSBlLmR5Z3JhcGg7XG4gIC8vIFRPRE8oa29uaWdzYmVyZyk6IENvbXB1dGUgYXR0cmlidXRlcyBvdXRzaWRlIHRoaXMgbWV0aG9kIGNhbGwuXG4gIHZhciBzdGVwUGxvdCA9IGcuZ2V0Qm9vbGVhbk9wdGlvbihcInN0ZXBQbG90XCIsIGUuc2V0TmFtZSk7XG5cbiAgaWYgKCF1dGlscy5pc0FycmF5TGlrZShzdHJva2VQYXR0ZXJuKSkge1xuICAgIHN0cm9rZVBhdHRlcm4gPSBudWxsO1xuICB9XG5cbiAgdmFyIGRyYXdHYXBQb2ludHMgPSBnLmdldEJvb2xlYW5PcHRpb24oJ2RyYXdHYXBFZGdlUG9pbnRzJywgZS5zZXROYW1lKTtcblxuICB2YXIgcG9pbnRzID0gZS5wb2ludHM7XG4gIHZhciBzZXROYW1lID0gZS5zZXROYW1lO1xuICB2YXIgaXRlciA9IHV0aWxzLmNyZWF0ZUl0ZXJhdG9yKHBvaW50cywgMCwgcG9pbnRzLmxlbmd0aCxcbiAgICAgIER5Z3JhcGhDYW52YXNSZW5kZXJlci5fZ2V0SXRlcmF0b3JQcmVkaWNhdGUoXG4gICAgICAgICAgZy5nZXRCb29sZWFuT3B0aW9uKFwiY29ubmVjdFNlcGFyYXRlZFBvaW50c1wiLCBzZXROYW1lKSkpO1xuXG4gIHZhciBzdHJva2luZyA9IHN0cm9rZVBhdHRlcm4gJiYgKHN0cm9rZVBhdHRlcm4ubGVuZ3RoID49IDIpO1xuXG4gIHZhciBjdHggPSBlLmRyYXdpbmdDb250ZXh0O1xuICBjdHguc2F2ZSgpO1xuICBpZiAoc3Ryb2tpbmcpIHtcbiAgICBpZiAoY3R4LnNldExpbmVEYXNoKSBjdHguc2V0TGluZURhc2goc3Ryb2tlUGF0dGVybik7XG4gIH1cbiAgdmFyIGdhcFRocmVzaG9sZCA9IGUuZHlncmFwaC5nZXRPcHRpb24oXCJnYXBUaHJlc2hvbGRcIiwgZS5zZXROYW1lKTtcbiAgdmFyIGdhcFRocmVzaG9sZEZ1bmMgPSBmdW5jdGlvbihwcmV2UG9pbnQsIGN1clBvaW50KSB7XG4gICAgcmV0dXJuIGZhbHNlO1xuICB9O1xuXG4gIGlmIChnYXBUaHJlc2hvbGQpIHtcbiAgICBpZiAodHlwZW9mIGdhcFRocmVzaG9sZCA9PSAnbnVtYmVyJykge1xuICAgICAgZ2FwVGhyZXNob2xkRnVuYyA9IGZ1bmN0aW9uIChwcmV2UG9pbnQsIHBvaW50KSB7XG4gICAgICAgIHJldHVybiAocG9pbnQueHZhbCAtIHByZXZQb2ludC54dmFsKSA+PSBnYXBUaHJlc2hvbGQ7XG4gICAgICB9O1xuICAgIH0gZWxzZSBpZiAodHlwZW9mIGdhcFRocmVzaG9sZCA9PSAnZnVuY3Rpb24nKSB7XG4gICAgICBnYXBUaHJlc2hvbGRGdW5jID0gZ2FwVGhyZXNob2xkO1xuICAgIH1cbiAgfVxuXG4gIHZhciBwb2ludHNPbkxpbmUgPSBEeWdyYXBoQ2FudmFzUmVuZGVyZXIuX2RyYXdTZXJpZXMoXG4gICAgICBlLCBpdGVyLCBzdHJva2VXaWR0aCwgcG9pbnRTaXplLCBkcmF3UG9pbnRzLCBkcmF3R2FwUG9pbnRzLCBzdGVwUGxvdCwgY29sb3IsIGdhcFRocmVzaG9sZEZ1bmMpO1xuICBEeWdyYXBoQ2FudmFzUmVuZGVyZXIuX2RyYXdQb2ludHNPbkxpbmUoXG4gICAgICBlLCBwb2ludHNPbkxpbmUsIGRyYXdQb2ludENhbGxiYWNrLCBjb2xvciwgcG9pbnRTaXplKTtcblxuICBpZiAoc3Ryb2tpbmcpIHtcbiAgICBpZiAoY3R4LnNldExpbmVEYXNoKSBjdHguc2V0TGluZURhc2goW10pO1xuICB9XG5cbiAgY3R4LnJlc3RvcmUoKTtcbn07XG5cbi8qKlxuICogVGhpcyBkb2VzIHRoZSBhY3R1YWwgZHJhd2luZyBvZiBsaW5lcyBvbiB0aGUgY2FudmFzLCBmb3IganVzdCBvbmUgc2VyaWVzLlxuICogUmV0dXJucyBhIGxpc3Qgb2YgW2NhbnZhc3gsIGNhbnZhc3ldIHBhaXJzIGZvciBwb2ludHMgZm9yIHdoaWNoIGFcbiAqIGRyYXdQb2ludENhbGxiYWNrIHNob3VsZCBiZSBmaXJlZC4gIFRoZXNlIGluY2x1ZGUgaXNvbGF0ZWQgcG9pbnRzLCBvciBhbGxcbiAqIHBvaW50cyBpZiBkcmF3UG9pbnRzPXRydWUuXG4gKiBAcGFyYW0ge09iamVjdH0gZSBUaGUgZGljdGlvbmFyeSBwYXNzZWQgdG8gdGhlIHBsb3R0ZXIgZnVuY3Rpb24uXG4gKiBAcHJpdmF0ZVxuICovXG5EeWdyYXBoQ2FudmFzUmVuZGVyZXIuX2RyYXdTZXJpZXMgPSBmdW5jdGlvbihlLFxuICAgIGl0ZXIsIHN0cm9rZVdpZHRoLCBwb2ludFNpemUsIGRyYXdQb2ludHMsIGRyYXdHYXBQb2ludHMsIHN0ZXBQbG90LCBjb2xvciwgZ2FwVGhyZXNob2xkRnVuYykge1xuXG4gIHZhciBwcmV2UG9pbnQgPSBudWxsO1xuICB2YXIgcHJldkNhbnZhc1ggPSBudWxsO1xuICB2YXIgcHJldkNhbnZhc1kgPSBudWxsO1xuICB2YXIgbmV4dENhbnZhc1kgPSBudWxsO1xuICB2YXIgaXNJc29sYXRlZDsgLy8gdHJ1ZSBpZiB0aGlzIHBvaW50IGlzIGlzb2xhdGVkIChubyBsaW5lIHNlZ21lbnRzKVxuICB2YXIgcG9pbnQ7IC8vIHRoZSBwb2ludCBiZWluZyBwcm9jZXNzZWQgaW4gdGhlIHdoaWxlIGxvb3BcbiAgdmFyIHBvaW50c09uTGluZSA9IFtdOyAvLyBBcnJheSBvZiBbY2FudmFzeCwgY2FudmFzeV0gcGFpcnMuXG4gIHZhciBmaXJzdCA9IHRydWU7IC8vIHRoZSBmaXJzdCBjeWNsZSB0aHJvdWdoIHRoZSB3aGlsZSBsb29wXG5cbiAgdmFyIGN0eCA9IGUuZHJhd2luZ0NvbnRleHQ7XG4gIGN0eC5iZWdpblBhdGgoKTtcbiAgY3R4LnN0cm9rZVN0eWxlID0gY29sb3I7XG4gIGN0eC5saW5lV2lkdGggPSBzdHJva2VXaWR0aDtcblxuICAvLyBOT1RFOiB3ZSBicmVhayB0aGUgaXRlcmF0b3IncyBlbmNhcHN1bGF0aW9uIGhlcmUgZm9yIGFib3V0IGEgMjUlIHNwZWVkdXAuXG4gIHZhciBhcnIgPSBpdGVyLmFycmF5XztcbiAgdmFyIGxpbWl0ID0gaXRlci5lbmRfO1xuICB2YXIgcHJlZGljYXRlID0gaXRlci5wcmVkaWNhdGVfO1xuXG4gIGZvciAodmFyIGkgPSBpdGVyLnN0YXJ0XzsgaSA8IGxpbWl0OyBpKyspIHtcbiAgICBwb2ludCA9IGFycltpXTtcbiAgICBpZiAocHJlZGljYXRlKSB7XG4gICAgICB3aGlsZSAoaSA8IGxpbWl0ICYmICFwcmVkaWNhdGUoYXJyLCBpKSkge1xuICAgICAgICBpKys7XG4gICAgICB9XG4gICAgICBpZiAoaSA9PSBsaW1pdCkgYnJlYWs7XG4gICAgICBwb2ludCA9IGFycltpXTtcbiAgICB9XG5cbiAgICAvLyBGSVhNRTogVGhlICdjYW52YXN5ICE9IGNhbnZhc3knIHRlc3QgaGVyZSBjYXRjaGVzIE5hTiB2YWx1ZXMgYnV0IHRoZSB0ZXN0XG4gICAgLy8gZG9lc24ndCBjYXRjaCBJbmZpbml0eSB2YWx1ZXMuIENvdWxkIGNoYW5nZSB0aGlzIHRvXG4gICAgLy8gIWlzRmluaXRlKHBvaW50LmNhbnZhc3kpLCBidXQgSSBhc3N1bWUgaXQgYXZvaWRzIGlzTmFOIGZvciBwZXJmb3JtYW5jZT9cbiAgICBpZiAocG9pbnQuY2FudmFzeSA9PT0gbnVsbCB8fCBwb2ludC5jYW52YXN5ICE9IHBvaW50LmNhbnZhc3kpIHtcbiAgICAgIGlmIChzdGVwUGxvdCAmJiBwcmV2Q2FudmFzWCAhPT0gbnVsbCkge1xuICAgICAgICAvLyBEcmF3IGEgaG9yaXpvbnRhbCBsaW5lIHRvIHRoZSBzdGFydCBvZiB0aGUgbWlzc2luZyBkYXRhXG4gICAgICAgIGN0eC5tb3ZlVG8ocHJldkNhbnZhc1gsIHByZXZDYW52YXNZKTtcbiAgICAgICAgY3R4LmxpbmVUbyhwb2ludC5jYW52YXN4LCBwcmV2Q2FudmFzWSk7XG4gICAgICB9XG4gICAgICBwcmV2Q2FudmFzWCA9IHByZXZDYW52YXNZID0gbnVsbDtcbiAgICB9IGVsc2Uge1xuICAgICAgaXNJc29sYXRlZCA9IGZhbHNlO1xuICAgICAgaWYgKGRyYXdHYXBQb2ludHMgfHwgcHJldkNhbnZhc1ggPT09IG51bGwpIHtcbiAgICAgICAgaXRlci5uZXh0SWR4XyA9IGk7XG4gICAgICAgIGl0ZXIubmV4dCgpO1xuICAgICAgICBuZXh0Q2FudmFzWSA9IGl0ZXIuaGFzTmV4dCA/IGl0ZXIucGVlay5jYW52YXN5IDogbnVsbDtcblxuICAgICAgICB2YXIgaXNOZXh0Q2FudmFzWU51bGxPck5hTiA9IG5leHRDYW52YXNZID09PSBudWxsIHx8XG4gICAgICAgICAgICBuZXh0Q2FudmFzWSAhPSBuZXh0Q2FudmFzWTtcbiAgICAgICAgaXNJc29sYXRlZCA9IChwcmV2Q2FudmFzWCA9PT0gbnVsbCAmJiBpc05leHRDYW52YXNZTnVsbE9yTmFOKTtcbiAgICAgICAgaWYgKGRyYXdHYXBQb2ludHMpIHtcbiAgICAgICAgICAvLyBBbHNvIGNvbnNpZGVyIGEgcG9pbnQgdG8gYmUgXCJpc29sYXRlZFwiIGlmIGl0J3MgYWRqYWNlbnQgdG8gYVxuICAgICAgICAgIC8vIG51bGwgcG9pbnQsIGV4Y2x1ZGluZyB0aGUgZ3JhcGggZWRnZXMuXG4gICAgICAgICAgaWYgKCghZmlyc3QgJiYgcHJldkNhbnZhc1ggPT09IG51bGwpIHx8XG4gICAgICAgICAgICAgIChpdGVyLmhhc05leHQgJiYgaXNOZXh0Q2FudmFzWU51bGxPck5hTikpIHtcbiAgICAgICAgICAgIGlzSXNvbGF0ZWQgPSB0cnVlO1xuICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgfVxuXG4gICAgICBpZiAocHJldkNhbnZhc1ggIT09IG51bGwpIHtcbiAgICAgICAgaWYgKHN0cm9rZVdpZHRoKSB7XG4gICAgICAgICAgaWYgKHN0ZXBQbG90KSB7XG4gICAgICAgICAgICBjdHgubW92ZVRvKHByZXZDYW52YXNYLCBwcmV2Q2FudmFzWSk7XG4gICAgICAgICAgICBjdHgubGluZVRvKHBvaW50LmNhbnZhc3gsIHByZXZDYW52YXNZKTtcbiAgICAgICAgICB9XG5cbiAgICAgICAgICBpZiAoZ2FwVGhyZXNob2xkRnVuYyhwcmV2UG9pbnQsIHBvaW50KSkge1xuICAgICAgICAgICAgY3R4Lm1vdmVUbyhwb2ludC5jYW52YXN4LCBwb2ludC5jYW52YXN5KTtcbiAgICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgY3R4LmxpbmVUbyhwb2ludC5jYW52YXN4LCBwb2ludC5jYW52YXN5KTtcbiAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgIH0gZWxzZSB7XG4gICAgICAgIGN0eC5tb3ZlVG8ocG9pbnQuY2FudmFzeCwgcG9pbnQuY2FudmFzeSk7XG4gICAgICB9XG4gICAgICBpZiAoZHJhd1BvaW50cyB8fCBpc0lzb2xhdGVkKSB7XG4gICAgICAgIHBvaW50c09uTGluZS5wdXNoKFtwb2ludC5jYW52YXN4LCBwb2ludC5jYW52YXN5LCBwb2ludC5pZHhdKTtcbiAgICAgIH1cbiAgICAgIHByZXZQb2ludCA9IHBvaW50XG4gICAgICBwcmV2Q2FudmFzWCA9IHBvaW50LmNhbnZhc3g7XG4gICAgICBwcmV2Q2FudmFzWSA9IHBvaW50LmNhbnZhc3k7XG4gICAgfVxuICAgIGZpcnN0ID0gZmFsc2U7XG4gIH1cbiAgY3R4LnN0cm9rZSgpO1xuICByZXR1cm4gcG9pbnRzT25MaW5lO1xufTtcblxuLyoqXG4gKiBUaGlzIGZpcmVzIHRoZSBkcmF3UG9pbnRDYWxsYmFjayBmdW5jdGlvbnMsIHdoaWNoIGRyYXcgZG90cyBvbiB0aGUgcG9pbnRzIGJ5XG4gKiBkZWZhdWx0LiBUaGlzIGdldHMgdXNlZCB3aGVuIHRoZSBcImRyYXdQb2ludHNcIiBvcHRpb24gaXMgc2V0LCBvciB3aGVuIHRoZXJlXG4gKiBhcmUgaXNvbGF0ZWQgcG9pbnRzLlxuICogQHBhcmFtIHtPYmplY3R9IGUgVGhlIGRpY3Rpb25hcnkgcGFzc2VkIHRvIHRoZSBwbG90dGVyIGZ1bmN0aW9uLlxuICogQHByaXZhdGVcbiAqL1xuRHlncmFwaENhbnZhc1JlbmRlcmVyLl9kcmF3UG9pbnRzT25MaW5lID0gZnVuY3Rpb24oXG4gICAgZSwgcG9pbnRzT25MaW5lLCBkcmF3UG9pbnRDYWxsYmFjaywgY29sb3IsIHBvaW50U2l6ZSkge1xuICB2YXIgY3R4ID0gZS5kcmF3aW5nQ29udGV4dDtcbiAgZm9yICh2YXIgaWR4ID0gMDsgaWR4IDwgcG9pbnRzT25MaW5lLmxlbmd0aDsgaWR4KyspIHtcbiAgICB2YXIgY2IgPSBwb2ludHNPbkxpbmVbaWR4XTtcbiAgICBjdHguc2F2ZSgpO1xuICAgIGRyYXdQb2ludENhbGxiYWNrLmNhbGwoZS5keWdyYXBoLFxuICAgICAgICBlLmR5Z3JhcGgsIGUuc2V0TmFtZSwgY3R4LCBjYlswXSwgY2JbMV0sIGNvbG9yLCBwb2ludFNpemUsIGNiWzJdKTtcbiAgICBjdHgucmVzdG9yZSgpO1xuICB9XG59O1xuXG4vKipcbiAqIEF0dGFjaGVzIGNhbnZhcyBjb29yZGluYXRlcyB0byB0aGUgcG9pbnRzIGFycmF5LlxuICogQHByaXZhdGVcbiAqL1xuRHlncmFwaENhbnZhc1JlbmRlcmVyLnByb3RvdHlwZS5fdXBkYXRlUG9pbnRzID0gZnVuY3Rpb24oKSB7XG4gIC8vIFVwZGF0ZSBQb2ludHNcbiAgLy8gVE9ETyhkYW52ayk6IGhlcmVcbiAgLy9cbiAgLy8gVE9ETyhiaHMpOiB0aGlzIGxvb3AgaXMgYSBob3Qtc3BvdCBmb3IgaGlnaC1wb2ludC1jb3VudCBjaGFydHMuIFRoZXNlXG4gIC8vIHRyYW5zZm9ybWF0aW9ucyBjYW4gYmUgcHVzaGVkIGludG8gdGhlIGNhbnZhcyB2aWEgbGluZWFyIHRyYW5zZm9ybWF0aW9uXG4gIC8vIG1hdHJpY2VzLlxuICAvLyBOT1RFKGRhbnZrKTogdGhpcyBpcyB0cmlja2llciB0aGFuIGl0IHNvdW5kcyBhdCBmaXJzdC4gVGhlIHRyYW5zZm9ybWF0aW9uXG4gIC8vIG5lZWRzIHRvIGJlIGRvbmUgYmVmb3JlIHRoZSAubW92ZVRvKCkgYW5kIC5saW5lVG8oKSBjYWxscywgYnV0IG11c3QgYmVcbiAgLy8gdW5kb25lIGJlZm9yZSB0aGUgLnN0cm9rZSgpIGNhbGwgdG8gZW5zdXJlIHRoYXQgdGhlIHN0cm9rZSB3aWR0aCBpc1xuICAvLyB1bmFmZmVjdGVkLiAgQW4gYWx0ZXJuYXRpdmUgaXMgdG8gcmVkdWNlIHRoZSBzdHJva2Ugd2lkdGggaW4gdGhlXG4gIC8vIHRyYW5zZm9ybWVkIGNvb3JkaW5hdGUgc3BhY2UsIGJ1dCB5b3UgY2FuJ3Qgc3BlY2lmeSBkaWZmZXJlbnQgdmFsdWVzIGZvclxuICAvLyBlYWNoIGRpbWVuc2lvbiAoYXMgeW91IGNhbiB3aXRoIC5zY2FsZSgpKS4gVGhlIHNwZWVkdXAgaGVyZSBpcyB+MTIlLlxuICB2YXIgc2V0cyA9IHRoaXMubGF5b3V0LnBvaW50cztcbiAgZm9yICh2YXIgaSA9IHNldHMubGVuZ3RoOyBpLS07KSB7XG4gICAgdmFyIHBvaW50cyA9IHNldHNbaV07XG4gICAgZm9yICh2YXIgaiA9IHBvaW50cy5sZW5ndGg7IGotLTspIHtcbiAgICAgIHZhciBwb2ludCA9IHBvaW50c1tqXTtcbiAgICAgIHBvaW50LmNhbnZhc3ggPSB0aGlzLmFyZWEudyAqIHBvaW50LnggKyB0aGlzLmFyZWEueDtcbiAgICAgIHBvaW50LmNhbnZhc3kgPSB0aGlzLmFyZWEuaCAqIHBvaW50LnkgKyB0aGlzLmFyZWEueTtcbiAgICB9XG4gIH1cbn07XG5cbi8qKlxuICogQWRkIGNhbnZhcyBBY3R1YWxseSBkcmF3IHRoZSBsaW5lcyBjaGFydCwgaW5jbHVkaW5nIGhpZ2gvbG93IGJhbmRzLlxuICpcbiAqIFRoaXMgZnVuY3Rpb24gY2FuIG9ubHkgYmUgY2FsbGVkIGlmIER5Z3JhcGhMYXlvdXQncyBwb2ludHMgYXJyYXkgaGFzIGJlZW5cbiAqIHVwZGF0ZWQgd2l0aCBjYW52YXN7eCx5fSBhdHRyaWJ1dGVzLCBpLmUuIGJ5XG4gKiBEeWdyYXBoQ2FudmFzUmVuZGVyZXIuX3VwZGF0ZVBvaW50cy5cbiAqXG4gKiBAcGFyYW0ge3N0cmluZz19IG9wdF9zZXJpZXNOYW1lIHdoZW4gc3BlY2lmaWVkLCBvbmx5IHRoYXQgc2VyaWVzIHdpbGxcbiAqICAgICBiZSBkcmF3bi4gKFRoaXMgaXMgdXNlZCBmb3IgZXhwZWRpdGVkIHJlZHJhd2luZyB3aXRoIGhpZ2hsaWdodFNlcmllc09wdHMpXG4gKiBAcGFyYW0ge0NhbnZhc1JlbmRlcmluZ0NvbnRleHQyRH0gb3B0X2N0eCB3aGVuIHNwZWNpZmllZCwgdGhlIGRyYXdpbmdcbiAqICAgICBjb250ZXh0LiAgSG93ZXZlciwgbGluZXMgYXJlIHR5cGljYWxseSBkcmF3biBvbiB0aGUgb2JqZWN0J3NcbiAqICAgICBlbGVtZW50Q29udGV4dC5cbiAqIEBwcml2YXRlXG4gKi9cbkR5Z3JhcGhDYW52YXNSZW5kZXJlci5wcm90b3R5cGUuX3JlbmRlckxpbmVDaGFydCA9IGZ1bmN0aW9uKG9wdF9zZXJpZXNOYW1lLCBvcHRfY3R4KSB7XG4gIHZhciBjdHggPSBvcHRfY3R4IHx8IHRoaXMuZWxlbWVudENvbnRleHQ7XG4gIHZhciBpO1xuXG4gIHZhciBzZXRzID0gdGhpcy5sYXlvdXQucG9pbnRzO1xuICB2YXIgc2V0TmFtZXMgPSB0aGlzLmxheW91dC5zZXROYW1lcztcbiAgdmFyIHNldE5hbWU7XG5cbiAgdGhpcy5jb2xvcnMgPSB0aGlzLmR5Z3JhcGhfLmNvbG9yc01hcF87XG5cbiAgLy8gRGV0ZXJtaW5lIHdoaWNoIHNlcmllcyBoYXZlIHNwZWNpYWxpemVkIHBsb3R0ZXJzLlxuICB2YXIgcGxvdHRlcl9hdHRyID0gdGhpcy5keWdyYXBoXy5nZXRPcHRpb24oXCJwbG90dGVyXCIpO1xuICB2YXIgcGxvdHRlcnMgPSBwbG90dGVyX2F0dHI7XG4gIGlmICghdXRpbHMuaXNBcnJheUxpa2UocGxvdHRlcnMpKSB7XG4gICAgcGxvdHRlcnMgPSBbcGxvdHRlcnNdO1xuICB9XG5cbiAgdmFyIHNldFBsb3R0ZXJzID0ge307ICAvLyBzZXJpZXMgbmFtZSAtPiBwbG90dGVyIGZuLlxuICBmb3IgKGkgPSAwOyBpIDwgc2V0TmFtZXMubGVuZ3RoOyBpKyspIHtcbiAgICBzZXROYW1lID0gc2V0TmFtZXNbaV07XG4gICAgdmFyIHNldFBsb3R0ZXIgPSB0aGlzLmR5Z3JhcGhfLmdldE9wdGlvbihcInBsb3R0ZXJcIiwgc2V0TmFtZSk7XG4gICAgaWYgKHNldFBsb3R0ZXIgPT0gcGxvdHRlcl9hdHRyKSBjb250aW51ZTsgIC8vIG5vdCBzcGVjaWFsaXplZC5cblxuICAgIHNldFBsb3R0ZXJzW3NldE5hbWVdID0gc2V0UGxvdHRlcjtcbiAgfVxuXG4gIGZvciAoaSA9IDA7IGkgPCBwbG90dGVycy5sZW5ndGg7IGkrKykge1xuICAgIHZhciBwbG90dGVyID0gcGxvdHRlcnNbaV07XG4gICAgdmFyIGlzX2xhc3QgPSAoaSA9PSBwbG90dGVycy5sZW5ndGggLSAxKTtcblxuICAgIGZvciAodmFyIGogPSAwOyBqIDwgc2V0cy5sZW5ndGg7IGorKykge1xuICAgICAgc2V0TmFtZSA9IHNldE5hbWVzW2pdO1xuICAgICAgaWYgKG9wdF9zZXJpZXNOYW1lICYmIHNldE5