X-Git-Url: https://adrianiainlam.tk/git/?a=blobdiff_plain;f=dygraph.js;h=78f933d61fd0808bf0ac5ac3838e57e656c57d38;hb=629a09aeb02dce06c406a638b4ece39adc83d2e3;hp=fe677f29688d874c74b06319cff267ae9d6d6108;hpb=5bc3e265be640a29bc57d7d58c059322ab361d63;p=dygraphs.git diff --git a/dygraph.js b/dygraph.js index fe677f2..78f933d 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,61 +73,12 @@ Dygraph.VERSION = "1.2"; 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). + * Returns information about the Dygraph class. */ -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). - */ -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 @@ -133,6 +89,9 @@ Dygraph.AXIS_LINE_WIDTH = 0.3; Dygraph.LOG_SCALE = 10; Dygraph.LN_TEN = Math.log(Dygraph.LOG_SCALE); +/** + * @private + */ Dygraph.log10 = function(x) { return Math.log(x) / Dygraph.LN_TEN; } @@ -153,11 +112,10 @@ Dygraph.DEFAULT_ATTRS = { 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, @@ -215,6 +173,23 @@ Dygraph.VERTICAL = 2; // 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, @@ -267,20 +242,6 @@ Dygraph.prototype.__init__ = function(div, file, attrs) { 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 = ""; @@ -359,12 +320,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') { @@ -391,6 +366,12 @@ Dygraph.prototype.attr_ = function(name, seriesName) { }; // 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) { @@ -408,16 +389,28 @@ Dygraph.prototype.log = function(severity, message) { 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. @@ -603,6 +596,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) { @@ -636,6 +633,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) { @@ -644,10 +643,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; @@ -655,6 +655,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; @@ -664,6 +665,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; @@ -672,6 +678,15 @@ Dygraph.prototype.getValue = function(row, col) { 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; @@ -685,8 +700,14 @@ Dygraph.addEvent = function(el, evt, fn) { }; -// 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) { @@ -699,7 +720,7 @@ Dygraph.cancelEvent = function(e) { e.cancel = true; e.returnValue = false; return false; -} +}; /** @@ -725,8 +746,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_); @@ -791,8 +815,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 @@ -812,7 +837,16 @@ Dygraph.prototype.createPlotKitCanvas_ = function(canvas) { 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; @@ -881,11 +915,11 @@ Dygraph.prototype.setColors_ = function() { 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} The list of colors. */ Dygraph.prototype.getColors = function() { @@ -895,6 +929,10 @@ 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) @@ -910,6 +948,10 @@ Dygraph.findPosX = function(obj) { return curleft; }; + +/** + * @private + */ Dygraph.findPosY = function(obj) { var curtop = 0; if(obj.offsetParent) @@ -926,7 +968,6 @@ Dygraph.findPosY = function(obj) { }; - /** * 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 @@ -967,6 +1008,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. @@ -1012,7 +1054,12 @@ Dygraph.prototype.createRollInterface_ = function() { 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; @@ -1025,6 +1072,12 @@ Dygraph.pageX = function(e) { } }; +/** + * @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; @@ -1037,21 +1090,37 @@ Dygraph.pageY = function(e) { } }; +/** + * @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(); @@ -1110,13 +1179,19 @@ Dygraph.startPan = function(event, g, context) { } }; -// 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); @@ -1171,15 +1246,21 @@ Dygraph.movePan = function(event, g, 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 @@ -1191,26 +1272,38 @@ Dygraph.endPan = function(event, g, context) { 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); @@ -1234,17 +1327,24 @@ Dygraph.moveZoom = function(event, g, 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); @@ -1286,14 +1386,21 @@ Dygraph.endZoom = function(event, g, 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) { @@ -1466,7 +1573,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) { @@ -1683,11 +1790,25 @@ Dygraph.prototype.idxToRow_ = function(idx) { 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, @@ -1699,16 +1820,16 @@ Dygraph.prototype.generateLegendHTML_ = function(x, sel_points) { var labels = this.attr_('labels'); var html = ''; for (var i = 1; i < labels.length; i++) { - var c = new RGBColor(this.plotter_.colors[labels[i]]); - if (i > 1) html += (sepLines ? '
' : ' '); - html += "—" + labels[i] + - ""; + if (!this.visibility()[i - 1]) continue; + var c = this.plotter_.colors[labels[i]]; + if (html != '') html += (sepLines ? '
' : ' '); + html += "—" + labels[i] + + ""; } 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"); @@ -1719,24 +1840,45 @@ Dygraph.prototype.generateLegendHTML_ = function(x, sel_points) { if (!Dygraph.isOK(pt.canvasy)) continue; if (sepLines) html += "
"; - var c = new RGBColor(this.plotter_.colors[pt.name]); - var yval = fmtFunc(pt.yval, displayDigits); + var c = this.plotter_.colors[pt.name]; + var yval = fmtFunc(pt.yval, this); // TODO(danvk): use a template string here and make it an attribute. - html += " " - + pt.name + ":" + html += " " + + pt.name + ":" + yval; } return html; }; /** + * @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; @@ -1753,8 +1895,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 @@ -1777,10 +1918,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 @@ -1810,7 +1952,6 @@ Dygraph.prototype.setSelection = function(row) { this.lastx_ = this.selPoints_[0].xval; this.updateSelection_(); } else { - this.lastx_ = -1; this.clearSelection(); } @@ -1832,22 +1973,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) { @@ -1860,11 +2000,85 @@ Dygraph.prototype.getSelection = function() { } } 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. @@ -1882,7 +2096,7 @@ Dygraph.hmsString_ = function(date) { } else { return zeropad(d.getHours()) + ":" + zeropad(d.getMinutes()); } -} +}; /** * Convert a JS date to a string appropriate to display on an axis that @@ -1905,7 +2119,7 @@ Dygraph.dateAxisFormatter = function(date, granularity) { return Dygraph.hmsString_(date.getTime()); } } -} +}; /** * Convert a JS date (millis since epoch) to YYYY/MM/DD @@ -1932,6 +2146,18 @@ Dygraph.dateString_ = function(date) { }; /** + * 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 @@ -1958,20 +2184,7 @@ Dygraph.prototype.addXTicks_ = function() { 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}); }; @@ -2016,11 +2229,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. @@ -2041,13 +2254,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 = []; @@ -2135,10 +2349,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.} 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); @@ -2155,10 +2371,13 @@ Dygraph.dateTicker = function(startDate, endDate, self) { } }; -// 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++) { @@ -2171,12 +2390,18 @@ Dygraph.PREFERRED_LOG_TICK_VALUES = function() { 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; @@ -2218,53 +2443,15 @@ Dygraph.binarySearch = function(val, arry, abs, low, high) { } }; -/** - * 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.} 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) { @@ -2379,27 +2566,18 @@ Dygraph.numericTicks = function(minV, maxV, self, axis_props, vals) { 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; } } @@ -2407,13 +2585,16 @@ Dygraph.numericTicks = function(minV, maxV, self, axis_props, vals) { 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; @@ -2451,6 +2632,7 @@ Dygraph.prototype.extremeValues_ = function(series) { }; /** + * @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, @@ -2464,7 +2646,9 @@ Dygraph.prototype.predraw_ = function() { // 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 @@ -2628,7 +2812,14 @@ Dygraph.prototype.drawGraph_ = function() { 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; + this.updateSelection_(); + } else { + this.clearSelection(); + } } if (this.attr_("drawCallback") !== null) { @@ -2637,6 +2828,7 @@ Dygraph.prototype.drawGraph_ = function() { }; /** + * @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 @@ -2647,15 +2839,6 @@ Dygraph.prototype.drawGraph_ = function() { * 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_ = {}; @@ -2732,13 +2915,6 @@ Dygraph.prototype.computeYAxes_ = function() { 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]; - } - } }; /** @@ -2756,6 +2932,7 @@ Dygraph.prototype.numAxes = function() { }; /** + * @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_. @@ -2770,25 +2947,14 @@ Dygraph.prototype.computeYAxisRanges_ = function(extremes) { 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]; @@ -2854,13 +3020,11 @@ Dygraph.prototype.computeYAxisRanges_ = function(extremes) { // 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; @@ -2873,17 +3037,16 @@ Dygraph.prototype.computeYAxisRanges_ = function(extremes) { 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] @@ -3020,12 +3183,12 @@ Dygraph.prototype.rollingAverage = function(originalData, rollPeriod) { }; /** + * @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; @@ -3076,7 +3239,8 @@ Dygraph.prototype.detectTypeFromString_ = function(str) { 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; @@ -3118,15 +3282,15 @@ Dygraph.prototype.parseFloat_ = function(x, opt_line_no, opt_line) { }; /** + * @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.} data See above. - * @private + * @param {[Object]} data See above. * - * @return Array. 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: @@ -3200,10 +3364,21 @@ Dygraph.prototype.parseCSV_ = function(data) { } 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 @@ -3249,11 +3424,12 @@ Dygraph.prototype.parseCSV_ = function(data) { }; /** + * @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.} data - * @return {Array.} 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. @@ -3299,7 +3475,7 @@ Dygraph.prototype.parseArray_ = function(data) { 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; } @@ -3311,7 +3487,7 @@ Dygraph.prototype.parseArray_ = function(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.} data See above. + * @param {[Object]} data See above. * @private */ Dygraph.prototype.parseDataTable_ = function(data) { @@ -3325,7 +3501,7 @@ 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; @@ -3431,14 +3607,22 @@ Dygraph.prototype.parseDataTable_ = function(data) { } } -// 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) { @@ -3450,6 +3634,9 @@ Dygraph.update = function (self, o) { return self; }; +/** + * @private + */ Dygraph.isArrayLike = function (o) { var typ = typeof(o); if ( @@ -3464,6 +3651,9 @@ Dygraph.isArrayLike = function (o) { return true; }; +/** + * @private + */ Dygraph.isDateLike = function (o) { if (typeof(o) != "object" || o === null || typeof(o.getTime) != 'function') { @@ -3472,6 +3662,9 @@ Dygraph.isDateLike = function (o) { return true; }; +/** + * @private + */ Dygraph.clone = function(o) { // TODO(danvk): figure out how MochiKit's version works var r = []; @@ -3580,8 +3773,8 @@ Dygraph.prototype.updateOptions = function(attrs) { * 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) { @@ -3685,6 +3878,12 @@ Dygraph.prototype.indexFromSetName = function(name) { 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; @@ -3721,6 +3920,7 @@ Dygraph.addAnnotationRule = function() { } /** + * @private * Create a new canvas element. This is more complex than a simple * document.createElement("canvas") because of IE and excanvas. */ @@ -4247,6 +4447,24 @@ Dygraph.OPTIONS_REFERENCE = // "labels": ["Zooming"], "type": "boolean", "description" : "When this option is passed to updateOptions() along with either the dateWindow or valueRange 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 isZoomed 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 sigFigs option), dygraphs displays numbers with digitsAfterDecimal 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 maxNumberWidth 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." } } ; //