*/
/**
- * 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) {
Dygraph.__repr__ = function() {
return "[" + this.NAME + " " + this.VERSION + "]";
};
-Dygraph.toString = function() {
- return this.__repr__();
-};
/**
- * Formatting to use for an integer number.
- *
- * @param {Number} x The number to format
- * @param {Number} unused_precision The precision to use, ignored.
- * @return {String} A string formatted like %g in printf. The max generated
- * string length should be precision + 6 (e.g 1.123e+300).
- */
-Dygraph.intFormat = function(x, unused_precision) {
- return x.toString();
-}
-
-/**
- * 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. 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).
+ * Returns information about the Dygraph class.
*/
-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);
+Dygraph.toString = function() {
+ return this.__repr__();
};
// Various default values
Dygraph.LOG_SCALE = 10;
Dygraph.LN_TEN = Math.log(Dygraph.LOG_SCALE);
+/**
+ * @private
+ */
Dygraph.log10 = function(x) {
return Math.log(x) / Dygraph.LN_TEN;
}
labelsKMG2: false,
showLabelsOnHighlight: true,
- yValueFormatter: function(x, opt_precision) {
- var s = Dygraph.floatFormat(x, opt_precision);
- var s2 = Dygraph.intFormat(x);
- return s.length < s2.length ? s : s2;
- },
+ yValueFormatter: function(a,b) { return Dygraph.numberFormatter(a,b); },
+ digitsAfterDecimal: 2,
+ maxNumberWidth: 6,
+ sigFigs: null,
strokeWidth: 1.0,
// Used for initializing annotation CSS rules only once.
Dygraph.addedAnnotationCSS = false;
+/**
+ * @private
+ * Return the 2d context for a dygraph canvas.
+ *
+ * This method is only exposed for the sake of replacing the function in
+ * automated tests, e.g.
+ *
+ * var oldFunc = Dygraph.getContext();
+ * Dygraph.getContext = function(canvas) {
+ * var realContext = oldFunc(canvas);
+ * return new Proxy(realContext);
+ * };
+ */
+Dygraph.getContext = function(canvas) {
+ return canvas.getContext("2d");
+};
+
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,
this.zoomed_x_ = false;
this.zoomed_y_ = false;
- // Number of digits to use when labeling the x (if numeric) and y axis
- // ticks.
- this.numXDigits_ = 2;
- this.numYDigits_ = 2;
-
- // When labeling x (if numeric) or y values in the legend, there are
- // numDigits + numExtraDigits of precision used. For axes labels with N
- // digits of precision, the data should be displayed with at least N+1 digits
- // of precision. The reason for this is to divide each interval between
- // successive ticks into tenths (for 1) or hundredths (for 2), etc. For
- // example, if the labels are [0, 1, 2], we want data to be displayed as
- // 0.1, 1.3, etc.
- this.numExtraDigits_ = 1;
-
// Clear the div. This ensure that, if multiple dygraphs are passed the same
// div, then only one will be drawn.
div.innerHTML = "";
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) {
// <REMOVE_FOR_COMBINED>
if (typeof(Dygraph.OPTIONS_REFERENCE) === 'undefined') {
};
// TODO(danvk): any way I can get the line numbers to be this.warn call?
+/**
+ * @private
+ * Log an error on the JS console at the given severity.
+ * @param { Integer } severity One of Dygraph.{DEBUG,INFO,WARNING,ERROR}
+ * @param { String } The message to log.
+ */
Dygraph.prototype.log = function(severity, message) {
if (typeof(console) != 'undefined') {
switch (severity) {
break;
}
}
-}
+};
+
+/**
+ * @private
+ */
Dygraph.prototype.info = function(message) {
this.log(Dygraph.INFO, message);
-}
+};
+
+/**
+ * @private
+ */
Dygraph.prototype.warn = function(message) {
this.log(Dygraph.WARNING, message);
-}
+};
+
+/**
+ * @private
+ */
Dygraph.prototype.error = function(message) {
this.log(Dygraph.ERROR, message);
-}
+};
/**
* Returns the current rolling period, as set by the user or an option.
*
* 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) {
* 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) {
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;
/**
* 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;
* 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;
return this.rawData_[row][col];
};
+/**
+ * @private
+ * Add an event handler. This smooths a difference between IE and the rest of
+ * the world.
+ * @param { DOM element } el The element to add the event to.
+ * @param { String } evt The name of the event, e.g. 'click' or 'mousemove'.
+ * @param { Function } fn The function to call on the event. The function takes
+ * one parameter: the event object.
+ */
Dygraph.addEvent = function(el, evt, fn) {
var normed_fn = function(e) {
if (!e) var e = window.event;
};
-// Based on the article at
-// http://www.switchonthecode.com/tutorials/javascript-tutorial-the-scroll-wheel
+/**
+ * @private
+ * Cancels further processing of an event. This is useful to prevent default
+ * browser actions, e.g. highlighting text on a double-click.
+ * Based on the article at
+ * http://www.switchonthecode.com/tutorials/javascript-tutorial-the-scroll-wheel
+ * @param { Event } e The event whose normal behavior should be canceled.
+ */
Dygraph.cancelEvent = function(e) {
e = e ? e : window.event;
if (e.stopPropagation) {
e.cancel = true;
e.returnValue = false;
return false;
-}
+};
/**
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_);
};
/**
- * 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
return h;
};
-// Taken from MochiKit.Color
+/**
+ * Convert hsv values to an rgb(r,g,b) string. Taken from MochiKit.Color. This
+ * is used to generate default series colors which are evenly spaced on the
+ * color wheel.
+ * @param { Number } hue Range is 0.0-1.0.
+ * @param { Number } saturation Range is 0.0-1.0.
+ * @param { Number } value Range is 0.0-1.0.
+ * @return { String } "rgb(r,g,b)" where r, g and b range from 0-255.
+ * @private
+ */
Dygraph.hsvToRGB = function (hue, saturation, value) {
var red;
var green;
Dygraph.update(this.plotter_.options, this.renderOptions_);
Dygraph.update(this.layoutOptions_, this.user_attrs_);
Dygraph.update(this.layoutOptions_, this.attrs_);
-}
+};
/**
* 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<string>} The list of colors.
*/
Dygraph.prototype.getColors = function() {
// 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
+
+/**
+ * @private
+ */
Dygraph.findPosX = function(obj) {
var curleft = 0;
if(obj.offsetParent)
return curleft;
};
+
+/**
+ * @private
+ */
Dygraph.findPosY = function(obj) {
var curtop = 0;
if(obj.offsetParent)
};
-
/**
* 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
* 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.
this.roller_.onchange = function() { dygraph.adjustRoll(dygraph.roller_.value); };
};
-// These functions are taken from MochiKit.Signal
+/**
+ * @private
+ * Returns the x-coordinate of the event in a coordinate system where the
+ * top-left corner of the page (not the window) is (0,0).
+ * Taken from MochiKit.Signal
+ */
Dygraph.pageX = function(e) {
if (e.pageX) {
return (!e.pageX || e.pageX < 0) ? 0 : e.pageX;
}
};
+/**
+ * @private
+ * Returns the y-coordinate of the event in a coordinate system where the
+ * top-left corner of the page (not the window) is (0,0).
+ * Taken from MochiKit.Signal
+ */
Dygraph.pageY = function(e) {
if (e.pageY) {
return (!e.pageY || e.pageY < 0) ? 0 : e.pageY;
}
};
+/**
+ * @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.
-//
+/**
+ * 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.
+ *
+ * @param { Event } event the event object which led to the startPan call.
+ * @param { Dygraph} g The dygraph on which to act.
+ * @param { Object} context The dragging context object (with
+ * dragStartX/dragStartY/etc. properties). This function modifies the context.
+ */
Dygraph.startPan = function(event, g, context) {
context.isPanning = true;
var xRange = g.xAxisRange();
}
};
-// 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.
-//
+/**
+ * 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.
+ *
+ * @param { Event } event the event object which led to the movePan call.
+ * @param { Dygraph} g The dygraph on which to act.
+ * @param { Object} context The dragging context object (with
+ * dragStartX/dragStartY/etc. properties). This function modifies the context.
+ */
Dygraph.movePan = function(event, g, context) {
context.dragEndX = g.dragGetX_(event, context);
context.dragEndY = g.dragGetY_(event, context);
}
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.
-//
+/**
+ * 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.
+ *
+ * @param { Event } event the event object which led to the startZoom call.
+ * @param { Dygraph} g The dygraph on which to act.
+ * @param { Object} context The dragging context object (with
+ * dragStartX/dragStartY/etc. properties). This function modifies the context.
+ */
Dygraph.endPan = function(event, g, context) {
// TODO(konigsberg): Clear the context data from the axis.
// TODO(konigsberg): mouseup should just delete the
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.
-//
+/**
+ * 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.
+ *
+ * @param { Event } event the event object which led to the startZoom call.
+ * @param { Dygraph} g The dygraph on which to act.
+ * @param { Object} context The dragging context object (with
+ * dragStartX/dragStartY/etc. properties). This function modifies the context.
+ */
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.
-//
+/**
+ * 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.
+ *
+ * @param { Event } event the event object which led to the moveZoom call.
+ * @param { Dygraph} g The dygraph on which to act.
+ * @param { Object} context The dragging context object (with
+ * dragStartX/dragStartY/etc. properties). This function modifies the context.
+ */
Dygraph.moveZoom = function(event, g, context) {
context.dragEndX = g.dragGetX_(event, context);
context.dragEndY = g.dragGetY_(event, context);
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.
-//
+/**
+ * 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.
+ *
+ * @param { Event } event the event object which led to the endZoom call.
+ * @param { Dygraph} g The dygraph on which to end the zoom.
+ * @param { Object} context The dragging context object (with
+ * dragStartX/dragStartY/etc. properties). This function modifies the context.
+ */
Dygraph.endZoom = function(event, g, context) {
+ // TODO(konigsberg): Refactor or rename this fn -- it deals with clicks, too.
context.isZooming = false;
context.dragEndX = g.dragGetX_(event, context);
context.dragEndY = g.dragGetY_(event, context);
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);
+ g.canvas_ctx_.clearRect(0, 0, g.canvas_.width, g.canvas_.height);
}
context.dragStartX = null;
context.dragStartY = null;
-}
+};
+/**
+ * Default interation model for dygraphs. You can refer to specific elements of
+ * this when constructing your own interaction model, e.g.:
+ * g.updateOptions( {
+ * interactionModel: {
+ * mousedown: Dygraph.defaultInteractionModel.mousedown
+ * }
+ * } );
+ */
Dygraph.defaultInteractionModel = {
// Track the beginning of drag events
mousedown: function(event, g, context) {
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) {
return -1;
};
+/**
+ * @private
+ * @param { Number } x The number to consider.
+ * @return { Boolean } Whether the number is zero or NaN.
+ */
// 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,
return html;
}
- var displayDigits = this.numXDigits_ + this.numExtraDigits_;
- var html = this.attr_('xValueFormatter')(x, displayDigits) + ":";
+ var html = this.attr_('xValueFormatter')(x) + ":";
var fmtFunc = this.attr_('yValueFormatter');
var showZeros = this.attr_("labelsShowZeroValues");
if (sepLines) html += "<br/>";
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 += " <b><span style='color: " + c + ";'>"
+ pt.name + "</span></b>:"
};
/**
+ * @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;
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
};
/**
- * 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
};
/**
- * 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) {
}
}
return -1;
-}
+};
+
+/**
+ * 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
+ * @param {Dygraph} g The dygraph object
+ */
+Dygraph.numberFormatter = function(x, g) {
+ var sigFigs = g.attr_('sigFigs');
+
+ if (sigFigs !== null) {
+ // User has opted for a fixed number of significant figures.
+ return Dygraph.floatFormat(x, sigFigs);
+ }
+ var digits = g.attr_('digitsAfterDecimal');
+ var maxNumberWidth = g.attr_('maxNumberWidth');
+
+ // switch to scientific notation if we underflow or overflow fixed display.
+ if (x !== 0.0 &&
+ (Math.abs(x) >= Math.pow(10, maxNumberWidth) ||
+ Math.abs(x) < Math.pow(10, -digits))) {
+ return x.toExponential(digits);
+ } else {
+ return '' + Dygraph.round_(x, digits);
+ }
+};
+
+/**
+ * @private
+ * Converts '9' to '09' (useful for dates)
+ */
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.
} else {
return zeropad(d.getHours()) + ":" + zeropad(d.getMinutes());
}
-}
+};
/**
* Convert a JS date to a string appropriate to display on an axis that
return Dygraph.hmsString_(date.getTime());
}
}
-}
+};
/**
* Convert a JS date (millis since epoch) to YYYY/MM/DD
};
/**
+ * 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
range = [this.rawData_[0][0], this.rawData_[this.rawData_.length - 1][0]];
}
- var formatter = this.attr_('xTicker');
- var ret = formatter(range[0], range[1], this);
- var xTicks = [];
-
- // Note: numericTicks() returns a {ticks: [...], numDigits: yy} dictionary,
- // whereas dateTicker and user-defined tickers typically just return a ticks
- // array.
- if (ret.ticks !== undefined) {
- xTicks = ret.ticks;
- this.numXDigits_ = ret.numDigits;
- } else {
- xTicks = ret;
- }
-
+ var xTicks = this.attr_('xTicker')(range[0], range[1], this);
this.layout_.updateOptions({xTicks: xTicks});
};
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.
}
};
-// 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 = [];
* 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.<Object>} Array of {label, value} tuples.
+ * @param {Dygraph} self The dygraph object
+ * @return { [Object] } Array of {label, value} tuples.
* @public
*/
Dygraph.dateTicker = function(startDate, endDate, self) {
+ // TODO(danvk): why does this take 'self' as a param?
var chosen = -1;
for (var i = 0; i < Dygraph.NUM_GRANULARITIES; i++) {
var num_ticks = self.NumXTicks(startDate, endDate, i);
}
};
-// This is a list of human-friendly values at which to show tick marks on a log
-// scale. It is k * 10^n, where k=1..9 and n=-39..+39, so:
-// ..., 1, 2, 3, 4, 5, ..., 9, 10, 20, 30, ..., 90, 100, 200, 300, ...
-// NOTE: this assumes that Dygraph.LOG_SCALE = 10.
+/**
+ * @private
+ * This is a list of human-friendly values at which to show tick marks on a log
+ * scale. It is k * 10^n, where k=1..9 and n=-39..+39, so:
+ * ..., 1, 2, 3, 4, 5, ..., 9, 10, 20, 30, ..., 90, 100, 200, 300, ...
+ * NOTE: this assumes that Dygraph.LOG_SCALE = 10.
+ */
Dygraph.PREFERRED_LOG_TICK_VALUES = function() {
var vals = [];
for (var power = -39; power <= 39; power++) {
return vals;
}();
-// val is the value to search for
-// arry is the value over which to search
-// if abs > 0, find the lowest entry greater than val
-// if abs < 0, find the highest entry less than val
-// if abs == 0, find the entry that equals val.
-// Currently does not work when val is outside the range of arry's values.
+/**
+ * @private
+ * Implementation of binary search over an array.
+ * Currently does not work when val is outside the range of arry's values.
+ * @param { Integer } val the value to search for
+ * @param { Integer[] } arry is the value over which to search
+ * @param { Integer } abs If abs > 0, find the lowest entry greater than val
+ * If abs < 0, find the highest entry less than val.
+ * if abs == 0, find the entry that equals val.
+ * @param { Integer } [low] The first index in arry to consider (optional)
+ * @param { Integer } [high] The last index in arry to consider (optional)
+ */
Dygraph.binarySearch = function(val, arry, abs, low, high) {
if (low == null || high == null) {
low = 0;
}
};
-/**
- * Determine the number of significant figures in a Number up to the specified
- * precision. Note that there is no way to determine if a trailing '0' is
- * significant or not, so by convention we return 1 for all of the following
- * inputs: 1, 1.0, 1.00, 1.000 etc.
- * @param {Number} x The input value.
- * @param {Number} opt_maxPrecision Optional maximum precision to consider.
- * Default and maximum allowed value is 13.
- * @return {Number} The number of significant figures which is >= 1.
- */
-Dygraph.significantFigures = function(x, opt_maxPrecision) {
- var precision = Math.max(opt_maxPrecision || 13, 13);
-
- // Convert the number to its exponential notation form and work backwards,
- // ignoring the 'e+xx' bit. This may seem like a hack, but doing a loop and
- // dividing by 10 leads to roundoff errors. By using toExponential(), we let
- // the JavaScript interpreter handle the low level bits of the Number for us.
- var s = x.toExponential(precision);
- var ePos = s.lastIndexOf('e'); // -1 case handled by return below.
-
- for (var i = ePos - 1; i >= 0; i--) {
- if (s[i] == '.') {
- // Got to the decimal place. We'll call this 1 digit of precision because
- // we can't know for sure how many trailing 0s are significant.
- return 1;
- } else if (s[i] != '0') {
- // Found the first non-zero digit. Return the number of characters
- // except for the '.'.
- return i; // This is i - 1 + 1 (-1 is for '.', +1 is for 0 based index).
- }
- }
-
- // Occurs if toExponential() doesn't return a string containing 'e', which
- // should never happen.
- return 1;
-};
-
+// TODO(konigsberg): Update comment.
/**
* Add ticks when the x axis has numbers on it (instead of dates)
- * TODO(konigsberg): Update comment.
*
* @param {Number} minV minimum value
* @param {Number} maxV maximum value
* @param self
* @param {function} attribute accessor function.
- * @return {Array.<Object>} Array of {label, value} tuples.
- * @public
+ * @return {[Object]} Array of {label, value} tuples.
*/
Dygraph.numericTicks = function(minV, maxV, self, axis_props, vals) {
var attr = function(k) {
var formatter = attr('yAxisLabelFormatter') ?
attr('yAxisLabelFormatter') : attr('yValueFormatter');
- // Determine the number of decimal places needed for the labels below by
- // taking the maximum number of significant figures for any label. We must
- // take the max because we can't tell if trailing 0s are significant.
- var numDigits = 0;
- for (var i = 0; i < ticks.length; i++) {
- numDigits = Math.max(Dygraph.significantFigures(ticks[i].v), numDigits);
- }
-
// Add labels to the ticks.
for (var i = 0; i < ticks.length; i++) {
if (ticks[i].label !== undefined) continue; // Use current label.
var tickV = ticks[i].v;
var absTickV = Math.abs(tickV);
- var label = (formatter !== undefined) ?
- formatter(tickV, numDigits) : tickV.toPrecision(numDigits);
+ var label = formatter(tickV, self);
if (k_labels.length > 0) {
// Round up to an appropriate unit.
var n = k*k*k*k;
for (var j = 3; j >= 0; j--, n /= k) {
if (absTickV >= n) {
- label = formatter(tickV / n, numDigits) + k_labels[j];
+ label = Dygraph.round_(tickV / n, attr('digitsAfterDecimal')) + k_labels[j];
break;
}
}
ticks[i].label = label;
}
- return {ticks: ticks, numDigits: numDigits};
+ return 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]], ...
-// Returns [low, high]
+/**
+ * @private
+ * Computes the range of the data series (including confidence intervals).
+ * @param { [Array] } series either [ [x1, y1], [x2, y2], ... ] or
+ * [ [x1, [y1, dev_low, dev_high]], [x2, [y2, dev_low, dev_high]], ...
+ * @return [low, high]
+ */
Dygraph.prototype.extremeValues_ = function(series) {
var minY = null, maxY = null;
};
/**
+ * @private
* 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,
// Create a new plotter.
if (this.plotter_) this.plotter_.clear();
this.plotter_ = new DygraphCanvasRenderer(this,
- this.hidden_, this.layout_,
+ this.hidden_,
+ this.hidden_ctx_,
+ this.layout_,
this.renderOptions_);
// The roller sits in the bottom left corner of the chart. We don't know where
if (is_initial_draw) {
// Generate a static legend before any particular point is selected.
- this.attr_('labelsDiv').innerHTML = this.generateLegendHTML_();
+ this.setLegendHTML_();
} else {
if (typeof(this.selPoints_) !== 'undefined' && this.selPoints_.length) {
this.lastx_ = this.selPoints_[0].xval;
};
/**
+ * @private
* 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
* indices are into the axes_ array.
*/
Dygraph.prototype.computeYAxes_ = function() {
- var valueWindows;
- if (this.axes_ != undefined) {
- // Preserve valueWindow settings.
- valueWindows = [];
- for (var index = 0; index < this.axes_.length; index++) {
- valueWindows.push(this.axes_[index].valueWindow);
- }
- }
-
this.axes_ = [{ yAxisId : 0, g : this }]; // always have at least one y-axis.
this.seriesToAxisMap_ = {};
if (vis[i - 1]) seriesToAxisFiltered[s] = this.seriesToAxisMap_[s];
}
this.seriesToAxisMap_ = seriesToAxisFiltered;
-
- if (valueWindows != undefined) {
- // Restore valueWindow settings.
- for (var index = 0; index < valueWindows.length; index++) {
- this.axes_[index].valueWindow = valueWindows[index];
- }
- }
};
/**
};
/**
+ * @private
* 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_.
seriesForAxis[idx].push(series);
}
- // If no series are defined or visible then fill in some reasonable defaults.
- if (seriesForAxis.length == 0) {
- var axis = this.axes_[0];
- axis.computedValueRange = [0, 1];
- var ret =
- Dygraph.numericTicks(axis.computedValueRange[0],
- axis.computedValueRange[1],
- this,
- axis);
- axis.ticks = ret.ticks;
- this.numYDigits_ = ret.numDigits;
- return;
- }
-
// 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 (!seriesForAxis[i]) {
+ // If no series are defined or visible then use a reasonable default
+ axis.extremeRange = [0, 1];
+ } else {
// Calculate the extremes of extremes.
var series = seriesForAxis[i];
var minY = Infinity; // extremes[series[0]][0];
// primary axis. However, if an axis is specifically marked as having
// independent ticks, then that is permissible as well.
if (i == 0 || axis.independentTicks) {
- var ret =
+ axis.ticks =
Dygraph.numericTicks(axis.computedValueRange[0],
axis.computedValueRange[1],
this,
axis);
- axis.ticks = ret.ticks;
- this.numYDigits_ = ret.numDigits;
} else {
var p_axis = this.axes_[0];
var p_ticks = p_axis.ticks;
tick_values.push(y_val);
}
- var ret =
+ axis.ticks =
Dygraph.numericTicks(axis.computedValueRange[0],
axis.computedValueRange[1],
this, axis, tick_values);
- axis.ticks = ret.ticks;
- this.numYDigits_ = ret.numDigits;
}
}
};
/**
+ * @private
* 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]
};
/**
+ * @private
* Parses a date, returning the number of milliseconds since epoch. This can be
* passed in as an xValueParser in the Dygraph constructor.
* TODO(danvk): enumerate formats that this understands.
* @param {String} A date in YYYYMMDD format.
* @return {Number} Milliseconds since epoch.
- * @public
*/
Dygraph.dateParser = function(dateStr, self) {
var dateStrSlashed;
this.attrs_.xTicker = Dygraph.dateTicker;
this.attrs_.xAxisLabelFormatter = Dygraph.dateAxisFormatter;
} else {
- this.attrs_.xValueFormatter = this.attrs_.yValueFormatter;
+ // TODO(danvk): use Dygraph.numberFormatter here?
+ 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;
};
/**
+ * @private
* Parses a string in a special csv format. We expect a csv file where each
* line is a date point, and the first field in each line is the date string.
* We also expect that all remaining fields represent series.
* if the errorBars attribute is set, then interpret the fields as:
* date, series1, stddev1, series2, stddev2, ...
- * @param {Array.<Object>} data See above.
- * @private
+ * @param {[Object]} data See above.
*
- * @return Array.<Object> An array with one entry for each row. These entries
+ * @return [Object] An array with one entry for each row. These entries
* are an array of cells in that row. The first entry is the parsed x-value for
* the row. The second, third, etc. are the y-values. These can take on one of
* three forms, depending on the CSV and constructor parameters:
} else if (this.attr_("customBars")) {
// Bars are a low;center;high tuple
for (var j = 1; j < inFields.length; j++) {
- var vals = inFields[j].split(";");
- fields[j] = [ this.parseFloat_(vals[0], i, line),
- this.parseFloat_(vals[1], i, line),
- this.parseFloat_(vals[2], i, line) ];
+ var val = inFields[j];
+ if (/^ *$/.test(val)) {
+ fields[j] = [null, null, null];
+ } else {
+ var vals = val.split(";");
+ if (vals.length == 3) {
+ fields[j] = [ this.parseFloat_(vals[0], i, line),
+ this.parseFloat_(vals[1], i, line),
+ this.parseFloat_(vals[2], i, line) ];
+ } else {
+ this.warning('When using customBars, values must be either blank ' +
+ 'or "low;center;high" tuples (got "' + val +
+ '" on line ' + (1+i));
+ }
+ }
}
} else {
// Values are just numbers
};
/**
+ * @private
* The user has provided their data as a pre-packaged JS array. If the x values
* are numeric, this is the same as dygraphs' internal format. If the x values
* are dates, we need to convert them from Date objects to ms since epoch.
- * @param {Array.<Object>} data
- * @return {Array.<Object>} data with numeric x values.
+ * @param {[Object]} data
+ * @return {[Object]} data with numeric x values.
*/
Dygraph.prototype.parseArray_ = function(data) {
// Peek at the first x value to see if it's numeric.
return parsedData;
} else {
// Some intelligent defaults for a numeric x-axis.
- this.attrs_.xValueFormatter = this.attrs_.yValueFormatter;
+ this.attrs_.xValueFormatter = function(x) { return x; };
this.attrs_.xTicker = Dygraph.numericTicks;
return data;
}
* 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. Fills out rawData_.
- * @param {Array.<Object>} data See above.
+ * @param {[Object]} data See above.
* @private
*/
Dygraph.prototype.parseDataTable_ = function(data) {
this.attrs_.xTicker = Dygraph.dateTicker;
this.attrs_.xAxisLabelFormatter = Dygraph.dateAxisFormatter;
} else if (indepType == 'number') {
- this.attrs_.xValueFormatter = this.attrs_.yValueFormatter;
+ 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;
}
}
-// This is identical to JavaScript's built-in Date.parse() method, except that
-// it doesn't get replaced with an incompatible method by aggressive JS
-// libraries like MooTools or Joomla.
+/**
+ * @private
+ * This is identical to JavaScript's built-in Date.parse() method, except that
+ * it doesn't get replaced with an incompatible method by aggressive JS
+ * libraries like MooTools or Joomla.
+ * @param { String } str The date string, e.g. "2011/05/06"
+ * @return { Integer } millis since epoch
+ */
Dygraph.dateStrToMillis = function(str) {
return new Date(str).getTime();
};
// These functions are all based on MochiKit.
+/**
+ * @private
+ */
Dygraph.update = function (self, o) {
if (typeof(o) != 'undefined' && o !== null) {
for (var k in o) {
return self;
};
+/**
+ * @private
+ */
Dygraph.isArrayLike = function (o) {
var typ = typeof(o);
if (
return true;
};
+/**
+ * @private
+ */
Dygraph.isDateLike = function (o) {
if (typeof(o) != "object" || o === null ||
typeof(o.getTime) != 'function') {
return true;
};
+/**
+ * @private
+ */
Dygraph.clone = function(o) {
// TODO(danvk): figure out how MochiKit's version works
var r = [];
* This is far more efficient than destroying and re-instantiating a
* Dygraph, since it doesn't have to reparse the underlying data.
*
- * @param {Number} width Width (in pixels)
- * @param {Number} height Height (in pixels)
+ * @param {Number} [width] Width (in pixels)
+ * @param {Number} [height] Height (in pixels)
*/
Dygraph.prototype.resize = function(width, height) {
if (this.resize_lock) {
return null;
};
+/**
+ * @private
+ * Adds a default style for the annotation CSS classes to the document. This is
+ * only executed when annotations are actually used. It is designed to only be
+ * called once -- all calls after the first will return immediately.
+ */
Dygraph.addAnnotationRule = function() {
if (Dygraph.addedAnnotationCSS) return;
}
/**
+ * @private
* Create a new canvas element. This is more complex than a simple
* document.createElement("canvas") because of IE and excanvas.
*/
"labels": ["Zooming"],
"type": "boolean",
"description" : "When this option is passed to updateOptions() along with either the <code>dateWindow</code> or <code>valueRange</code> options, the zoom flags are not changed to reflect a zoomed state. This is primarily useful for when the display area of a chart is changed programmatically and also where manual zooming is allowed and use is made of the <code>isZoomed</code> method to determine this."
+ },
+ "sigFigs" : {
+ "default": "null",
+ "labels": ["Value display/formatting"],
+ "type": "integer",
+ "description": "By default, dygraphs displays numbers with a fixed number of digits after the decimal point. If you'd prefer to have a fixed number of significant figures, set this option to that number of sig figs. A value of 2, for instance, would cause 1 to be display as 1.0 and 1234 to be displayed as 1.23e+3."
+ },
+ "digitsAfterDecimal" : {
+ "default": "2",
+ "labels": ["Value display/formatting"],
+ "type": "integer",
+ "description": "Unless it's run in scientific mode (see the <code>sigFigs</code> option), dygraphs displays numbers with <code>digitsAfterDecimal</code> digits after the decimal point. Trailing zeros are not displayed, so with a value of 2 you'll get '0', '0.1', '0.12', '123.45' but not '123.456' (it will be rounded to '123.46'). Numbers with absolute value less than 0.1^digitsAfterDecimal (i.e. those which would show up as '0.00') will be displayed in scientific notation."
+ },
+ "maxNumberWidth" : {
+ "default": "6",
+ "labels": ["Value display/formatting"],
+ "type": "integer",
+ "description": "When displaying numbers in normal (not scientific) mode, large numbers will be displayed with many trailing zeros (e.g. 100000000 instead of 1e9). This can lead to unwieldy y-axis labels. If there are more than <code>maxNumberWidth</code> digits to the left of the decimal in a number, dygraphs will switch to scientific notation, even when not operating in scientific mode. If you'd like to see all those digits, set this to something large, like 20 or 30."
}
}
; // </JSON>