*/
/*jshint globalstrict: true */
-/*global DygraphLayout:false, DygraphCanvasRenderer:false, DygraphOptions:false, G_vmlCanvasManager:false */
+/*global DygraphLayout:false, DygraphCanvasRenderer:false, DygraphOptions:false, G_vmlCanvasManager:false,ActiveXObject:false */
"use strict";
/**
* options, see http://dygraphs.com/options.html.
*/
var Dygraph = function(div, data, opts, opt_fourth_param) {
+ // These have to go above the "Hack for IE" in __init__ since .ready() can be
+ // called as soon as the constructor returns. Once support for OldIE is
+ // dropped, this can go down with the rest of the initializers.
+ this.is_initial_draw_ = true;
+ this.readyFns_ = [];
+
if (opt_fourth_param !== undefined) {
// Old versions of dygraphs took in the series labels as a constructor
// parameter. This doesn't make sense anymore, but it's easy to continue
};
Dygraph.NAME = "Dygraph";
-Dygraph.VERSION = "1.2";
+Dygraph.VERSION = "1.0.1";
Dygraph.__repr__ = function() {
return "[" + this.NAME + " " + this.VERSION + "]";
};
connectSeparatedPoints: false,
stackedGraph: false,
+ stackedGraphNaNFill: 'all',
hideOverlayOnMouseOut: true,
// TODO(danvk): support 'onmouseover' and 'never', and remove synonyms.
this.fractions_ = attrs.fractions || false;
this.dateWindow_ = attrs.dateWindow || null;
- this.is_initial_draw_ = true;
this.annotations_ = [];
// Zoomed indicators - These indicate when the graph has been zoomed and on what axis.
div.style.width = Dygraph.DEFAULT_WIDTH + "px";
}
}
- // these will be zero if the dygraph's div is hidden.
- this.width_ = div.clientWidth;
- this.height_ = div.clientHeight;
+ // These will be zero if the dygraph's div is hidden. In that case,
+ // use the user-specified attributes if present. If not, use zero
+ // and assume the user will call resize to fix things later.
+ this.width_ = div.clientWidth || attrs.width || 0;
+ this.height_ = div.clientHeight || attrs.height || 0;
// TODO(danvk): set fillGraph to be part of attrs_ here, not user_attrs_.
if (attrs.stackedGraph) {
var yRange = this.yAxisRange(axis);
if (typeof(axis) == "undefined") axis = 0;
- if (!this.axes_[axis].logscale) {
+ if (!this.attributes_.getForAxis("logscale", axis)) {
return yRange[0] + (area.y + area.h - y) / area.h * (yRange[1] - yRange[0]);
} else {
// Computing the inverse of toDomCoord.
this.canvas_ = Dygraph.createCanvas();
this.canvas_.style.position = "absolute";
+ // ... and for static parts of the chart.
+ this.hidden_ = this.createPlotKitCanvas_(this.canvas_);
+
this.resizeElements_();
this.canvas_ctx_ = Dygraph.getContext(this.canvas_);
-
- // ... and for static parts of the chart.
- this.hidden_ = this.createPlotKitCanvas_(this.canvas_);
this.hidden_ctx_ = Dygraph.getContext(this.hidden_);
// The interactive parts of the graph are drawn on top of the chart.
this.canvas_.height = this.height_;
this.canvas_.style.width = this.width_ + "px"; // for IE
this.canvas_.style.height = this.height_ + "px"; // for IE
+ this.hidden_.width = this.width_;
+ this.hidden_.height = this.height_;
+ this.hidden_.style.width = this.width_ + "px"; // for IE
+ this.hidden_.style.height = this.height_ + "px"; // for IE
};
/**
if (prevDirection == Dygraph.HORIZONTAL) {
ctx.clearRect(Math.min(startX, prevEndX), this.layout_.getPlotArea().y,
Math.abs(startX - prevEndX), this.layout_.getPlotArea().h);
- } else if (prevDirection == Dygraph.VERTICAL){
+ } else if (prevDirection == Dygraph.VERTICAL) {
ctx.clearRect(this.layout_.getPlotArea().x, Math.min(startY, prevEndY),
this.layout_.getPlotArea().w, Math.abs(startY - prevEndY));
}
oldValueRanges = this.yAxisRanges();
// TODO(danvk): this is pretty inefficient
var packed = this.gatherDatasets_(this.rolledSeries_, null);
- var extremes = packed[1];
+ var extremes = packed.extremes;
// this has the side-effect of modifying this.axes_.
// this doesn't make much sense in this context, but it's convenient (we
*/
Dygraph.prototype.findClosestRow = function(domX) {
var minDistX = Infinity;
- var pointIdx = -1, setIdx = -1;
+ var closestRow = -1;
var sets = this.layout_.points;
for (var i = 0; i < sets.length; i++) {
var points = sets[i];
var dist = Math.abs(point.canvasx - domX);
if (dist < minDistX) {
minDistX = dist;
- setIdx = i;
- pointIdx = j;
+ closestRow = point.idx;
}
}
}
- // TODO(danvk): remove this function; it's trivial and has only one use.
- return this.idxToRow_(setIdx, pointIdx);
+ return closestRow;
};
/**
*/
Dygraph.prototype.findClosestPoint = function(domX, domY) {
var minDist = Infinity;
- var idx = -1;
- var dist, dx, dy, point, closestPoint, closestSeries;
- for ( var setIdx = this.layout_.datasets.length - 1 ; setIdx >= 0 ; --setIdx ) {
+ var dist, dx, dy, point, closestPoint, closestSeries, closestRow;
+ for ( var setIdx = this.layout_.points.length - 1 ; setIdx >= 0 ; --setIdx ) {
var points = this.layout_.points[setIdx];
for (var i = 0; i < points.length; ++i) {
- var point = points[i];
+ point = points[i];
if (!Dygraph.isValidPoint(point)) continue;
dx = point.canvasx - domX;
dy = point.canvasy - domY;
minDist = dist;
closestPoint = point;
closestSeries = setIdx;
- idx = i;
+ closestRow = point.idx;
}
}
}
var name = this.layout_.setNames[closestSeries];
return {
- row: idx + this.getLeftBoundary_(),
+ row: closestRow,
seriesName: name,
point: closestPoint
};
*/
Dygraph.prototype.findStackedPoint = function(domX, domY) {
var row = this.findClosestRow(domX);
- var boundary = this.getLeftBoundary_();
- var rowIdx = row - boundary;
var closestPoint, closestSeries;
- for (var setIdx = 0; setIdx < this.layout_.datasets.length; ++setIdx) {
+ for (var setIdx = 0; setIdx < this.layout_.points.length; ++setIdx) {
+ var boundary = this.getLeftBoundary_(setIdx);
+ var rowIdx = row - boundary;
var points = this.layout_.points[setIdx];
if (rowIdx >= points.length) continue;
var p1 = points[rowIdx];
callback(event,
this.lastx_,
this.selPoints_,
- this.lastRow_ + this.getLeftBoundary_(),
+ this.lastRow_,
this.highlightSet_);
}
};
/**
- * Fetch left offset from first defined boundaryIds record (see bug #236).
+ * Fetch left offset from the specified set index or if not passed, the
+ * first defined boundaryIds record (see bug #236).
* @private
*/
-Dygraph.prototype.getLeftBoundary_ = function() {
- for (var i = 0; i < this.boundaryIds_.length; i++) {
- if (this.boundaryIds_[i] !== undefined) {
- return this.boundaryIds_[i][0];
+Dygraph.prototype.getLeftBoundary_ = function(setIdx) {
+ if (this.boundaryIds_[setIdx]) {
+ return this.boundaryIds_[setIdx][0];
+ } else {
+ for (var i = 0; i < this.boundaryIds_.length; i++) {
+ if (this.boundaryIds_[i] !== undefined) {
+ return this.boundaryIds_[i][0];
+ }
}
+ return 0;
}
- return 0;
-};
-
-/**
- * Transforms layout_.points index into data row number.
- * @param int layout_.points index
- * @return int row number, or -1 if none could be found.
- * @private
- */
-Dygraph.prototype.idxToRow_ = function(setIdx, rowIdx) {
- if (rowIdx < 0) return -1;
-
- var boundary = this.getLeftBoundary_();
- return boundary + rowIdx;
- // for (var setIdx = 0; setIdx < this.layout_.datasets.length; ++setIdx) {
- // var set = this.layout_.datasets[setIdx];
- // if (idx < set.length) {
- // return boundary + idx;
- // }
- // idx -= set.length;
- // }
- // return -1;
};
Dygraph.prototype.animateSelection_ = function(direction) {
// Extract the points we've selected
this.selPoints_ = [];
- if (row !== false) {
- row -= this.getLeftBoundary_();
- }
-
var changed = false;
if (row !== false && row >= 0) {
if (row != this.lastRow_) changed = true;
this.lastRow_ = row;
- for (var setIdx = 0; setIdx < this.layout_.datasets.length; ++setIdx) {
- var set = this.layout_.datasets[setIdx];
- if (row < set.length) {
- var point = this.layout_.points[setIdx][row];
-
- if (this.attr_("stackedGraph")) {
- point = this.layout_.unstackPointAtIndex(setIdx, row);
- }
-
+ for (var setIdx = 0; setIdx < this.layout_.points.length; ++setIdx) {
+ var points = this.layout_.points[setIdx];
+ var setRow = row - this.getLeftBoundary_(setIdx);
+ if (setRow < points.length) {
+ var point = points[setRow];
if (point.yval !== null) this.selPoints_.push(point);
}
}
var points = this.layout_.points[setIdx];
for (var row = 0; row < points.length; row++) {
if (points[row].x == this.selPoints_[0].x) {
- return row + this.getLeftBoundary_();
+ return points[row].idx;
}
}
}
this.plotter_.clear();
}
- if(!this.is_initial_draw_) {
+ if (!this.is_initial_draw_) {
this.canvas_ctx_.restore();
this.hidden_ctx_.restore();
}
};
/**
+ * 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.
+ *
+ * @private
+ * @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'.
+ * @private
+ */
+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;
+ }
+ }
+};
+
+
+/**
* Loop over all fields and create datasets, calculating extreme y-values for
* each series and extreme x-indices as we go.
*
* extreme values "speculatively", i.e. without actually setting state on the
* dygraph.
*
- * TODO(danvk): make this more of a true function
- * @return [ datasets, seriesExtremes, boundaryIds ]
+ * @param {Array.<Array.<Array.<(number|Array<number>)>>} rolledSeries, where
+ * rolledSeries[seriesIndex][row] = raw point, where
+ * seriesIndex is the column number starting with 1, and
+ * rawPoint is [x,y] or [x, [y, err]] or [x, [y, yminus, yplus]].
+ * @param {?Array.<number>} dateWindow [xmin, xmax] pair, or null.
+ * @return {{
+ * points: Array.<Array.<Dygraph.PointType>>,
+ * seriesExtremes: Array.<Array.<number>>,
+ * boundaryIds: Array.<number>}}
* @private
*/
Dygraph.prototype.gatherDatasets_ = function(rolledSeries, dateWindow) {
var boundaryIds = [];
- var cumulative_y = []; // For stacked series.
- var datasets = [];
+ var points = [];
+ var cumulativeYval = []; // For stacked series.
var extremes = {}; // series name -> [low, high]
- var i, j, k;
+ var i, k;
var errorBars = this.attr_("errorBars");
var customBars = this.attr_("customBars");
var bars = errorBars || customBars;
// Loop over the fields (series). Go from the last to the first,
// because if they're stacked that's how we accumulate the values.
var num_series = rolledSeries.length - 1;
+ var series;
for (i = num_series; i >= 1; i--) {
if (!this.visibility()[i - 1]) continue;
- // Note: this copy _is_ necessary at the moment.
- // If you remove it, it breaks zooming with error bars on.
- // TODO(danvk): investigate further & write a test for this.
- var series = [];
- for (j = 0; j < rolledSeries[i].length; j++) {
- series.push(rolledSeries[i][j]);
- }
-
// Prune down to the desired range, if necessary (for zooming)
// Because there can be lines going to points outside of the visible area,
// we actually prune to visible points, plus one on either side.
if (dateWindow) {
+ series = rolledSeries[i];
var low = dateWindow[0];
var high = dateWindow[1];
- var pruned = [];
// TODO(danvk): do binary search instead of linear search.
// TODO(danvk): pass firstIdx and lastIdx directly to the renderer.
isInvalidValue = isValueNull(series[correctedLastIdx]);
}
- boundaryIds[i-1] = [(firstIdx > 0) ? firstIdx - 1 : firstIdx,
- (lastIdx < series.length - 1) ? lastIdx + 1 : lastIdx];
if (correctedFirstIdx!==firstIdx) {
- pruned.push(series[correctedFirstIdx]);
- }
- for (k = firstIdx; k <= lastIdx; k++) {
- pruned.push(series[k]);
+ firstIdx = correctedFirstIdx;
}
if (correctedLastIdx !== lastIdx) {
- pruned.push(series[correctedLastIdx]);
+ lastIdx = correctedLastIdx;
}
-
- series = pruned;
+
+ boundaryIds[i-1] = [firstIdx, lastIdx];
+
+ // .slice's end is exclusive, we want to include lastIdx.
+ series = series.slice(firstIdx, lastIdx + 1);
} else {
+ series = rolledSeries[i];
boundaryIds[i-1] = [0, series.length-1];
}
+ var seriesName = this.attr_("labels")[i];
var seriesExtremes = this.extremeValues_(series);
- if (bars) {
- for (j=0; j<series.length; j++) {
- series[j] = [series[j][0],
- series[j][1][0],
- series[j][1][1],
- series[j][1][2]];
- }
- } else if (this.attr_("stackedGraph")) {
- // Need to clear last_x explicitly as javascript's locals are
- // local to function, not to a block of statements
- var actual_y, last_x = null;
- for (j = 0; j < series.length; j++) {
- // If one data set has a NaN, let all subsequent stacked
- // sets inherit the NaN -- only start at 0 for the first set.
- var x = series[j][0];
- if (cumulative_y[x] === undefined) {
- cumulative_y[x] = 0;
- }
-
- actual_y = series[j][1];
- if (actual_y === null) {
- series[j] = [x, null];
- continue;
- }
-
- if (last_x != x) {
- cumulative_y[x] += actual_y;
- // If an x-value is repeated, we ignore the duplicates.
- }
- last_x = x;
-
- series[j] = [x, cumulative_y[x]];
+ var seriesPoints = Dygraph.seriesToPoints_(
+ series, bars, seriesName, boundaryIds[i-1][0]);
- if (cumulative_y[x] > seriesExtremes[1]) {
- seriesExtremes[1] = cumulative_y[x];
- }
- if (cumulative_y[x] < seriesExtremes[0]) {
- seriesExtremes[0] = cumulative_y[x];
- }
- }
+ if (this.attr_("stackedGraph")) {
+ Dygraph.stackPoints_(seriesPoints, cumulativeYval, seriesExtremes,
+ this.attr_("stackedGraphNaNFill"));
}
- var seriesName = this.attr_("labels")[i];
extremes[seriesName] = seriesExtremes;
- datasets[i] = series;
- }
-
- // For stacked graphs, a NaN value for any point in the sum should create a
- // clean gap in the graph. Back-propagate NaNs to all points at this X value.
- if (this.attr_("stackedGraph")) {
- for (k = datasets.length - 1; k >= 0; --k) {
- // Use the first nonempty dataset to get X values.
- if (!datasets[k]) continue;
- for (j = 0; j < datasets[k].length; j++) {
- var x = datasets[k][j][0];
- if (isNaN(cumulative_y[x])) {
- // Set all Y values to NaN at that X value.
- for (i = datasets.length - 1; i >= 0; i--) {
- if (!datasets[i]) continue;
- datasets[i][j][1] = NaN;
- }
- }
- }
- break;
- }
+ points[i] = seriesPoints;
}
- return [ datasets, extremes, boundaryIds ];
+ return { points: points, extremes: extremes, boundaryIds: boundaryIds };
};
/**
this.attrs_.pointSize = 0.5 * this.attr_('highlightCircleSize');
var packed = this.gatherDatasets_(this.rolledSeries_, this.dateWindow_);
- var datasets = packed[0];
- var extremes = packed[1];
- this.boundaryIds_ = packed[2];
+ var points = packed.points;
+ var extremes = packed.extremes;
+ this.boundaryIds_ = packed.boundaryIds;
this.setIndexByName_ = {};
var labels = this.attr_("labels");
this.setIndexByName_[labels[0]] = 0;
}
var dataIdx = 0;
- for (var i = 1; i < datasets.length; i++) {
+ for (var i = 1; i < points.length; i++) {
this.setIndexByName_[labels[i]] = i;
if (!this.visibility()[i - 1]) continue;
- this.layout_.addDataset(labels[i], datasets[i]);
+ this.layout_.addDataset(labels[i], points[i]);
this.datasetIndex_[i] = dataIdx++;
}
var tmp_zoomed_x = this.zoomed_x_;
// Tell PlotKit to use this new data and render itself
this.zoomed_x_ = tmp_zoomed_x;
- this.layout_.evaluateWithError();
+ this.layout_.evaluate();
this.renderGraph_(is_initial_draw);
if (this.attr_("timingName")) {
if (this.attr_("drawCallback") !== null) {
this.attr_("drawCallback")(this, is_initial_draw);
}
+ if (is_initial_draw) {
+ this.readyFired_ = true;
+ while (this.readyFns_.length > 0) {
+ var fn = this.readyFns_.pop();
+ fn(this);
+ }
+ }
};
/**
}
- if(independentTicks) {
+ if (independentTicks) {
axis.independentTicks = independentTicks;
var opts = this.optionsViewForAxis_('y' + (i ? '2' : ''));
var ticker = opts('ticker');
* TODO(danvk): the "missing values" bit above doesn't seem right.
*
* @private
+ * @param {Array.<Array.<(number|Array<Number>)>>} rawData Input data. Rectangular
+ * grid of points, where rawData[row][0] is the X value for the row,
+ * and rawData[row][i] is the Y data for series #i.
+ * @param {number} i Series index, starting from 1.
+ * @param {boolean} logScale True if using logarithmic Y scale.
+ * @return {Array.<Array.<(?number|Array<?number>)>} Series array, where
+ * series[row] = [x,y] or [x, [y, err]] or [x, [y, yplus, yminus]].
*/
Dygraph.prototype.extractSeries_ = function(rawData, i, logScale) {
// TODO(danvk): pre-allocate series here.
} else {
// Calculate the rolling average for the first rollPeriod - 1 points where
// there is not enough data to roll over the full number of points
- if (!this.attr_("errorBars")){
+ if (!this.attr_("errorBars")) {
if (rollPeriod == 1) {
return originalData;
}
if (line_delimiter) {
this.loadedEvent_(data);
} else {
- var req = new XMLHttpRequest();
+ // REMOVE_FOR_IE
+ var req;
+ if (window.XMLHttpRequest) {
+ // Firefox, Opera, IE7, and other browsers will use the native object
+ req = new XMLHttpRequest();
+ } else {
+ // IE 5 and 6 will use the ActiveX control
+ req = new ActiveXObject("Microsoft.XMLHTTP");
+ }
+
var caller = this;
req.onreadystatechange = function () {
if (req.readyState == 4) {
this.height_ = this.maindiv_.clientHeight;
}
- this.resizeElements_();
-
if (old_width != this.width_ || old_height != this.height_) {
+ // Resizing a canvas erases it, even when the size doesn't change, so
+ // any resize needs to be followed by a redraw.
+ this.resizeElements_();
this.predraw_();
}
this.annotations_ = ann;
if (!this.layout_) {
this.warn("Tried to setAnnotations before dygraph was ready. " +
- "Try setting them in a drawCallback. See " +
+ "Try setting them in a ready() block. See " +
"dygraphs.com/tests/annotation.html");
return;
}
};
/**
- * Get the internal dataset index given its name. These are numbered starting from 0,
- * and only count visible sets.
- * @private
+ * Trigger a callback when the dygraph has drawn itself and is ready to be
+ * manipulated. This is primarily useful when dygraphs has to do an XHR for the
+ * data (i.e. a URL is passed as the data source) and the chart is drawn
+ * asynchronously. If the chart has already drawn, the callback will fire
+ * immediately.
+ *
+ * This is a good place to call setAnnotation().
+ *
+ * @param {function(!Dygraph)} callback The callback to trigger when the chart
+ * is ready.
*/
-Dygraph.prototype.datasetIndexFromSetName_ = function(name) {
- return this.datasetIndex_[this.indexFromSetName(name)];
+Dygraph.prototype.ready = function(callback) {
+ if (this.is_initial_draw_) {
+ this.readyFns_.push(callback);
+ } else {
+ callback(this);
+ }
};
/**