+ * Point structure.
+ *
+ * xval_* and yval_* are the original unscaled data values,
+ * while x_* and y_* are scaled to the range (0.0-1.0) for plotting.
+ * yval_stacked is the cumulative Y value used for stacking graphs,
+ * and bottom/top/minus/plus are used for error bar graphs.
+ *
+ * @typedef {{
+ * idx: number,
+ * name: string,
+ * x: ?number,
+ * xval: ?number,
+ * y_bottom: ?number,
+ * y: ?number,
+ * y_stacked: ?number,
+ * y_top: ?number,
+ * yval_minus: ?number,
+ * yval: ?number,
+ * yval_plus: ?number,
+ * yval_stacked
+ * }}
+ */
+Dygraph.PointType = undefined;
+
+// TODO(bhs): these loops are a hot-spot for high-point-count charts. In fact,
+// on chrome+linux, they are 6 times more expensive than iterating through the
+// points and drawing the lines. The brunt of the cost comes from allocating
+// the |point| structures.
+/**
+ * Converts a series to a Point array.
+ *
+ * @param {Array.<Array.<(?number|Array<?number>)>} series Array where
+ * series[row] = [x,y] or [x, [y, err]] or [x, [y, yplus, yminus]].
+ * @param {boolean} bars True if error bars or custom bars are being drawn.
+ * @param {string} setName Name of the series.
+ * @param {number} boundaryIdStart Index offset of the first point, equal to
+ * the number of skipped points left of the date window minimum (if any).
+ * @return {Array.<Dygraph.PointType>} List of points for this series.
+ */
+Dygraph.seriesToPoints_ = function(series, bars, setName, boundaryIdStart) {
+ var points = [];
+ for (var i = 0; i < series.length; ++i) {
+ var item = series[i];
+ var yraw = bars ? item[1][0] : item[1];
+ var yval = yraw === null ? null : DygraphLayout.parseFloat_(yraw);
+ var point = {
+ x: NaN,
+ y: NaN,
+ xval: DygraphLayout.parseFloat_(item[0]),
+ yval: yval,
+ name: setName, // TODO(danvk): is this really necessary?
+ idx: i + boundaryIdStart
+ };
+
+ if (bars) {
+ point.y_top = NaN;
+ point.y_bottom = NaN;
+ point.yval_minus = DygraphLayout.parseFloat_(item[1][1]);
+ point.yval_plus = DygraphLayout.parseFloat_(item[1][2]);
+ }
+ points.push(point);
+ }
+ return points;
+};
+
+
+/**
+ * Calculates point stacking for stackedGraph=true.
+ *
+ * For stacking purposes, interpolate or extend neighboring data across
+ * NaN values based on stackedGraphNaNFill settings. This is for display
+ * only, the underlying data value as shown in the legend remains NaN.
+ *
+ * @param {Array.<Dygraph.PointType>} points Point array for a single series.
+ * Updates each Point's yval_stacked property.
+ * @param {Array.<number>} cumulativeYval Accumulated top-of-graph stacked Y
+ * values for the series seen so far. Index is the row number. Updated
+ * based on the current series's values.
+ * @param {Array.<number>} seriesExtremes Min and max values, updated
+ * to reflect the stacked values.
+ * @param {string} fillMethod Interpolation method, one of 'all', 'inside', or
+ * 'none'.
+ */
+Dygraph.stackPoints_ = function(
+ points, cumulativeYval, seriesExtremes, fillMethod) {
+ var lastXval = null;
+ var prevPoint = null;
+ var nextPoint = null;
+ var nextPointIdx = -1;
+
+ // Find the next stackable point starting from the given index.
+ var updateNextPoint = function(idx) {
+ // If we've previously found a non-NaN point and haven't gone past it yet,
+ // just use that.
+ if (nextPointIdx >= idx) return;
+
+ // We haven't found a non-NaN point yet or have moved past it,
+ // look towards the right to find a non-NaN point.
+ for (var j = idx; j < points.length; ++j) {
+ // Clear out a previously-found point (if any) since it's no longer
+ // valid, we shouldn't use it for interpolation anymore.
+ nextPoint = null;
+ if (!isNaN(points[j].yval) && points[j].yval !== null) {
+ nextPointIdx = j;
+ nextPoint = points[j];
+ break;
+ }
+ }
+ };
+
+ for (var i = 0; i < points.length; ++i) {
+ var point = points[i];
+ var xval = point.xval;
+ if (cumulativeYval[xval] === undefined) {
+ cumulativeYval[xval] = 0;
+ }
+
+ var actualYval = point.yval;
+ if (isNaN(actualYval) || actualYval === null) {
+ // Interpolate/extend for stacking purposes if possible.
+ updateNextPoint(i);
+ if (prevPoint && nextPoint && fillMethod != 'none') {
+ // Use linear interpolation between prevPoint and nextPoint.
+ actualYval = prevPoint.yval + (nextPoint.yval - prevPoint.yval) *
+ ((xval - prevPoint.xval) / (nextPoint.xval - prevPoint.xval));
+ } else if (prevPoint && fillMethod == 'all') {
+ actualYval = prevPoint.yval;
+ } else if (nextPoint && fillMethod == 'all') {
+ actualYval = nextPoint.yval;
+ } else {
+ actualYval = 0;
+ }
+ } else {
+ prevPoint = point;
+ }
+
+ var stackedYval = cumulativeYval[xval];
+ if (lastXval != xval) {
+ // If an x-value is repeated, we ignore the duplicates.
+ stackedYval += actualYval;
+ cumulativeYval[xval] = stackedYval;
+ }
+ lastXval = xval;
+
+ point.yval_stacked = stackedYval;
+
+ if (stackedYval > seriesExtremes[1]) {
+ seriesExtremes[1] = stackedYval;
+ }
+ if (stackedYval < seriesExtremes[0]) {
+ seriesExtremes[0] = stackedYval;
+ }
+ }
+};
+
+
+/**