X-Git-Url: https://adrianiainlam.tk/git/?a=blobdiff_plain;f=dygraph.js;h=a266af05403128bd6c1bd36d5137067a4a395f01;hb=c1dbeb10b48e0f805be2cef09efebf5e35218c1a;hp=929a9ea09212457d2a2d234b0fea5b8741d14d41;hpb=de0445fc1b969ab85a3d985dbd1e6747f3ddac0b;p=dygraphs.git diff --git a/dygraph.js b/dygraph.js index 929a9ea..a266af0 100644 --- a/dygraph.js +++ b/dygraph.js @@ -126,7 +126,8 @@ Dygraph.DEFAULT_ATTRS = { stackedGraph: false, hideOverlayOnMouseOut: true, - stepPlot: false + stepPlot: false, + avoidMinZero: false }; // Various logging levels. @@ -171,7 +172,6 @@ Dygraph.prototype.__init__ = function(div, file, attrs) { this.previousVerticalX_ = -1; this.fractions_ = attrs.fractions || false; this.dateWindow_ = attrs.dateWindow || null; - this.valueRange_ = attrs.valueRange || null; this.wilsonInterval_ = attrs.wilsonInterval || true; this.is_initial_draw_ = true; this.annotations_ = []; @@ -232,20 +232,18 @@ Dygraph.prototype.__init__ = function(div, file, attrs) { // Make a note of whether labels will be pulled from the CSV file. this.labelsFromCSV_ = (this.attr_("labels") == null); - Dygraph.addAnnotationRule(); - // Create the containing DIV and other interactive elements this.createInterface_(); this.start_(); }; -Dygraph.prototype.attr_ = function(name, series) { - if (series && - typeof(this.user_attrs_[series]) != 'undefined' && - this.user_attrs_[series] != null && - typeof(this.user_attrs_[series][name]) != 'undefined') { - return this.user_attrs_[series][name]; +Dygraph.prototype.attr_ = function(name, seriesName) { + if (seriesName && + typeof(this.user_attrs_[seriesName]) != 'undefined' && + this.user_attrs_[seriesName] != null && + typeof(this.user_attrs_[seriesName][name]) != 'undefined') { + return this.user_attrs_[seriesName][name]; } else if (typeof(this.user_attrs_[name]) != 'undefined') { return this.user_attrs_[name]; } else if (typeof(this.attrs_[name]) != 'undefined') { @@ -395,13 +393,6 @@ Dygraph.addEvent = function(el, evt, fn) { } }; -Dygraph.clipCanvas_ = function(cnv, clip) { - var ctx = cnv.getContext("2d"); - ctx.beginPath(); - ctx.rect(clip.left, clip.top, clip.width, clip.height); - ctx.clip(); -}; - /** * Generates interface elements for the Dygraph: a containing div, a div to * display the current point, and a textbox to adjust the rolling average @@ -417,15 +408,6 @@ Dygraph.prototype.createInterface_ = function() { this.graphDiv.style.height = this.height_ + "px"; enclosing.appendChild(this.graphDiv); - var clip = { - top: 0, - left: this.attr_("yAxisLabelWidth") + 2 * this.attr_("axisTickSize") - }; - clip.width = this.width_ - clip.left - this.attr_("rightGap"); - clip.height = this.height_ - this.attr_("axisLabelFontSize") - - 2 * this.attr_("axisTickSize"); - this.clippingArea_ = clip; - // Create the canvas for interactive parts of the chart. this.canvas_ = Dygraph.createCanvas(); this.canvas_.style.position = "absolute"; @@ -442,10 +424,6 @@ Dygraph.prototype.createInterface_ = function() { this.graphDiv.appendChild(this.canvas_); this.mouseEventElement_ = this.canvas_; - // Make sure we don't overdraw. - Dygraph.clipCanvas_(this.hidden_, this.clippingArea_); - Dygraph.clipCanvas_(this.canvas_, this.clippingArea_); - var dygraph = this; Dygraph.addEvent(this.mouseEventElement_, 'mousemove', function(e) { dygraph.mouseMove_(e); @@ -470,12 +448,8 @@ Dygraph.prototype.createInterface_ = function() { axisLineWidth: Dygraph.AXIS_LINE_WIDTH }; Dygraph.update(this.renderOptions_, this.attrs_); Dygraph.update(this.renderOptions_, this.user_attrs_); - this.plotter_ = new DygraphCanvasRenderer(this, - this.hidden_, this.layout_, - this.renderOptions_); this.createStatusMessage_(); - this.createRollInterface_(); this.createDragInterface_(); }; @@ -686,6 +660,9 @@ Dygraph.prototype.createStatusMessage_ = function() { * @private */ Dygraph.prototype.createRollInterface_ = function() { + // Destroy any existing roller. + if (this.roller_) this.graphDiv.removeChild(this.roller_); + var display = this.attr_('showRoller') ? "block" : "none"; var textAttr = { "position": "absolute", "zIndex": 10, @@ -778,7 +755,7 @@ Dygraph.prototype.createDragInterface_ = function() { self.dateWindow_[0] = draggingDate - (dragEndX / self.width_) * dateRange; self.dateWindow_[1] = self.dateWindow_[0] + dateRange; - self.drawGraph_(self.rawData_); + self.drawGraph_(); } }); @@ -886,7 +863,7 @@ Dygraph.prototype.createDragInterface_ = function() { Dygraph.addEvent(this.mouseEventElement_, 'dblclick', function(event) { if (self.dateWindow_ == null) return; self.dateWindow_ = null; - self.drawGraph_(self.rawData_); + self.drawGraph_(); var minDate = self.rawData_[0][0]; var maxDate = self.rawData_[self.rawData_.length - 1][0]; if (self.attr_("zoomCallback")) { @@ -940,7 +917,7 @@ Dygraph.prototype.doZoom_ = function(lowX, highX) { var maxDate = r[0]; this.dateWindow_ = [minDate, maxDate]; - this.drawGraph_(this.rawData_); + this.drawGraph_(); if (this.attr_("zoomCallback")) { this.attr_("zoomCallback")(minDate, maxDate); } @@ -1026,9 +1003,9 @@ Dygraph.prototype.updateSelection_ = function() { if (this.previousVerticalX_ >= 0) { // Determine the maximum highlight circle size. var maxCircleSize = 0; - var num_series = this.attr_('labels').length; - for (var i = 1; i < num_series; i++) { - var r = this.attr_('highlightCircleSize', i); + var labels = this.attr_('labels'); + for (var i = 1; i < labels.length; i++) { + var r = this.attr_('highlightCircleSize', labels[i]); if (r > maxCircleSize) maxCircleSize = r; } var px = this.previousVerticalX_; @@ -1055,7 +1032,7 @@ Dygraph.prototype.updateSelection_ = function() { replace += "
"; } var point = this.selPoints_[i]; - var c = new RGBColor(this.colors_[i%clen]); + var c = new RGBColor(this.plotter_.colors[point.name]); var yval = fmtFunc(point.yval); replace += " " + point.name + ":" @@ -1069,8 +1046,8 @@ Dygraph.prototype.updateSelection_ = function() { ctx.save(); for (var i = 0; i < this.selPoints_.length; i++) { if (!isOK(this.selPoints_[i].canvasy)) continue; - var setIdx = this.indexFromSetName(this.selPoints_[i].name); - var circleSize = this.attr_('highlightCircleSize', setIdx); + var circleSize = + this.attr_('highlightCircleSize', this.selPoints_[i].name); ctx.beginPath(); ctx.fillStyle = this.plotter_.colors[this.selPoints_[i].name]; ctx.arc(canvasx, this.selPoints_[i].canvasy, circleSize, @@ -1101,7 +1078,13 @@ Dygraph.prototype.setSelection = function(row) { if (row !== false && row >= 0) { for (var i in this.layout_.datasets) { if (row < this.layout_.datasets[i].length) { - this.selPoints_.push(this.layout_.points[pos+row]); + var point = this.layout_.points[pos+row]; + + if (this.attr_("stackedGraph")) { + point = this.layout_.unstackPointAtIndex(pos+row); + } + + this.selPoints_.push(point); } pos += this.layout_.datasets[i].length; } @@ -1249,7 +1232,7 @@ Dygraph.round_ = function(num, places) { */ Dygraph.prototype.loadedEvent_ = function(data) { this.rawData_ = this.parseCSV_(data); - this.drawGraph_(this.rawData_); + this.predraw_(); }; Dygraph.prototype.months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", @@ -1452,62 +1435,86 @@ Dygraph.dateTicker = function(startDate, endDate, self) { * Add ticks when the x axis has numbers on it (instead of dates) * @param {Number} startDate Start of the date window (millis since epoch) * @param {Number} endDate End of the date window (millis since epoch) + * @param self + * @param {function} attribute accessor function. * @return {Array.} Array of {label, value} tuples. * @public */ -Dygraph.numericTicks = function(minV, maxV, self) { - // Basic idea: - // Try labels every 1, 2, 5, 10, 20, 50, 100, etc. - // Calculate the resulting tick spacing (i.e. this.height_ / nTicks). - // The first spacing greater than pixelsPerYLabel is what we use. - // TODO(danvk): version that works on a log scale. - if (self.attr_("labelsKMG2")) { - var mults = [1, 2, 4, 8]; +Dygraph.numericTicks = function(minV, maxV, self, axis_props, vals) { + var attr = function(k) { + if (axis_props && axis_props.hasOwnProperty(k)) return axis_props[k]; + return self.attr_(k); + }; + + var ticks = []; + if (vals) { + for (var i = 0; i < vals.length; i++) { + ticks.push({v: vals[i]}); + } } else { - var mults = [1, 2, 5]; - } - var scale, low_val, high_val, nTicks; - // TODO(danvk): make it possible to set this for x- and y-axes independently. - var pixelsPerTick = self.attr_('pixelsPerYLabel'); - for (var i = -10; i < 50; i++) { - if (self.attr_("labelsKMG2")) { - var base_scale = Math.pow(16, i); + // Basic idea: + // Try labels every 1, 2, 5, 10, 20, 50, 100, etc. + // Calculate the resulting tick spacing (i.e. this.height_ / nTicks). + // The first spacing greater than pixelsPerYLabel is what we use. + // TODO(danvk): version that works on a log scale. + if (attr("labelsKMG2")) { + var mults = [1, 2, 4, 8]; } else { - var base_scale = Math.pow(10, i); - } - for (var j = 0; j < mults.length; j++) { - scale = base_scale * mults[j]; - low_val = Math.floor(minV / scale) * scale; - high_val = Math.ceil(maxV / scale) * scale; - nTicks = Math.abs(high_val - low_val) / scale; - var spacing = self.height_ / nTicks; - // wish I could break out of both loops at once... + var mults = [1, 2, 5]; + } + var scale, low_val, high_val, nTicks; + // TODO(danvk): make it possible to set this for x- and y-axes independently. + var pixelsPerTick = attr('pixelsPerYLabel'); + for (var i = -10; i < 50; i++) { + if (attr("labelsKMG2")) { + var base_scale = Math.pow(16, i); + } else { + var base_scale = Math.pow(10, i); + } + for (var j = 0; j < mults.length; j++) { + scale = base_scale * mults[j]; + low_val = Math.floor(minV / scale) * scale; + high_val = Math.ceil(maxV / scale) * scale; + nTicks = Math.abs(high_val - low_val) / scale; + var spacing = self.height_ / nTicks; + // wish I could break out of both loops at once... + if (spacing > pixelsPerTick) break; + } if (spacing > pixelsPerTick) break; } - if (spacing > pixelsPerTick) break; + + // Construct the set of ticks. + // Allow reverse y-axis if it's explicitly requested. + if (low_val > high_val) scale *= -1; + for (var i = 0; i < nTicks; i++) { + var tickV = low_val + i * scale; + ticks.push( {v: tickV} ); + } } - // Construct labels for the ticks - var ticks = []; + // Add formatted labels to the ticks. var k; var k_labels = []; - if (self.attr_("labelsKMB")) { + if (attr("labelsKMB")) { k = 1000; k_labels = [ "K", "M", "B", "T" ]; } - if (self.attr_("labelsKMG2")) { + if (attr("labelsKMG2")) { if (k) self.warn("Setting both labelsKMB and labelsKMG2. Pick one!"); k = 1024; k_labels = [ "k", "M", "G", "T" ]; } + var formatter = attr('yAxisLabelFormatter') ? attr('yAxisLabelFormatter') : attr('yValueFormatter'); - // Allow reverse y-axis if it's explicitly requested. - if (low_val > high_val) scale *= -1; - - for (var i = 0; i < nTicks; i++) { - var tickV = low_val + i * scale; + for (var i = 0; i < ticks.length; i++) { + var tickV = ticks[i].v; var absTickV = Math.abs(tickV); - var label = Dygraph.round_(tickV, 2); + var label; + if (formatter != undefined) { + label = formatter(tickV); + } else { + label = Dygraph.round_(tickV, 2); + } if (k_labels.length) { // Round up to an appropriate unit. var n = k*k*k*k; @@ -1518,25 +1525,11 @@ Dygraph.numericTicks = function(minV, maxV, self) { } } } - ticks.push( {label: label, v: tickV} ); + ticks[i].label = label; } return ticks; }; -/** - * Adds appropriate ticks on the y-axis - * @param {Number} minY The minimum Y value in the data set - * @param {Number} maxY The maximum Y value in the data set - * @private - */ -Dygraph.prototype.addYTicks_ = function(minY, maxY) { - // Set the number of ticks so that the labels are human-friendly. - // TODO(danvk): make this an attribute as well. - var ticks = Dygraph.numericTicks(minY, maxY, this); - this.layout_.updateOptions( { yAxis: [minY, maxY], - yTicks: ticks } ); -}; - // Computes the range of the data series (including confidence intervals). // series is either [ [x1, y1], [x2, y2], ... ] or // [ [x1, [y1, dev_low, dev_high]], [x2, [y2, dev_low, dev_high]], ... @@ -1578,14 +1571,37 @@ Dygraph.prototype.extremeValues_ = function(series) { }; /** - * Update the graph with new data. Data is in the format - * [ [date1, val1, val2, ...], [date2, val1, val2, ...] if errorBars=false - * or, if errorBars=true, - * [ [date1, [val1,stddev1], [val2,stddev2], ...], [date2, ...], ...] - * @param {Array.} data The data (see above) + * This function is called once when the chart's data is changed or the options + * dictionary is updated. It is _not_ called when the user pans or zooms. The + * idea is that values derived from the chart's data can be computed here, + * rather than every time the chart is drawn. This includes things like the + * number of axes, rolling averages, etc. + */ +Dygraph.prototype.predraw_ = function() { + // TODO(danvk): move more computations out of drawGraph_ and into here. + this.computeYAxes_(); + + // Create a new plotter. + if (this.plotter_) this.plotter_.clear(); + this.plotter_ = new DygraphCanvasRenderer(this, + this.hidden_, this.layout_, + this.renderOptions_); + + this.roller_ = this.createRollInterface_(); + + // If the data or options have changed, then we'd better redraw. + this.drawGraph_(); +}; + +/** + * Update the graph with new data. This method is called when the viewing area + * has changed. If the underlying data or options have changed, predraw_ will + * be called before drawGraph_ is called. * @private */ -Dygraph.prototype.drawGraph_ = function(data) { +Dygraph.prototype.drawGraph_ = function() { + var data = this.rawData_; + // This is used to set the second parameter to drawCallback, below. var is_initial_draw = this.is_initial_draw_; this.is_initial_draw_ = false; @@ -1601,10 +1617,13 @@ Dygraph.prototype.drawGraph_ = function(data) { var cumulative_y = []; // For stacked series. var datasets = []; + var extremes = {}; // series name -> [low, high] + // Loop over all fields and create datasets for (var i = data[0].length - 1; i >= 1; i--) { if (!this.visibility()[i - 1]) continue; + var seriesName = this.attr_("labels")[i]; var connectSeparatedPoints = this.attr_('connectSeparatedPoints', i); var series = []; @@ -1648,11 +1667,12 @@ Dygraph.prototype.drawGraph_ = function(data) { this.boundaryIds_[i-1] = [0, series.length-1]; } - var extremes = this.extremeValues_(series); - var thisMinY = extremes[0]; - var thisMaxY = extremes[1]; - if (minY === null || thisMinY < minY) minY = thisMinY; - if (maxY === null || thisMaxY > maxY) maxY = thisMaxY; + var seriesExtremes = this.extremeValues_(series); + extremes[seriesName] = seriesExtremes; + var thisMinY = seriesExtremes[0]; + var thisMaxY = seriesExtremes[1]; + if (minY === null || (thisMinY != null && thisMinY < minY)) minY = thisMinY; + if (maxY === null || (thisMaxY != null && thisMaxY > maxY)) maxY = thisMaxY; if (bars) { for (var j=0; j 0) { - minY = 0; - } - - // Add some padding and round up to an integer to be human-friendly. - var span = maxY - minY; - // special case: if we have no sense of scale, use +/-10% of the sole value. - if (span == 0) { span = maxY; } - var maxAxisY = maxY + 0.1 * span; - var minAxisY = minY - 0.1 * span; - - // Try to include zero and make it minAxisY (or maxAxisY) if it makes sense. - if (minAxisY < 0 && minY >= 0) minAxisY = 0; - if (maxAxisY > 0 && maxY <= 0) maxAxisY = 0; - - if (this.attr_("includeZero")) { - if (maxY < 0) maxAxisY = 0; - if (minY > 0) minAxisY = 0; - } - - this.addYTicks_(minAxisY, maxAxisY); - this.displayedYRange_ = [minAxisY, maxAxisY]; - } + // TODO(danvk): this method doesn't need to return anything. + var out = this.computeYAxisRanges_(extremes); + var axes = out[0]; + var seriesToAxisMap = out[1]; + this.displayedYRange_ = axes[0].valueRange; + this.layout_.updateOptions( { yAxes: axes, + seriesToAxisMap: seriesToAxisMap + } ); this.addXTicks_(); @@ -1734,6 +1732,175 @@ Dygraph.prototype.drawGraph_ = function(data) { }; /** + * Determine properties of the y-axes which are independent of the data + * currently being displayed. This includes things like the number of axes and + * the style of the axes. It does not include the range of each axis and its + * tick marks. + * This fills in this.axes_ and this.seriesToAxisMap_. + * axes_ = [ { options } ] + * seriesToAxisMap_ = { seriesName: 0, seriesName2: 1, ... } + * indices are into the axes_ array. + */ +Dygraph.prototype.computeYAxes_ = function() { + this.axes_ = [{}]; // always have at least one y-axis. + this.seriesToAxisMap_ = {}; + + // Get a list of series names. + var labels = this.attr_("labels"); + var series = []; + for (var i = 1; i < labels.length; i++) series[labels[i]] = (i - 1); + + // all options which could be applied per-axis: + var axisOptions = [ + 'includeZero', + 'valueRange', + 'labelsKMB', + 'labelsKMG2', + 'pixelsPerYLabel', + 'yAxisLabelWidth', + 'axisLabelFontSize', + 'axisTickSize' + ]; + + // Copy global axis options over to the first axis. + for (var i = 0; i < axisOptions.length; i++) { + var k = axisOptions[i]; + var v = this.attr_(k); + if (v) this.axes_[0][k] = v; + } + + // Go through once and add all the axes. + for (var seriesName in series) { + if (!series.hasOwnProperty(seriesName)) continue; + var axis = this.attr_("axis", seriesName); + if (axis == null) { + this.seriesToAxisMap_[seriesName] = 0; + continue; + } + if (typeof(axis) == 'object') { + // Add a new axis, making a copy of its per-axis options. + var opts = {}; + Dygraph.update(opts, this.axes_[0]); + Dygraph.update(opts, { valueRange: null }); // shouldn't inherit this. + Dygraph.update(opts, axis); + this.axes_.push(opts); + this.seriesToAxisMap_[seriesName] = this.axes_.length - 1; + } + } + + // Go through one more time and assign series to an axis defined by another + // series, e.g. { 'Y1: { axis: {} }, 'Y2': { axis: 'Y1' } } + for (var seriesName in series) { + if (!series.hasOwnProperty(seriesName)) continue; + var axis = this.attr_("axis", seriesName); + if (typeof(axis) == 'string') { + if (!this.seriesToAxisMap_.hasOwnProperty(axis)) { + this.error("Series " + seriesName + " wants to share a y-axis with " + + "series " + axis + ", which does not define its own axis."); + return null; + } + var idx = this.seriesToAxisMap_[axis]; + this.seriesToAxisMap_[seriesName] = idx; + } + } +}; + +/** + * Returns the number of y-axes on the chart. + * @return {Number} the number of axes. + */ +Dygraph.prototype.numAxes = function() { + var last_axis = 0; + for (var series in this.seriesToAxisMap_) { + if (!this.seriesToAxisMap_.hasOwnProperty(series)) continue; + var idx = this.seriesToAxisMap_[series]; + if (idx > last_axis) last_axis = idx; + } + return 1 + last_axis; +}; + +/** + * Determine the value range and tick marks for each axis. + * @param {Object} extremes A mapping from seriesName -> [low, high] + * This fills in the valueRange and ticks fields in each entry of this.axes_. + */ +Dygraph.prototype.computeYAxisRanges_ = function(extremes) { + // Build a map from axis number -> [list of series names] + var seriesForAxis = []; + for (var series in this.seriesToAxisMap_) { + if (!this.seriesToAxisMap_.hasOwnProperty(series)) continue; + var idx = this.seriesToAxisMap_[series]; + while (seriesForAxis.length <= idx) seriesForAxis.push([]); + seriesForAxis[idx].push(series); + } + + // Compute extreme values, a span and tick marks for each axis. + for (var i = 0; i < this.axes_.length; i++) { + var axis = this.axes_[i]; + if (axis.valueRange) { + axis.computedValueRange = [axis.valueRange[0], axis.valueRange[1]]; + } else { + // Calcuate the extremes of extremes. + var series = seriesForAxis[i]; + var minY = Infinity; // extremes[series[0]][0]; + var maxY = -Infinity; // extremes[series[0]][1]; + for (var j = 0; j < series.length; j++) { + minY = Math.min(extremes[series[j]][0], minY); + maxY = Math.max(extremes[series[j]][1], maxY); + } + if (axis.includeZero && minY > 0) minY = 0; + + // Add some padding and round up to an integer to be human-friendly. + var span = maxY - minY; + // special case: if we have no sense of scale, use +/-10% of the sole value. + if (span == 0) { span = maxY; } + var maxAxisY = maxY + 0.1 * span; + var minAxisY = minY - 0.1 * span; + + // Try to include zero and make it minAxisY (or maxAxisY) if it makes sense. + if (minAxisY < 0 && minY >= 0) minAxisY = 0; + if (maxAxisY > 0 && maxY <= 0) maxAxisY = 0; + + if (this.attr_("includeZero")) { + if (maxY < 0) maxAxisY = 0; + if (minY > 0) minAxisY = 0; + } + + axis.computedValueRange = [minAxisY, maxAxisY]; + } + + // Add ticks. By default, all axes inherit the tick positions of the + // primary axis. However, if an axis is specifically marked as having + // independent ticks, then that is permissible as well. + if (i == 0 || axis.independentTicks) { + axis.ticks = + Dygraph.numericTicks(axis.computedValueRange[0], + axis.computedValueRange[1], + this, + axis); + } else { + var p_axis = this.axes_[0]; + var p_ticks = p_axis.ticks; + var p_scale = p_axis.computedValueRange[1] - p_axis.computedValueRange[0]; + var scale = axis.computedValueRange[1] - axis.computedValueRange[0]; + var tick_values = []; + for (var i = 0; i < p_ticks.length; i++) { + var y_frac = (p_ticks[i].v - p_axis.computedValueRange[0]) / p_scale; + var y_val = axis.computedValueRange[0] + y_frac * scale; + tick_values.push(y_val); + } + + axis.ticks = + Dygraph.numericTicks(axis.computedValueRange[0], + axis.computedValueRange[1], + this, axis, tick_values); + } + } + + return [this.axes_, this.seriesToAxisMap_]; +}; + +/** * Calculates the rolling average of a data set. * If originalData is [label, val], rolls the average of those. * If originalData is [label, [, it's interpreted as [value, stddev] @@ -2151,6 +2318,7 @@ Dygraph.prototype.parseDataTable_ = function(data) { var labels = [data.getColumnLabel(0)]; for (var i = 0; i < colIdx.length; i++) { labels.push(data.getColumnLabel(colIdx[i])); + if (this.attr_("errorBars")) i += 1; } this.attrs_.labels = labels; cols = labels.length; @@ -2272,12 +2440,12 @@ Dygraph.prototype.start_ = function() { this.loadedEvent_(this.file_()); } else if (Dygraph.isArrayLike(this.file_)) { this.rawData_ = this.parseArray_(this.file_); - this.drawGraph_(this.rawData_); + this.predraw_(); } else if (typeof this.file_ == 'object' && typeof this.file_.getColumnRange == 'function') { // must be a DataTable from gviz. this.parseDataTable_(this.file_); - this.drawGraph_(this.rawData_); + this.predraw_(); } else if (typeof this.file_ == 'string') { // Heuristic: a newline means it's CSV data. Otherwise it's an URL. if (this.file_.indexOf('\n') >= 0) { @@ -2317,9 +2485,6 @@ Dygraph.prototype.updateOptions = function(attrs) { if (attrs.dateWindow) { this.dateWindow_ = attrs.dateWindow; } - if (attrs.valueRange) { - this.valueRange_ = attrs.valueRange; - } // TODO(danvk): validate per-series options. // Supported: @@ -2339,7 +2504,7 @@ Dygraph.prototype.updateOptions = function(attrs) { this.file_ = attrs['file']; this.start_(); } else { - this.drawGraph_(this.rawData_); + this.predraw_(); } }; @@ -2381,7 +2546,7 @@ Dygraph.prototype.resize = function(width, height) { } this.createInterface_(); - this.drawGraph_(this.rawData_); + this.predraw_(); this.resize_lock = false; }; @@ -2393,7 +2558,7 @@ Dygraph.prototype.resize = function(width, height) { */ Dygraph.prototype.adjustRoll = function(length) { this.rollPeriod_ = length; - this.drawGraph_(this.rawData_); + this.predraw_(); }; /** @@ -2420,7 +2585,7 @@ Dygraph.prototype.setVisibility = function(num, value) { this.warn("invalid series number in setVisibility: " + num); } else { x[num] = value; - this.drawGraph_(this.rawData_); + this.predraw_(); } }; @@ -2428,10 +2593,12 @@ Dygraph.prototype.setVisibility = function(num, value) { * Update the list of annotations and redraw the chart. */ Dygraph.prototype.setAnnotations = function(ann, suppressDraw) { + // Only add the annotation CSS rule once we know it will be used. + Dygraph.addAnnotationRule(); this.annotations_ = ann; this.layout_.setAnnotations(this.annotations_); if (!suppressDraw) { - this.drawGraph_(this.rawData_); + this.predraw_(); } }; @@ -2474,7 +2641,8 @@ Dygraph.addAnnotationRule = function() { "background-color: white; " + "text-align: center;"; if (mysheet.insertRule) { // Firefox - mysheet.insertRule(".dygraphDefaultAnnotation { " + rule + " }", 0); + var idx = mysheet.cssRules ? mysheet.cssRules.length : 0; + mysheet.insertRule(".dygraphDefaultAnnotation { " + rule + " }", idx); } else if (mysheet.addRule) { // IE mysheet.addRule(".dygraphDefaultAnnotation", rule); } @@ -2490,7 +2658,7 @@ Dygraph.createCanvas = function() { var canvas = document.createElement("canvas"); isIE = (/MSIE/.test(navigator.userAgent) && !window.opera); - if (isIE) { + if (isIE && (typeof(G_vmlCanvasManager) != 'undefined')) { canvas = G_vmlCanvasManager.initElement(canvas); }