X-Git-Url: https://adrianiainlam.tk/git/?a=blobdiff_plain;f=dygraph.js;h=f004c4ed4d4b1c4ced5b2b06b912329bb1027675;hb=47c97d86490258703f825f1b50f519b8772380c8;hp=d7dc8e5fd9cbab5689ce83ddf577adb61bc376ea;hpb=9d6c91a04c3544be4a933080b5e0ece6afe26dc3;p=dygraphs.git
diff --git a/dygraph.js b/dygraph.js
index d7dc8e5..f004c4e 100644
--- a/dygraph.js
+++ b/dygraph.js
@@ -41,13 +41,18 @@
*/
/**
- * An interactive, zoomable graph
- * @param {String | Function} file A file containing CSV data or a function that
- * returns this data. The expected format for each line is
- * YYYYMMDD,val1,val2,... or, if attrs.errorBars is set,
- * YYYYMMDD,val1,stddev1,val2,stddev2,...
+ * Creates an interactive, zoomable chart.
+ *
+ * @constructor
+ * @param {div | String} div A div or the id of a div into which to construct
+ * the chart.
+ * @param {String | Function} file A file containing CSV data or a function
+ * that returns this data. The most basic expected format for each line is
+ * "YYYY/MM/DD,val1,val2,...". For more information, see
+ * http://dygraphs.com/data.html.
* @param {Object} attrs Various other attributes, e.g. errorBars determines
- * whether the input data contains error ranges.
+ * whether the input data contains error ranges. For a complete list of
+ * options, see http://dygraphs.com/options.html.
*/
Dygraph = function(div, data, opts) {
if (arguments.length > 0) {
@@ -68,6 +73,10 @@ Dygraph.VERSION = "1.2";
Dygraph.__repr__ = function() {
return "[" + this.NAME + " " + this.VERSION + "]";
};
+
+/**
+ * Returns information about the Dygraph class.
+ */
Dygraph.toString = function() {
return this.__repr__();
};
@@ -76,13 +85,6 @@ Dygraph.toString = function() {
Dygraph.DEFAULT_ROLL_PERIOD = 1;
Dygraph.DEFAULT_WIDTH = 480;
Dygraph.DEFAULT_HEIGHT = 320;
-Dygraph.AXIS_LINE_WIDTH = 0.3;
-
-Dygraph.LOG_SCALE = 10;
-Dygraph.LN_TEN = Math.log(Dygraph.LOG_SCALE);
-Dygraph.log10 = function(x) {
- return Math.log(x) / Dygraph.LN_TEN;
-}
// Default attribute values.
Dygraph.DEFAULT_ATTRS = {
@@ -144,14 +146,20 @@ Dygraph.DEFAULT_ATTRS = {
xLabelHeight: 18,
yLabelWidth: 18,
- interactionModel: null // will be set to Dygraph.defaultInteractionModel.
-};
+ drawXAxis: true,
+ drawYAxis: true,
+ axisLineColor: "black",
+ axisLineWidth: 0.3,
+ gridLineWidth: 0.3,
+ axisLabelColor: "black",
+ axisLabelFont: "Arial", // TODO(danvk): is this implemented?
+ axisLabelWidth: 50,
+ drawYGrid: true,
+ drawXGrid: true,
+ gridLineColor: "rgb(128,128,128)",
-// Various logging levels.
-Dygraph.DEBUG = 1;
-Dygraph.INFO = 2;
-Dygraph.WARNING = 3;
-Dygraph.ERROR = 3;
+ interactionModel: null // will be set to Dygraph.Interaction.defaultModel
+};
// Directions for panning and zooming. Use bit operations when combined
// values are possible.
@@ -217,31 +225,25 @@ Dygraph.prototype.__init__ = function(div, file, attrs) {
// div, then only one will be drawn.
div.innerHTML = "";
- // If the div isn't already sized then inherit from our attrs or
- // give it a default size.
- if (div.style.width == '') {
- div.style.width = (attrs.width || Dygraph.DEFAULT_WIDTH) + "px";
- }
- if (div.style.height == '') {
- div.style.height = (attrs.height || Dygraph.DEFAULT_HEIGHT) + "px";
- }
- this.width_ = parseInt(div.style.width, 10);
- this.height_ = parseInt(div.style.height, 10);
- // 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) {
- this.width_ = div.offsetWidth;
+ // For historical reasons, the 'width' and 'height' options trump all CSS
+ // rules _except_ for an explicit 'width' or 'height' on the div.
+ // As an added convenience, if the div has zero height (like
does
+ // without any styles), then we use a default height/width.
+ if (div.style.width == '' && attrs.width) {
+ div.style.width = attrs.width + "px";
}
- if (div.style.height.indexOf("%") == div.style.height.length - 1) {
- this.height_ = div.offsetHeight;
+ if (div.style.height == '' && attrs.height) {
+ div.style.height = attrs.height + "px";
}
-
- 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.");
+ if (div.style.height == '' && div.offsetHeight == 0) {
+ div.style.height = Dygraph.DEFAULT_HEIGHT + "px";
+ if (div.style.width == '') {
+ div.style.width = Dygraph.DEFAULT_WIDTH + "px";
+ }
}
+ // these will be zero if the dygraph's div is hidden.
+ this.width_ = div.offsetWidth;
+ this.height_ = div.offsetHeight;
// TODO(danvk): set fillGraph to be part of attrs_ here, not user_attrs_.
if (attrs['stackedGraph']) {
@@ -266,9 +268,6 @@ Dygraph.prototype.__init__ = function(div, file, attrs) {
this.boundaryIds_ = [];
- // Make a note of whether labels will be pulled from the CSV file.
- this.labelsFromCSV_ = (this.attr_("labels") == null);
-
// Create the containing DIV and other interactive elements
this.createInterface_();
@@ -291,12 +290,26 @@ Dygraph.prototype.isZoomed = function(axis) {
throw "axis parameter to Dygraph.isZoomed must be missing, 'x' or 'y'.";
};
+/**
+ * Returns information about the Dygraph object, including its containing ID.
+ */
Dygraph.prototype.toString = function() {
var maindiv = this.maindiv_;
var id = (maindiv && maindiv.id) ? maindiv.id : maindiv
return "[Dygraph " + id + "]";
}
+/**
+ * @private
+ * Returns the value of an option. This may be set by the user (either in the
+ * constructor or by calling updateOptions) or by dygraphs, and may be set to a
+ * per-series value.
+ * @param { String } name The name of the option, e.g. 'rollPeriod'.
+ * @param { String } [seriesName] The name of the series to which the option
+ * will be applied. If no per-series value of this option is available, then
+ * the global value is returned. This is optional.
+ * @return { ... } The value of the option.
+ */
Dygraph.prototype.attr_ = function(name, seriesName) {
//
if (typeof(Dygraph.OPTIONS_REFERENCE) === 'undefined') {
@@ -322,35 +335,6 @@ Dygraph.prototype.attr_ = function(name, seriesName) {
}
};
-// TODO(danvk): any way I can get the line numbers to be this.warn call?
-Dygraph.prototype.log = function(severity, message) {
- if (typeof(console) != 'undefined') {
- switch (severity) {
- case Dygraph.DEBUG:
- console.debug('dygraphs: ' + message);
- break;
- case Dygraph.INFO:
- console.info('dygraphs: ' + message);
- break;
- case Dygraph.WARNING:
- console.warn('dygraphs: ' + message);
- break;
- case Dygraph.ERROR:
- console.error('dygraphs: ' + message);
- break;
- }
- }
-}
-Dygraph.prototype.info = function(message) {
- this.log(Dygraph.INFO, message);
-}
-Dygraph.prototype.warn = function(message) {
- this.log(Dygraph.WARNING, message);
-}
-Dygraph.prototype.error = function(message) {
- this.log(Dygraph.ERROR, message);
-}
-
/**
* Returns the current rolling period, as set by the user or an option.
* @return {Number} The number of points in the rolling window
@@ -387,9 +371,11 @@ Dygraph.prototype.xAxisExtremes = function() {
*/
Dygraph.prototype.yAxisRange = function(idx) {
if (typeof(idx) == "undefined") idx = 0;
- if (idx < 0 || idx >= this.axes_.length) return null;
- return [ this.axes_[idx].computedValueRange[0],
- this.axes_[idx].computedValueRange[1] ];
+ if (idx < 0 || idx >= this.axes_.length) {
+ return null;
+ }
+ var axis = this.axes_[idx];
+ return [ axis.computedValueRange[0], axis.computedValueRange[1] ];
};
/**
@@ -495,7 +481,7 @@ Dygraph.prototype.toDataYCoord = function(y, axis) {
if (typeof(axis) == "undefined") axis = 0;
if (!this.axes_[axis].logscale) {
- return yRange[0] + (area.h - y) / area.h * (yRange[1] - yRange[0]);
+ return yRange[0] + (area.y + area.h - y) / area.h * (yRange[1] - yRange[0]);
} else {
// Computing the inverse of toDomCoord.
var pct = (y - area.y) / area.h
@@ -535,6 +521,10 @@ Dygraph.prototype.toDataYCoord = function(y, axis) {
*
* If y is null, this returns null.
* if axis is null, this uses the first axis.
+ *
+ * @param { Number } y The data y-coordinate.
+ * @param { Number } [axis] The axis number on which the data coordinate lives.
+ * @return { Number } A fraction in [0, 1] where 0 = the top edge.
*/
Dygraph.prototype.toPercentYCoord = function(y, axis) {
if (y == null) {
@@ -568,6 +558,8 @@ Dygraph.prototype.toPercentYCoord = function(y, axis) {
* values can fall outside the canvas.
*
* If x is null, this returns null.
+ * @param { Number } x The data x-coordinate.
+ * @return { Number } A fraction in [0, 1] where 0 = the left edge.
*/
Dygraph.prototype.toPercentXCoord = function(x) {
if (x == null) {
@@ -576,10 +568,11 @@ Dygraph.prototype.toPercentXCoord = function(x) {
var xRange = this.xAxisRange();
return (x - xRange[0]) / (xRange[1] - xRange[0]);
-}
+};
/**
* Returns the number of columns (including the independent variable).
+ * @return { Integer } The number of columns.
*/
Dygraph.prototype.numColumns = function() {
return this.rawData_[0].length;
@@ -587,6 +580,7 @@ Dygraph.prototype.numColumns = function() {
/**
* Returns the number of rows (excluding any header/label row).
+ * @return { Integer } The number of rows, less any header.
*/
Dygraph.prototype.numRows = function() {
return this.rawData_.length;
@@ -596,6 +590,11 @@ Dygraph.prototype.numRows = function() {
* Returns the value in the given row and column. If the row and column exceed
* the bounds on the data, returns null. Also returns null if the value is
* missing.
+ * @param { Number} row The row number of the data (0-based). Row 0 is the
+ * first row of data, not a header row.
+ * @param { Number} col The column number of the data (0-based)
+ * @return { Number } The value in the specified cell or null if the row/col
+ * were out of range.
*/
Dygraph.prototype.getValue = function(row, col) {
if (row < 0 || row > this.rawData_.length) return null;
@@ -604,36 +603,6 @@ Dygraph.prototype.getValue = function(row, col) {
return this.rawData_[row][col];
};
-Dygraph.addEvent = function(el, evt, fn) {
- var normed_fn = function(e) {
- if (!e) var e = window.event;
- fn(e);
- };
- if (window.addEventListener) { // Mozilla, Netscape, Firefox
- el.addEventListener(evt, normed_fn, false);
- } else { // IE
- el.attachEvent('on' + evt, normed_fn);
- }
-};
-
-
-// Based on the article at
-// http://www.switchonthecode.com/tutorials/javascript-tutorial-the-scroll-wheel
-Dygraph.cancelEvent = function(e) {
- e = e ? e : window.event;
- if (e.stopPropagation) {
- e.stopPropagation();
- }
- if (e.preventDefault) {
- e.preventDefault();
- }
- e.cancelBubble = true;
- e.cancel = true;
- e.returnValue = false;
- return false;
-}
-
-
/**
* Generates interface elements for the Dygraph: a containing div, a div to
* display the current point, and a textbox to adjust the rolling average
@@ -657,8 +626,11 @@ Dygraph.prototype.createInterface_ = function() {
this.canvas_.style.width = this.width_ + "px"; // for IE
this.canvas_.style.height = this.height_ + "px"; // for IE
+ 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.graphDiv.appendChild(this.hidden_);
@@ -674,21 +646,7 @@ Dygraph.prototype.createInterface_ = function() {
});
// Create the grapher
- // TODO(danvk): why does the Layout need its own set of options?
- this.layoutOptions_ = { 'xOriginIsZero': false };
- Dygraph.update(this.layoutOptions_, this.attrs_);
- Dygraph.update(this.layoutOptions_, this.user_attrs_);
- Dygraph.update(this.layoutOptions_, {
- 'errorBars': (this.attr_("errorBars") || this.attr_("customBars")) });
-
- this.layout_ = new DygraphLayout(this, this.layoutOptions_);
-
- // TODO(danvk): why does the Renderer need its own set of options?
- this.renderOptions_ = { colorScheme: this.colors_,
- strokeColor: null,
- axisLineWidth: Dygraph.AXIS_LINE_WIDTH };
- Dygraph.update(this.renderOptions_, this.attrs_);
- Dygraph.update(this.renderOptions_, this.user_attrs_);
+ this.layout_ = new DygraphLayout(this);
this.createStatusMessage_();
this.createDragInterface_();
@@ -723,8 +681,9 @@ Dygraph.prototype.destroy = function() {
};
/**
- * Creates the canvas containing the PlotKit graph. Only plotkit ever draws on
- * this particular canvas. All Dygraph work is done on this.canvas_.
+ * Creates the canvas on which the chart will be drawn. Only the Renderer ever
+ * draws on this particular canvas. All Dygraph work (i.e. drawing hover dots
+ * or the zoom rectangles) is done on this.canvas_.
* @param {Object} canvas The Dygraph canvas over which to overlay the plot
* @return {Object} The newly-created canvas
* @private
@@ -744,38 +703,6 @@ Dygraph.prototype.createPlotKitCanvas_ = function(canvas) {
return h;
};
-// Taken from MochiKit.Color
-Dygraph.hsvToRGB = function (hue, saturation, value) {
- var red;
- var green;
- var blue;
- if (saturation === 0) {
- red = value;
- green = value;
- blue = value;
- } else {
- var i = Math.floor(hue * 6);
- var f = (hue * 6) - i;
- var p = value * (1 - saturation);
- var q = value * (1 - (saturation * f));
- var t = value * (1 - (saturation * (1 - f)));
- switch (i) {
- case 1: red = q; green = value; blue = p; break;
- case 2: red = p; green = value; blue = t; break;
- case 3: red = p; green = q; blue = value; break;
- case 4: red = t; green = p; blue = value; break;
- case 5: red = value; green = p; blue = q; break;
- case 6: // fall through
- case 0: red = value; green = t; blue = p; break;
- }
- }
- red = Math.floor(255 * red + 0.5);
- green = Math.floor(255 * green + 0.5);
- blue = Math.floor(255 * blue + 0.5);
- return 'rgb(' + red + ',' + green + ',' + blue + ')';
-};
-
-
/**
* Generate a set of distinct colors for the data series. This is done with a
* color wheel. Saturation/Value are customizable, and the hue is
@@ -784,8 +711,6 @@ Dygraph.hsvToRGB = function (hue, saturation, value) {
* @private
*/
Dygraph.prototype.setColors_ = function() {
- // TODO(danvk): compute this directly into this.attrs_['colorScheme'] and do
- // away with this.renderOptions_.
var num = this.attr_("labels").length - 1;
this.colors_ = [];
var colors = this.attr_('colors');
@@ -808,57 +733,18 @@ Dygraph.prototype.setColors_ = function() {
}
}
- // TODO(danvk): update this w/r/t/ the new options system.
- this.renderOptions_.colorScheme = this.colors_;
- Dygraph.update(this.plotter_.options, this.renderOptions_);
- Dygraph.update(this.layoutOptions_, this.user_attrs_);
- Dygraph.update(this.layoutOptions_, this.attrs_);
-}
+ this.plotter_.setColors(this.colors_);
+};
/**
* Return the list of colors. This is either the list of colors passed in the
- * attributes, or the autogenerated list of rgb(r,g,b) strings.
+ * attributes or the autogenerated list of rgb(r,g,b) strings.
* @return {Array} The list of colors.
*/
Dygraph.prototype.getColors = function() {
return this.colors_;
};
-// The following functions are from quirksmode.org with a modification for Safari from
-// http://blog.firetree.net/2005/07/04/javascript-find-position/
-// http://www.quirksmode.org/js/findpos.html
-Dygraph.findPosX = function(obj) {
- var curleft = 0;
- if(obj.offsetParent)
- while(1)
- {
- curleft += obj.offsetLeft;
- if(!obj.offsetParent)
- break;
- obj = obj.offsetParent;
- }
- else if(obj.x)
- curleft += obj.x;
- return curleft;
-};
-
-Dygraph.findPosY = function(obj) {
- var curtop = 0;
- if(obj.offsetParent)
- while(1)
- {
- curtop += obj.offsetTop;
- if(!obj.offsetParent)
- break;
- obj = obj.offsetParent;
- }
- else if(obj.y)
- curtop += obj.y;
- return curtop;
-};
-
-
-
/**
* Create the div that contains information on the selected point(s)
* This goes in the top right of the canvas, unless an external div has already
@@ -899,6 +785,7 @@ Dygraph.prototype.createStatusMessage_ = function() {
* Position the labels div so that:
* - its right edge is flush with the right edge of the charting area
* - its top edge is flush with the top edge of the charting area
+ * @private
*/
Dygraph.prototype.positionLabelsDiv_ = function() {
// Don't touch a user-specified labelsDiv.
@@ -944,338 +831,24 @@ Dygraph.prototype.createRollInterface_ = function() {
this.roller_.onchange = function() { dygraph.adjustRoll(dygraph.roller_.value); };
};
-// These functions are taken from MochiKit.Signal
-Dygraph.pageX = function(e) {
- if (e.pageX) {
- return (!e.pageX || e.pageX < 0) ? 0 : e.pageX;
- } else {
- var de = document;
- var b = document.body;
- return e.clientX +
- (de.scrollLeft || b.scrollLeft) -
- (de.clientLeft || 0);
- }
-};
-
-Dygraph.pageY = function(e) {
- if (e.pageY) {
- return (!e.pageY || e.pageY < 0) ? 0 : e.pageY;
- } else {
- var de = document;
- var b = document.body;
- return e.clientY +
- (de.scrollTop || b.scrollTop) -
- (de.clientTop || 0);
- }
-};
-
+/**
+ * @private
+ * Converts page the x-coordinate of the event to pixel x-coordinates on the
+ * canvas (i.e. DOM Coords).
+ */
Dygraph.prototype.dragGetX_ = function(e, context) {
return Dygraph.pageX(e) - context.px
};
+/**
+ * @private
+ * Converts page the y-coordinate of the event to pixel y-coordinates on the
+ * canvas (i.e. DOM Coords).
+ */
Dygraph.prototype.dragGetY_ = function(e, context) {
return Dygraph.pageY(e) - context.py
};
-// Called in response to an interaction model operation that
-// should start the default panning behavior.
-//
-// It's used in the default callback for "mousedown" operations.
-// Custom interaction model builders can use it to provide the default
-// panning behavior.
-//
-Dygraph.startPan = function(event, g, context) {
- context.isPanning = true;
- var xRange = g.xAxisRange();
- context.dateRange = xRange[1] - xRange[0];
- context.initialLeftmostDate = xRange[0];
- context.xUnitsPerPixel = context.dateRange / (g.plotter_.area.w - 1);
-
- if (g.attr_("panEdgeFraction")) {
- var maxXPixelsToDraw = g.width_ * g.attr_("panEdgeFraction");
- var xExtremes = g.xAxisExtremes(); // I REALLY WANT TO CALL THIS xTremes!
-
- var boundedLeftX = g.toDomXCoord(xExtremes[0]) - maxXPixelsToDraw;
- var boundedRightX = g.toDomXCoord(xExtremes[1]) + maxXPixelsToDraw;
-
- var boundedLeftDate = g.toDataXCoord(boundedLeftX);
- var boundedRightDate = g.toDataXCoord(boundedRightX);
- context.boundedDates = [boundedLeftDate, boundedRightDate];
-
- var boundedValues = [];
- var maxYPixelsToDraw = g.height_ * g.attr_("panEdgeFraction");
-
- for (var i = 0; i < g.axes_.length; i++) {
- var axis = g.axes_[i];
- var yExtremes = axis.extremeRange;
-
- var boundedTopY = g.toDomYCoord(yExtremes[0], i) + maxYPixelsToDraw;
- var boundedBottomY = g.toDomYCoord(yExtremes[1], i) - maxYPixelsToDraw;
-
- var boundedTopValue = g.toDataYCoord(boundedTopY);
- var boundedBottomValue = g.toDataYCoord(boundedBottomY);
-
- boundedValues[i] = [boundedTopValue, boundedBottomValue];
- }
- context.boundedValues = boundedValues;
- }
-
- // Record the range of each y-axis at the start of the drag.
- // If any axis has a valueRange or valueWindow, then we want a 2D pan.
- context.is2DPan = false;
- for (var i = 0; i < g.axes_.length; i++) {
- var axis = g.axes_[i];
- var yRange = g.yAxisRange(i);
- // TODO(konigsberg): These values should be in |context|.
- // In log scale, initialTopValue, dragValueRange and unitsPerPixel are log scale.
- if (axis.logscale) {
- axis.initialTopValue = Dygraph.log10(yRange[1]);
- axis.dragValueRange = Dygraph.log10(yRange[1]) - Dygraph.log10(yRange[0]);
- } else {
- axis.initialTopValue = yRange[1];
- axis.dragValueRange = yRange[1] - yRange[0];
- }
- axis.unitsPerPixel = axis.dragValueRange / (g.plotter_.area.h - 1);
-
- // While calculating axes, set 2dpan.
- if (axis.valueWindow || axis.valueRange) context.is2DPan = true;
- }
-};
-
-// Called in response to an interaction model operation that
-// responds to an event that pans the view.
-//
-// It's used in the default callback for "mousemove" operations.
-// Custom interaction model builders can use it to provide the default
-// panning behavior.
-//
-Dygraph.movePan = function(event, g, context) {
- context.dragEndX = g.dragGetX_(event, context);
- context.dragEndY = g.dragGetY_(event, context);
-
- var minDate = context.initialLeftmostDate -
- (context.dragEndX - context.dragStartX) * context.xUnitsPerPixel;
- if (context.boundedDates) {
- minDate = Math.max(minDate, context.boundedDates[0]);
- }
- var maxDate = minDate + context.dateRange;
- if (context.boundedDates) {
- if (maxDate > context.boundedDates[1]) {
- // Adjust minDate, and recompute maxDate.
- minDate = minDate - (maxDate - context.boundedDates[1]);
- maxDate = minDate + context.dateRange;
- }
- }
-
- g.dateWindow_ = [minDate, maxDate];
-
- // y-axis scaling is automatic unless this is a full 2D pan.
- if (context.is2DPan) {
- // Adjust each axis appropriately.
- for (var i = 0; i < g.axes_.length; i++) {
- var axis = g.axes_[i];
-
- var pixelsDragged = context.dragEndY - context.dragStartY;
- var unitsDragged = pixelsDragged * axis.unitsPerPixel;
-
- var boundedValue = context.boundedValues ? context.boundedValues[i] : null;
-
- // In log scale, maxValue and minValue are the logs of those values.
- var maxValue = axis.initialTopValue + unitsDragged;
- if (boundedValue) {
- maxValue = Math.min(maxValue, boundedValue[1]);
- }
- var minValue = maxValue - axis.dragValueRange;
- if (boundedValue) {
- if (minValue < boundedValue[0]) {
- // Adjust maxValue, and recompute minValue.
- maxValue = maxValue - (minValue - boundedValue[0]);
- minValue = maxValue - axis.dragValueRange;
- }
- }
- if (axis.logscale) {
- axis.valueWindow = [ Math.pow(Dygraph.LOG_SCALE, minValue),
- Math.pow(Dygraph.LOG_SCALE, maxValue) ];
- } else {
- axis.valueWindow = [ minValue, maxValue ];
- }
- }
- }
-
- g.drawGraph_();
-}
-
-// Called in response to an interaction model operation that
-// responds to an event that ends panning.
-//
-// It's used in the default callback for "mouseup" operations.
-// Custom interaction model builders can use it to provide the default
-// panning behavior.
-//
-Dygraph.endPan = function(event, g, context) {
- // TODO(konigsberg): Clear the context data from the axis.
- // TODO(konigsberg): mouseup should just delete the
- // context object, and mousedown should create a new one.
- context.isPanning = false;
- context.is2DPan = false;
- context.initialLeftmostDate = null;
- context.dateRange = null;
- context.valueRange = null;
- context.boundedDates = null;
- context.boundedValues = null;
-}
-
-// Called in response to an interaction model operation that
-// responds to an event that starts zooming.
-//
-// It's used in the default callback for "mousedown" operations.
-// Custom interaction model builders can use it to provide the default
-// zooming behavior.
-//
-Dygraph.startZoom = function(event, g, context) {
- context.isZooming = true;
-}
-
-// Called in response to an interaction model operation that
-// responds to an event that defines zoom boundaries.
-//
-// It's used in the default callback for "mousemove" operations.
-// Custom interaction model builders can use it to provide the default
-// zooming behavior.
-//
-Dygraph.moveZoom = function(event, g, context) {
- context.dragEndX = g.dragGetX_(event, context);
- context.dragEndY = g.dragGetY_(event, context);
-
- var xDelta = Math.abs(context.dragStartX - context.dragEndX);
- var yDelta = Math.abs(context.dragStartY - context.dragEndY);
-
- // drag direction threshold for y axis is twice as large as x axis
- context.dragDirection = (xDelta < yDelta / 2) ? Dygraph.VERTICAL : Dygraph.HORIZONTAL;
-
- g.drawZoomRect_(
- context.dragDirection,
- context.dragStartX,
- context.dragEndX,
- context.dragStartY,
- context.dragEndY,
- context.prevDragDirection,
- context.prevEndX,
- context.prevEndY);
-
- context.prevEndX = context.dragEndX;
- context.prevEndY = context.dragEndY;
- context.prevDragDirection = context.dragDirection;
-}
-
-// Called in response to an interaction model operation that
-// responds to an event that performs a zoom based on previously defined
-// bounds..
-//
-// It's used in the default callback for "mouseup" operations.
-// Custom interaction model builders can use it to provide the default
-// zooming behavior.
-//
-Dygraph.endZoom = function(event, g, context) {
- context.isZooming = false;
- context.dragEndX = g.dragGetX_(event, context);
- context.dragEndY = g.dragGetY_(event, context);
- var regionWidth = Math.abs(context.dragEndX - context.dragStartX);
- var regionHeight = Math.abs(context.dragEndY - context.dragStartY);
-
- if (regionWidth < 2 && regionHeight < 2 &&
- g.lastx_ != undefined && g.lastx_ != -1) {
- // TODO(danvk): pass along more info about the points, e.g. 'x'
- if (g.attr_('clickCallback') != null) {
- g.attr_('clickCallback')(event, g.lastx_, g.selPoints_);
- }
- if (g.attr_('pointClickCallback')) {
- // check if the click was on a particular point.
- var closestIdx = -1;
- var closestDistance = 0;
- for (var i = 0; i < g.selPoints_.length; i++) {
- var p = g.selPoints_[i];
- var distance = Math.pow(p.canvasx - context.dragEndX, 2) +
- Math.pow(p.canvasy - context.dragEndY, 2);
- if (closestIdx == -1 || distance < closestDistance) {
- closestDistance = distance;
- closestIdx = i;
- }
- }
-
- // Allow any click within two pixels of the dot.
- var radius = g.attr_('highlightCircleSize') + 2;
- if (closestDistance <= 5 * 5) {
- g.attr_('pointClickCallback')(event, g.selPoints_[closestIdx]);
- }
- }
- }
-
- if (regionWidth >= 10 && context.dragDirection == Dygraph.HORIZONTAL) {
- g.doZoomX_(Math.min(context.dragStartX, context.dragEndX),
- Math.max(context.dragStartX, context.dragEndX));
- } else if (regionHeight >= 10 && context.dragDirection == Dygraph.VERTICAL) {
- g.doZoomY_(Math.min(context.dragStartY, context.dragEndY),
- Math.max(context.dragStartY, context.dragEndY));
- } else {
- g.canvas_.getContext("2d").clearRect(0, 0,
- g.canvas_.width,
- g.canvas_.height);
- }
- context.dragStartX = null;
- context.dragStartY = null;
-}
-
-Dygraph.defaultInteractionModel = {
- // Track the beginning of drag events
- mousedown: function(event, g, context) {
- context.initializeMouseDown(event, g, context);
-
- if (event.altKey || event.shiftKey) {
- Dygraph.startPan(event, g, context);
- } else {
- Dygraph.startZoom(event, g, context);
- }
- },
-
- // Draw zoom rectangles when the mouse is down and the user moves around
- mousemove: function(event, g, context) {
- if (context.isZooming) {
- Dygraph.moveZoom(event, g, context);
- } else if (context.isPanning) {
- Dygraph.movePan(event, g, context);
- }
- },
-
- mouseup: function(event, g, context) {
- if (context.isZooming) {
- Dygraph.endZoom(event, g, context);
- } else if (context.isPanning) {
- Dygraph.endPan(event, g, context);
- }
- },
-
- // Temporarily cancel the dragging event when the mouse leaves the graph
- mouseout: function(event, g, context) {
- if (context.isZooming) {
- context.dragEndX = null;
- context.dragEndY = null;
- }
- },
-
- // Disable zooming out if panning.
- dblclick: function(event, g, context) {
- if (event.altKey || event.shiftKey) {
- return;
- }
- // TODO(konigsberg): replace g.doUnzoom()_ with something that is
- // friendlier to public use.
- g.doUnzoom_();
- }
-};
-
-Dygraph.DEFAULT_ATTRS.interactionModel = Dygraph.defaultInteractionModel;
-
/**
* Set up all the mouse handlers needed to capture dragging behavior for zoom
* events.
@@ -1287,13 +860,13 @@ Dygraph.prototype.createDragInterface_ = function() {
isZooming: false,
isPanning: false, // is this drag part of a pan?
is2DPan: false, // if so, is that pan 1- or 2-dimensional?
- dragStartX: null,
- dragStartY: null,
- dragEndX: null,
- dragEndY: null,
+ dragStartX: null, // pixel coordinates
+ dragStartY: null, // pixel coordinates
+ dragEndX: null, // pixel coordinates
+ dragEndY: null, // pixel coordinates
dragDirection: null,
- prevEndX: null,
- prevEndY: null,
+ prevEndX: null, // pixel coordinates
+ prevEndY: null, // pixel coordinates
prevDragDirection: null,
// The value on the left side of the graph when a pan operation starts.
@@ -1308,7 +881,8 @@ Dygraph.prototype.createDragInterface_ = function() {
// panning operation.
dateRange: null,
- // Utility function to convert page-wide coordinates to canvas coords
+ // Top-left corner of the canvas, in DOM coords
+ // TODO(konigsberg): Rename topLeftCanvasX, topLeftCanvasY.
px: 0,
py: 0,
@@ -1398,7 +972,7 @@ Dygraph.prototype.createDragInterface_ = function() {
Dygraph.prototype.drawZoomRect_ = function(direction, startX, endX, startY,
endY, prevDirection, prevEndX,
prevEndY) {
- var ctx = this.canvas_.getContext("2d");
+ var ctx = this.canvas_ctx_;
// Clean up from the previous rect if necessary
if (prevDirection == Dygraph.HORIZONTAL) {
@@ -1512,6 +1086,9 @@ Dygraph.prototype.doUnzoom_ = function() {
}
}
+ // Clear any selection, since it's likely to be drawn in the wrong place.
+ this.clearSelection();
+
if (dirty) {
// Putting the drawing operation before the callback because it resets
// yAxisRange.
@@ -1615,11 +1192,15 @@ Dygraph.prototype.idxToRow_ = function(idx) {
return -1;
};
-// TODO(danvk): rename this function to something like 'isNonZeroNan'.
-Dygraph.isOK = function(x) {
- return x && !isNaN(x);
-};
-
+/**
+ * @private
+ * Generates HTML for the legend which is displayed when hovering over the
+ * chart. If no selected points are specified, a default legend is returned
+ * (this may just be the empty string).
+ * @param { Number } [x] The x-value of the selected points.
+ * @param { [Object] } [sel_points] List of selected points for the given
+ * x-value. Should have properties like 'name', 'yval' and 'canvasy'.
+ */
Dygraph.prototype.generateLegendHTML_ = function(x, sel_points) {
// If no points are selected, we display a default legend. Traditionally,
// this has been blank. But a better default would be a conventional legend,
@@ -1652,7 +1233,7 @@ Dygraph.prototype.generateLegendHTML_ = function(x, sel_points) {
if (sepLines) html += " ";
var c = this.plotter_.colors[pt.name];
- var yval = fmtFunc(pt.yval, displayDigits);
+ var yval = fmtFunc(pt.yval, this);
// TODO(danvk): use a template string here and make it an attribute.
html += " "
+ pt.name + ":"
@@ -1662,13 +1243,34 @@ Dygraph.prototype.generateLegendHTML_ = function(x, sel_points) {
};
/**
+ * @private
+ * Displays information about the selected points in the legend. If there is no
+ * selection, the legend will be cleared.
+ * @param { Number } [x] The x-value of the selected points.
+ * @param { [Object] } [sel_points] List of selected points for the given
+ * x-value. Should have properties like 'name', 'yval' and 'canvasy'.
+ */
+Dygraph.prototype.setLegendHTML_ = function(x, sel_points) {
+ var html = this.generateLegendHTML_(x, sel_points);
+ var labelsDiv = this.attr_("labelsDiv");
+ if (labelsDiv !== null) {
+ labelsDiv.innerHTML = html;
+ } else {
+ if (typeof(this.shown_legend_error_) == 'undefined') {
+ this.error('labelsDiv is set to something nonexistent; legend will not be shown.');
+ this.shown_legend_error_ = true;
+ }
+ }
+};
+
+/**
* Draw dots over the selectied points in the data series. This function
* takes care of cleanup of previously-drawn dots.
* @private
*/
Dygraph.prototype.updateSelection_ = function() {
// Clear the previously drawn vertical, if there is one
- var ctx = this.canvas_.getContext("2d");
+ var ctx = this.canvas_ctx_;
if (this.previousVerticalX_ >= 0) {
// Determine the maximum highlight circle size.
var maxCircleSize = 0;
@@ -1685,8 +1287,7 @@ Dygraph.prototype.updateSelection_ = function() {
if (this.selPoints_.length > 0) {
// Set the status message to indicate the selected point(s)
if (this.attr_('showLabelsOnHighlight')) {
- var html = this.generateLegendHTML_(this.lastx_, this.selPoints_);
- this.attr_("labelsDiv").innerHTML = html;
+ this.setLegendHTML_(this.lastx_, this.selPoints_);
}
// Draw colored circles over the center of each selected point
@@ -1709,10 +1310,11 @@ Dygraph.prototype.updateSelection_ = function() {
};
/**
- * Set manually set selected dots, and display information about them
- * @param int row number that should by highlighted
- * false value clears the selection
- * @public
+ * Manually set the selected points and display information about them in the
+ * legend. The selection can be cleared using clearSelection() and queried
+ * using getSelection().
+ * @param { Integer } row number that should be highlighted (i.e. appear with
+ * hover dots on the chart). Set to false to clear any selection.
*/
Dygraph.prototype.setSelection = function(row) {
// Extract the points we've selected
@@ -1763,22 +1365,21 @@ Dygraph.prototype.mouseOut_ = function(event) {
};
/**
- * Remove all selection from the canvas
- * @public
+ * Clears the current selection (i.e. points that were highlighted by moving
+ * the mouse over the chart).
*/
Dygraph.prototype.clearSelection = function() {
// Get rid of the overlay data
- var ctx = this.canvas_.getContext("2d");
- ctx.clearRect(0, 0, this.width_, this.height_);
- this.attr_('labelsDiv').innerHTML = this.generateLegendHTML_();
+ this.canvas_ctx_.clearRect(0, 0, this.width_, this.height_);
+ this.setLegendHTML_();
this.selPoints_ = [];
this.lastx_ = -1;
}
/**
- * Returns the number of the currently selected row
- * @return int row number, of -1 if nothing is selected
- * @public
+ * Returns the number of the currently selected row. To get data for this row,
+ * you can use the getValue method.
+ * @return { Integer } row number, or -1 if nothing is selected
*/
Dygraph.prototype.getSelection = function() {
if (!this.selPoints_ || this.selPoints_.length < 1) {
@@ -1794,48 +1395,7 @@ Dygraph.prototype.getSelection = function() {
};
/**
- * Number formatting function which mimicks the behavior of %g in printf, i.e.
- * either exponential or fixed format (without trailing 0s) is used depending on
- * the length of the generated string. The advantage of this format is that
- * there is a predictable upper bound on the resulting string length,
- * significant figures are not dropped, and normal numbers are not displayed in
- * exponential notation.
- *
- * NOTE: JavaScript's native toPrecision() is NOT a drop-in replacement for %g.
- * It creates strings which are too long for absolute values between 10^-4 and
- * 10^-6, e.g. '0.00001' instead of '1e-5'. See tests/number-format.html for
- * output examples.
- *
- * @param {Number} x The number to format
- * @param {Number} opt_precision The precision to use, default 2.
- * @return {String} A string formatted like %g in printf. The max generated
- * string length should be precision + 6 (e.g 1.123e+300).
- */
-Dygraph.floatFormat = function(x, opt_precision) {
- // Avoid invalid precision values; [1, 21] is the valid range.
- var p = Math.min(Math.max(1, opt_precision || 2), 21);
-
- // This is deceptively simple. The actual algorithm comes from:
- //
- // Max allowed length = p + 4
- // where 4 comes from 'e+n' and '.'.
- //
- // Length of fixed format = 2 + y + p
- // where 2 comes from '0.' and y = # of leading zeroes.
- //
- // Equating the two and solving for y yields y = 2, or 0.00xxxx which is
- // 1.0e-3.
- //
- // Since the behavior of toPrecision() is identical for larger numbers, we
- // don't have to worry about the other bound.
- //
- // Finally, the argument for toExponential() is the number of trailing digits,
- // so we take off 1 for the value before the '.'.
- return (Math.abs(x) < 1.0e-3 && x != 0.0) ?
- x.toExponential(p - 1) : x.toPrecision(p);
-};
-
-/**
+ * @private
* Return a string version of a number. This respects the digitsAfterDecimal
* and maxNumberWidth options.
* @param {Number} x The number to be formatted
@@ -1862,28 +1422,6 @@ Dygraph.numberFormatter = function(x, g) {
}
};
-Dygraph.zeropad = function(x) {
- if (x < 10) return "0" + x; else return "" + x;
-};
-
-/**
- * Return a string version of the hours, minutes and seconds portion of a date.
- * @param {Number} date The JavaScript date (ms since epoch)
- * @return {String} A time of the form "HH:MM:SS"
- * @private
- */
-Dygraph.hmsString_ = function(date) {
- var zeropad = Dygraph.zeropad;
- var d = new Date(date);
- if (d.getSeconds()) {
- return zeropad(d.getHours()) + ":" +
- zeropad(d.getMinutes()) + ":" +
- zeropad(d.getSeconds());
- } else {
- return zeropad(d.getHours()) + ":" + zeropad(d.getMinutes());
- }
-};
-
/**
* Convert a JS date to a string appropriate to display on an axis that
* is displaying values at the stated granularity.
@@ -1908,42 +1446,6 @@ Dygraph.dateAxisFormatter = function(date, granularity) {
};
/**
- * 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
- */
-Dygraph.dateString_ = function(date) {
- var zeropad = Dygraph.zeropad;
- var d = new Date(date);
-
- // Get the year:
- var year = "" + d.getFullYear();
- // Get a 0 padded month string
- var month = zeropad(d.getMonth() + 1); //months are 0-offset, sigh
- // Get a 0 padded day string
- var day = zeropad(d.getDate());
-
- var ret = "";
- var frac = d.getHours() * 3600 + d.getMinutes() * 60 + d.getSeconds();
- if (frac) ret = " " + Dygraph.hmsString_(date);
-
- return year + "/" + month + "/" + day + ret;
-};
-
-/**
- * Round a number to the specified number of digits past the decimal point.
- * @param {Number} num The number to round
- * @param {Number} places The number of decimals to which to round
- * @return {Number} The rounded number
- * @private
- */
-Dygraph.round_ = function(num, places) {
- var shift = Math.pow(10, places);
- return Math.round(num * shift)/shift;
-};
-
-/**
* Fires when there's data available to be graphed.
* @param {String} data Raw CSV data to be plotted
* @private
@@ -1971,7 +1473,7 @@ Dygraph.prototype.addXTicks_ = function() {
}
var xTicks = this.attr_('xTicker')(range[0], range[1], this);
- this.layout_.updateOptions({xTicks: xTicks});
+ this.layout_.setXTicks(xTicks);
};
// Time granularity enumeration
@@ -2015,11 +1517,11 @@ Dygraph.SHORT_SPACINGS[Dygraph.SIX_HOURLY] = 1000 * 3600 * 6;
Dygraph.SHORT_SPACINGS[Dygraph.DAILY] = 1000 * 86400;
Dygraph.SHORT_SPACINGS[Dygraph.WEEKLY] = 1000 * 604800;
-// NumXTicks()
-//
-// If we used this time granularity, how many ticks would there be?
-// This is only an approximation, but it's generally good enough.
-//
+/**
+ * @private
+ * If we used this time granularity, how many ticks would there be?
+ * This is only an approximation, but it's generally good enough.
+ */
Dygraph.prototype.NumXTicks = function(start_time, end_time, granularity) {
if (granularity < Dygraph.MONTHLY) {
// Generate one tick mark for every fixed interval of time.
@@ -2040,13 +1542,14 @@ Dygraph.prototype.NumXTicks = function(start_time, end_time, granularity) {
}
};
-// GetXAxis()
-//
-// Construct an x-axis of nicely-formatted times on meaningful boundaries
-// (e.g. 'Jan 09' rather than 'Jan 22, 2009').
-//
-// Returns an array containing {v: millis, label: label} dictionaries.
-//
+/**
+ * @private
+ *
+ * Construct an x-axis of nicely-formatted times on meaningful boundaries
+ * (e.g. 'Jan 09' rather than 'Jan 22, 2009').
+ *
+ * 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 = [];
@@ -2134,10 +1637,12 @@ Dygraph.prototype.GetXAxis = function(start_time, end_time, granularity) {
* Add ticks to the x-axis based on a date range.
* @param {Number} startDate Start of the date window (millis since epoch)
* @param {Number} endDate End of the date window (millis since epoch)
- * @return {Array.