X-Git-Url: https://adrianiainlam.tk/git/?a=blobdiff_plain;f=dygraph.js;h=ee0880230250a82128765582da121034511d6d71;hb=5d13ef68a27544eff72ea3a2ef3aabdc4c4fd740;hp=1bf5e05766cab0d103ced85d25a9ab3376372191;hpb=bb66df31ffc04645f3227d81651574dd5d4561ae;p=dygraphs.git diff --git a/dygraph.js b/dygraph.js index 1bf5e05..ee08802 100644 --- a/dygraph.js +++ b/dygraph.js @@ -102,6 +102,7 @@ Dygraph.DEFAULT_ATTRS = { axisLabelFontSize: 14, xAxisLabelWidth: 50, yAxisLabelWidth: 50, + xAxisLabelFormatter: Dygraph.dateAxisFormatter, rightGap: 5, showRoller: false, @@ -122,7 +123,9 @@ Dygraph.DEFAULT_ATTRS = { connectSeparatedPoints: false, stackedGraph: false, - hideOverlayOnMouseOut: true + hideOverlayOnMouseOut: true, + + stepPlot: false }; // Various logging levels. @@ -185,11 +188,17 @@ 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_ = (this.width_ * self.innerWidth / 100) - 10; + this.width_ = div.offsetWidth; } if (div.style.height.indexOf("%") == div.style.height.length - 1) { - this.height_ = (this.height_ * self.innerHeight / 100) - 10; + 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_. @@ -386,20 +395,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); }); @@ -475,7 +488,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; }; @@ -706,7 +718,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); @@ -728,7 +740,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); @@ -762,7 +774,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; @@ -771,7 +783,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); @@ -807,7 +819,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_); @@ -878,7 +890,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; @@ -901,9 +913,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) { @@ -911,11 +921,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]; } @@ -924,13 +934,13 @@ Dygraph.prototype.mouseMove_ = function(event) { this.selPoints_.push(p); } } + this.selPoints_.reverse(); } if (this.attr_("highlightCallback")) { - var px = this.lastHighlightCallbackX; + var px = this.lastx_; if (px !== null && lastx != px) { // only fire if the selected point has changed. - this.lastHighlightCallbackX = lastx; this.attr_("highlightCallback")(event, lastx, this.selPoints_); } } @@ -1039,6 +1049,10 @@ Dygraph.prototype.setSelection = function(row) { * @private */ Dygraph.prototype.mouseOut_ = function(event) { + if (this.attr_("unhighlightCallback")) { + this.attr_("unhighlightCallback")(event); + } + if (this.attr_("hideOverlayOnMouseOut")) { this.clearSelection(); } @@ -1085,7 +1099,7 @@ Dygraph.zeropad = function(x) { * @return {String} A time of the form "HH:MM:SS" * @private */ -Dygraph.prototype.hmsString_ = function(date) { +Dygraph.hmsString_ = function(date) { var zeropad = Dygraph.zeropad; var d = new Date(date); if (d.getSeconds()) { @@ -1098,11 +1112,31 @@ Dygraph.prototype.hmsString_ = function(date) { } /** + * Convert a JS date to a string appropriate to display on an axis that + * is displaying values at the stated granularity. + * @param {Date} date The date to format + * @param {Number} granularity One of the Dygraph granularity constants + * @return {String} The formatted date + * @private + */ +Dygraph.dateAxisFormatter = function(date, granularity) { + if (granularity >= Dygraph.MONTHLY) { + return date.strftime('%b %y'); + } else { + var frac = date.getHours() * 3600 + date.getMinutes() * 60 + date.getSeconds() + date.getMilliseconds(); + if (frac == 0 || granularity >= Dygraph.DAILY) { + return new Date(date.getTime() + 3600*1000).strftime('%d%b'); + } else { + return Dygraph.hmsString_(date.getTime()); + } + } +} + +/** * Convert a JS date (millis since epoch) to YYYY/MM/DD * @param {Number} date The JavaScript date (ms since epoch) * @return {String} A date of the form "YYYY/MM/DD" * @private - * TODO(danvk): why is this part of the prototype? */ Dygraph.dateString_ = function(date, self) { var zeropad = Dygraph.zeropad; @@ -1117,7 +1151,7 @@ Dygraph.dateString_ = function(date, self) { var ret = ""; var frac = d.getHours() * 3600 + d.getMinutes() * 60 + d.getSeconds(); - if (frac) ret = " " + self.hmsString_(date); + if (frac) ret = " " + Dygraph.hmsString_(date); return year + "/" + month + "/" + day + ret; }; @@ -1239,6 +1273,7 @@ Dygraph.prototype.NumXTicks = function(start_time, end_time, granularity) { // Returns an array containing {v: millis, label: label} dictionaries. // Dygraph.prototype.GetXAxis = function(start_time, end_time, granularity) { + var formatter = this.attr_("xAxisLabelFormatter"); var ticks = []; if (granularity < Dygraph.MONTHLY) { // Generate one tick mark for every fixed interval of time. @@ -1275,14 +1310,7 @@ Dygraph.prototype.GetXAxis = function(start_time, end_time, granularity) { start_time = d.getTime(); for (var t = start_time; t <= end_time; t += spacing) { - var d = new Date(t); - var frac = d.getHours() * 3600 + d.getMinutes() * 60 + d.getSeconds(); - if (frac == 0 || granularity >= Dygraph.DAILY) { - // the extra hour covers DST problems. - ticks.push({ v:t, label: new Date(t + 3600*1000).strftime(format) }); - } else { - ticks.push({ v:t, label: this.hmsString_(t) }); - } + ticks.push({ v:t, label: formatter(new Date(t), granularity) }); } } else { // Display a tick mark on the first of a set of months of each year. @@ -1313,7 +1341,7 @@ Dygraph.prototype.GetXAxis = function(start_time, end_time, granularity) { var date_str = i + "/" + zeropad(1 + months[j]) + "/01"; var t = Date.parse(date_str); if (t < start_time || t > end_time) continue; - ticks.push({ v:t, label: new Date(t).strftime('%b %y') }); + ticks.push({ v:t, label: formatter(new Date(t), granularity) }); } } } @@ -1350,10 +1378,12 @@ 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} formatter: Optional formatter to use for each tick value * @return {Array.} Array of {label, value} tuples. * @public */ -Dygraph.numericTicks = function(minV, maxV, self) { +Dygraph.numericTicks = function(minV, maxV, self, formatter) { // Basic idea: // Try labels every 1, 2, 5, 10, 20, 50, 100, etc. // Calculate the resulting tick spacing (i.e. this.height_ / nTicks). @@ -1405,7 +1435,12 @@ Dygraph.numericTicks = function(minV, maxV, self) { for (var i = 0; i < nTicks; i++) { var tickV = low_val + i * scale; 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; @@ -1430,7 +1465,8 @@ Dygraph.numericTicks = function(minV, maxV, self) { 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); + var formatter = this.attr_('yAxisLabelFormatter') ? this.attr_('yAxisLabelFormatter') : this.attr_('yValueFormatter'); + var ticks = Dygraph.numericTicks(minY, maxY, this, formatter); this.layout_.updateOptions( { yAxis: [minY, maxY], yTicks: ticks } ); }; @@ -1495,17 +1531,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] || !connectSeparatedPoints) { + if (data[j][i] != null || !connectSeparatedPoints) { var date = data[j][0]; series.push([date, data[j][i]]); } @@ -1547,42 +1585,39 @@ Dygraph.prototype.drawGraph_ = function(data) { var extremes = this.extremeValues_(series); var thisMinY = extremes[0]; var thisMaxY = extremes[1]; - if (!minY || thisMinY < minY) minY = thisMinY; - if (!maxY || thisMaxY > maxY) maxY = thisMaxY; + if (minY === null || thisMinY < minY) minY = thisMinY; + 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++) { + this.layout_.addDataset(this.attr_("labels")[i], datasets[i]); } // Use some heuristics to come up with a good maxY value, unless it's been @@ -1821,10 +1856,12 @@ Dygraph.prototype.detectTypeFromString_ = function(str) { this.attrs_.xValueFormatter = Dygraph.dateString_; this.attrs_.xValueParser = Dygraph.dateParser; this.attrs_.xTicker = Dygraph.dateTicker; + this.attrs_.xAxisLabelFormatter = Dygraph.dateAxisFormatter; } else { this.attrs_.xValueFormatter = function(x) { return x; }; this.attrs_.xValueParser = function(x) { return parseFloat(x); }; this.attrs_.xTicker = Dygraph.numericTicks; + this.attrs_.xAxisLabelFormatter = this.attrs_.xValueFormatter; } }; @@ -1956,6 +1993,7 @@ Dygraph.prototype.parseArray_ = function(data) { if (Dygraph.isDateLike(data[0][0])) { // Some intelligent defaults for a date x-axis. this.attrs_.xValueFormatter = Dygraph.dateString_; + this.attrs_.xAxisLabelFormatter = Dygraph.dateAxisFormatter; this.attrs_.xTicker = Dygraph.dateTicker; // Assume they're all dates. @@ -2009,10 +2047,12 @@ Dygraph.prototype.parseDataTable_ = function(data) { this.attrs_.xValueFormatter = Dygraph.dateString_; this.attrs_.xValueParser = Dygraph.dateParser; this.attrs_.xTicker = Dygraph.dateTicker; + this.attrs_.xAxisLabelFormatter = Dygraph.dateAxisFormatter; } else if (indepType == 'number') { this.attrs_.xValueFormatter = function(x) { return x; }; this.attrs_.xValueParser = function(x) { return parseFloat(x); }; this.attrs_.xTicker = Dygraph.numericTicks; + this.attrs_.xAxisLabelFormatter = this.attrs_.xValueFormatter; } else { this.error("only 'date', 'datetime' and 'number' types are supported for " + "column 1 of DataTable input (Got '" + indepType + "')"); @@ -2190,6 +2230,11 @@ Dygraph.prototype.updateOptions = function(attrs) { * @param {Number} height Height (in pixels) */ Dygraph.prototype.resize = function(width, height) { + if (this.resize_lock) { + return; + } + this.resize_lock = true; + if ((width === null) != (height === null)) { this.warn("Dygraph.resize() should be called with zero parameters or " + "two non-NULL parameters. Pretending it was zero."); @@ -2212,6 +2257,8 @@ Dygraph.prototype.resize = function(width, height) { this.createInterface_(); this.drawGraph_(this.rawData_); + + this.resize_lock = false; }; /**