X-Git-Url: https://adrianiainlam.tk/git/?a=blobdiff_plain;f=dygraph.js;h=e8afb2b5b12cf6cf8db6728b9e092d1b77d5a6ae;hb=a685723c0438aa2b29060404e6048d4409b95b1c;hp=6e72efea6dbe974790ec871c7af02e4e29ecb77f;hpb=c6f45033360faf6265be6532e3045bfcf2abcb8e;p=dygraphs.git diff --git a/dygraph.js b/dygraph.js index 6e72efe..e8afb2b 100644 --- a/dygraph.js +++ b/dygraph.js @@ -123,7 +123,9 @@ Dygraph.DEFAULT_ATTRS = { connectSeparatedPoints: false, stackedGraph: false, - hideOverlayOnMouseOut: true + hideOverlayOnMouseOut: true, + + stepPlot: false }; // Various logging levels. @@ -132,6 +134,9 @@ Dygraph.INFO = 2; Dygraph.WARNING = 3; Dygraph.ERROR = 3; +// Used for initializing annotation CSS rules only once. +Dygraph.addedAnnotationCSS = false; + Dygraph.prototype.__old_init__ = function(div, file, labels, attrs) { // Labels is no longer a constructor parameter, since it's typically set // directly from the data source. It also conains a name for the x-axis, @@ -168,6 +173,7 @@ Dygraph.prototype.__init__ = function(div, file, attrs) { this.valueRange_ = attrs.valueRange || null; this.wilsonInterval_ = attrs.wilsonInterval || true; this.is_initial_draw_ = true; + this.annotations_ = []; // Clear the div. This ensure that, if multiple dygraphs are passed the same // div, then only one will be drawn. @@ -186,13 +192,19 @@ Dygraph.prototype.__init__ = function(div, file, attrs) { // The div might have been specified as percent of the current window size, // convert that to an appropriate number of pixels. if (div.style.width.indexOf("%") == div.style.width.length - 1) { - // Minus ten pixels keeps scrollbars from showing up for a 100% width div. this.width_ = div.offsetWidth; } if (div.style.height.indexOf("%") == div.style.height.length - 1) { this.height_ = div.offsetHeight; } + if (this.width_ == 0) { + this.error("dygraph has zero width. Please specify a width in pixels."); + } + if (this.height_ == 0) { + this.error("dygraph has zero height. Please specify a height in pixels."); + } + // TODO(danvk): set fillGraph to be part of attrs_ here, not user_attrs_. if (attrs['stackedGraph']) { attrs['fillGraph'] = true; @@ -219,6 +231,8 @@ 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_(); @@ -387,20 +401,24 @@ Dygraph.prototype.createInterface_ = function() { this.canvas_.height = this.height_; this.canvas_.style.width = this.width_ + "px"; // for IE this.canvas_.style.height = this.height_ + "px"; // for IE - this.graphDiv.appendChild(this.canvas_); // ... and for static parts of the chart. this.hidden_ = this.createPlotKitCanvas_(this.canvas_); + // The interactive parts of the graph are drawn on top of the chart. + this.graphDiv.appendChild(this.hidden_); + 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.hidden_, 'mousemove', function(e) { + Dygraph.addEvent(this.mouseEventElement_, 'mousemove', function(e) { dygraph.mouseMove_(e); }); - Dygraph.addEvent(this.hidden_, 'mouseout', function(e) { + Dygraph.addEvent(this.mouseEventElement_, 'mouseout', function(e) { dygraph.mouseOut_(e); }); @@ -476,7 +494,6 @@ Dygraph.prototype.createPlotKitCanvas_ = function(canvas) { h.height = this.height_; h.style.width = this.width_ + "px"; // for IE h.style.height = this.height_ + "px"; // for IE - this.graphDiv.appendChild(h); return h; }; @@ -707,7 +724,7 @@ Dygraph.prototype.createDragInterface_ = function() { var getY = function(e) { return Dygraph.pageX(e) - py }; // Draw zoom rectangles when the mouse is down and the user moves around - Dygraph.addEvent(this.hidden_, 'mousemove', function(event) { + Dygraph.addEvent(this.mouseEventElement_, 'mousemove', function(event) { if (isZooming) { dragEndX = getX(event); dragEndY = getY(event); @@ -729,7 +746,7 @@ Dygraph.prototype.createDragInterface_ = function() { }); // Track the beginning of drag events - Dygraph.addEvent(this.hidden_, 'mousedown', function(event) { + Dygraph.addEvent(this.mouseEventElement_, 'mousedown', function(event) { px = Dygraph.findPosX(self.canvas_); py = Dygraph.findPosY(self.canvas_); dragStartX = getX(event); @@ -763,7 +780,7 @@ Dygraph.prototype.createDragInterface_ = function() { }); // Temporarily cancel the dragging event when the mouse leaves the graph - Dygraph.addEvent(this.hidden_, 'mouseout', function(event) { + Dygraph.addEvent(this.mouseEventElement_, 'mouseout', function(event) { if (isZooming) { dragEndX = null; dragEndY = null; @@ -772,7 +789,7 @@ Dygraph.prototype.createDragInterface_ = function() { // If the mouse is released on the canvas during a drag event, then it's a // zoom. Only do the zoom if it's over a large enough area (>= 10 pixels) - Dygraph.addEvent(this.hidden_, 'mouseup', function(event) { + Dygraph.addEvent(this.mouseEventElement_, 'mouseup', function(event) { if (isZooming) { isZooming = false; dragEndX = getX(event); @@ -808,7 +825,7 @@ Dygraph.prototype.createDragInterface_ = function() { }); // Double-clicking zooms back out - Dygraph.addEvent(this.hidden_, 'dblclick', function(event) { + Dygraph.addEvent(this.mouseEventElement_, 'dblclick', function(event) { if (self.dateWindow_ == null) return; self.dateWindow_ = null; self.drawGraph_(self.rawData_); @@ -879,7 +896,7 @@ Dygraph.prototype.doZoom_ = function(lowX, highX) { * @private */ Dygraph.prototype.mouseMove_ = function(event) { - var canvasx = Dygraph.pageX(event) - Dygraph.findPosX(this.hidden_); + var canvasx = Dygraph.pageX(event) - Dygraph.findPosX(this.mouseEventElement_); var points = this.layout_.points; var lastx = -1; @@ -902,9 +919,7 @@ Dygraph.prototype.mouseMove_ = function(event) { // Extract the points we've selected this.selPoints_ = []; - var cumulative_sum = 0; // used only if we have a stackedGraph. var l = points.length; - var isStacked = this.attr_("stackedGraph"); if (!this.attr_("stackedGraph")) { for (var i = 0; i < l; i++) { if (points[i].xval == lastx) { @@ -912,11 +927,11 @@ Dygraph.prototype.mouseMove_ = function(event) { } } } else { - // Stacked points need to be examined in reverse order. + // Need to 'unstack' points starting from the bottom + var cumulative_sum = 0; for (var i = l - 1; i >= 0; i--) { if (points[i].xval == lastx) { - // Clone the point, since we need to 'unstack' it below. - var p = {}; + var p = {}; // Clone the point since we modify it for (var k in points[i]) { p[k] = points[i][k]; } @@ -925,6 +940,7 @@ Dygraph.prototype.mouseMove_ = function(event) { this.selPoints_.push(p); } } + this.selPoints_.reverse(); } if (this.attr_("highlightCallback")) { @@ -1513,17 +1529,19 @@ Dygraph.prototype.drawGraph_ = function(data) { var connectSeparatedPoints = this.attr_('connectSeparatedPoints'); - // For stacked series. - var cumulative_y = []; - var stacked_datasets = []; + // 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 cumulative_y = []; // For stacked series. + var datasets = []; - // Loop over all fields in the dataset - for (var i = 1; i < data[0].length; i++) { + // Loop over all fields and create datasets + for (var i = data[0].length - 1; i >= 1; i--) { if (!this.visibility()[i - 1]) continue; var series = []; for (var j = 0; j < data.length; j++) { - if (data[j][i] !== undefined || !connectSeparatedPoints) { + if (data[j][i] != null || !connectSeparatedPoints) { var date = data[j][0]; series.push([date, data[j][i]]); } @@ -1569,38 +1587,36 @@ Dygraph.prototype.drawGraph_ = function(data) { if (maxY === null || thisMaxY > maxY) maxY = thisMaxY; if (bars) { - var vals = []; - for (var j=0; j maxY) - maxY = cumulative_y[series[j][0]]; + if (!maxY || cumulative_y[x] > maxY) + maxY = cumulative_y[x]; } - stacked_datasets.push([this.attr_("labels")[i], vals]); - //this.layout_.addDataset(this.attr_("labels")[i], vals); - } else { - this.layout_.addDataset(this.attr_("labels")[i], series); } + + datasets[i] = series; } - if (stacked_datasets.length > 0) { - for (var i = (stacked_datasets.length - 1); i >= 0; i--) { - this.layout_.addDataset(stacked_datasets[i][0], stacked_datasets[i][1]); - } + for (var i = 1; i < datasets.length; i++) { + if (!this.visibility()[i - 1]) continue; + this.layout_.addDataset(this.attr_("labels")[i], datasets[i]); } // Use some heuristics to come up with a good maxY value, unless it's been @@ -2008,7 +2024,7 @@ Dygraph.prototype.parseArray_ = function(data) { * The data is expected to have a first column that is either a date or a * number. All subsequent columns must be numbers. If there is a clear mismatch * between this.xValueParser_ and the type of the first column, it will be - * fixed. Returned value is in the same format as return value of parseCSV_. + * fixed. Fills out rawData_. * @param {Array.} data See above. * @private */ @@ -2016,15 +2032,6 @@ Dygraph.prototype.parseDataTable_ = function(data) { var cols = data.getNumberOfColumns(); var rows = data.getNumberOfRows(); - // Read column labels - var labels = []; - for (var i = 0; i < cols; i++) { - labels.push(data.getColumnLabel(i)); - if (i != 0 && this.attr_("errorBars")) i += 1; - } - this.attrs_.labels = labels; - cols = labels.length; - var indepType = data.getColumnType(0); if (indepType == 'date' || indepType == 'datetime') { this.attrs_.xValueFormatter = Dygraph.dateString_; @@ -2042,8 +2049,41 @@ Dygraph.prototype.parseDataTable_ = function(data) { return null; } + // Array of the column indices which contain data (and not annotations). + var colIdx = []; + var annotationCols = {}; // data index -> [annotation cols] + var hasAnnotations = false; + for (var i = 1; i < cols; i++) { + var type = data.getColumnType(i); + if (type == 'number') { + colIdx.push(i); + } else if (type == 'string' && this.attr_('displayAnnotations')) { + // This is OK -- it's an annotation column. + var dataIdx = colIdx[colIdx.length - 1]; + if (!annotationCols.hasOwnProperty(dataIdx)) { + annotationCols[dataIdx] = [i]; + } else { + annotationCols[dataIdx].push(i); + } + hasAnnotations = true; + } else { + this.error("Only 'number' is supported as a dependent type with Gviz." + + " 'string' is only supported if displayAnnotations is true"); + } + } + + // Read column labels + // TODO(danvk): add support back for errorBars + var labels = [data.getColumnLabel(0)]; + for (var i = 0; i < colIdx.length; i++) { + labels.push(data.getColumnLabel(colIdx[i])); + } + this.attrs_.labels = labels; + cols = labels.length; + var ret = []; var outOfOrder = false; + var annotations = []; for (var i = 0; i < rows; i++) { var row = []; if (typeof(data.getValue(i, 0)) === 'undefined' || @@ -2059,8 +2099,23 @@ Dygraph.prototype.parseDataTable_ = function(data) { row.push(data.getValue(i, 0)); } if (!this.attr_("errorBars")) { - for (var j = 1; j < cols; j++) { - row.push(data.getValue(i, j)); + for (var j = 0; j < colIdx.length; j++) { + var col = colIdx[j]; + row.push(data.getValue(i, col)); + if (hasAnnotations && + annotationCols.hasOwnProperty(col) && + data.getValue(i, annotationCols[col][0]) != null) { + var ann = {}; + ann.series = data.getColumnLabel(col); + ann.xval = row[0]; + ann.shortText = String.fromCharCode(65 /* A */ + annotations.length) + ann.text = ''; + for (var k = 0; k < annotationCols[col].length; k++) { + if (k) ann.text += "\n"; + ann.text += data.getValue(i, annotationCols[col][k]); + } + annotations.push(ann); + } } } else { for (var j = 0; j < cols - 1; j++) { @@ -2077,7 +2132,11 @@ Dygraph.prototype.parseDataTable_ = function(data) { this.warn("DataTable is out of order; order it correctly to speed loading."); ret.sort(function(a,b) { return a[0] - b[0] }); } - return ret; + this.rawData_ = ret; + + if (annotations.length > 0) { + this.setAnnotations(annotations, true); + } } // These functions are all based on MochiKit. @@ -2143,7 +2202,7 @@ Dygraph.prototype.start_ = function() { } else if (typeof this.file_ == 'object' && typeof this.file_.getColumnRange == 'function') { // must be a DataTable from gviz. - this.rawData_ = this.parseDataTable_(this.file_); + this.parseDataTable_(this.file_); this.drawGraph_(this.rawData_); } else if (typeof this.file_ == 'string') { // Heuristic: a newline means it's CSV data. Otherwise it's an URL. @@ -2188,6 +2247,7 @@ Dygraph.prototype.updateOptions = function(attrs) { this.valueRange_ = attrs.valueRange; } Dygraph.update(this.user_attrs_, attrs); + Dygraph.update(this.renderOptions_, attrs); this.labelsFromCSV_ = (this.attr_("labels") == null); @@ -2283,6 +2343,40 @@ Dygraph.prototype.setVisibility = function(num, value) { }; /** + * Update the list of annotations and redraw the chart. + */ +Dygraph.prototype.setAnnotations = function(ann, suppressDraw) { + this.annotations_ = ann; + this.layout_.setAnnotations(this.annotations_); + if (!suppressDraw) { + this.drawGraph_(this.rawData_); + } +}; + +/** + * Return the list of annotations. + */ +Dygraph.prototype.annotations = function() { + return this.annotations_; +}; + +Dygraph.addAnnotationRule = function() { + if (Dygraph.addedAnnotationCSS) return; + + var mysheet=document.styleSheets[0] + var rule = "border: 1px solid black; " + + "background-color: white; " + + "text-align: center;"; + if (mysheet.insertRule) { // Firefox + mysheet.insertRule(".dygraphDefaultAnnotation { " + rule + " }"); + } else if (mysheet.addRule) { // IE + mysheet.addRule(".dygraphDefaultAnnotation", rule); + } + + Dygraph.addedAnnotationCSS = true; +} + +/** * Create a new canvas element. This is more complex than a simple * document.createElement("canvas") because of IE and excanvas. */