X-Git-Url: https://adrianiainlam.tk/git/?a=blobdiff_plain;f=dygraph.js;h=375670289af7c3234bf39122769f2f285c4a521c;hb=5cd7ac686768da92135a96a21d589b076269ffb0;hp=3e3229744e6e0ba88c25ca401a36c61b7dfdafc7;hpb=6fd1990aa8fc8dc1f5c4017db17db6c4ba36c629;p=dygraphs.git diff --git a/dygraph.js b/dygraph.js index 3e32297..3756702 100644 --- a/dygraph.js +++ b/dygraph.js @@ -156,7 +156,7 @@ Dygraph.DEFAULT_ATTRS = { yValueFormatter: function(x, opt_precision) { var s = Dygraph.floatFormat(x, opt_precision); var s2 = Dygraph.intFormat(x); - return s.length <= s2.length ? s : s2; + return s.length < s2.length ? s : s2; }, strokeWidth: 1.0, @@ -221,7 +221,7 @@ Dygraph.prototype.__old_init__ = function(div, file, labels, attrs) { /** * Initializes the Dygraph. This creates a new DIV and constructs the PlotKit - * and context <canvas> inside of it. See the constructor for details + * and context <canvas> inside of it. See the constructor for details. * on the parameters. * @param {Element} div the Element to render the graph into. * @param {String | Function} file Source data @@ -254,7 +254,7 @@ Dygraph.prototype.__init__ = function(div, file, attrs) { this.wilsonInterval_ = attrs.wilsonInterval || true; this.is_initial_draw_ = true; this.annotations_ = []; - + // Number of digits to use when labeling the x (if numeric) and y axis // ticks. this.numXDigits_ = 2; @@ -331,6 +331,12 @@ Dygraph.prototype.__init__ = function(div, file, attrs) { this.start_(); }; +Dygraph.prototype.toString = function() { + var maindiv = this.maindiv_; + var id = (maindiv && maindiv.id) ? maindiv.id : maindiv + return "[Dygraph " + id + "]"; +} + Dygraph.prototype.attr_ = function(name, seriesName) { if (seriesName && typeof(this.user_attrs_[seriesName]) != 'undefined' && @@ -1469,6 +1475,9 @@ Dygraph.prototype.mouseMove_ = function(event) { var canvasx = Dygraph.pageX(event) - Dygraph.findPosX(this.mouseEventElement_); var points = this.layout_.points; + // This prevents JS errors when mousing over the canvas before data loads. + if (points === undefined) return; + var lastx = -1; var lasty = -1; @@ -1799,8 +1808,10 @@ Dygraph.prototype.addXTicks_ = function() { 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) { - // numericTicks() returns multiple values. xTicks = ret.ticks; this.numXDigits_ = ret.numDigits; } else { @@ -2110,7 +2121,7 @@ Dygraph.numericTicks = function(minV, maxV, self, axis_props, vals) { var ticks = []; if (vals) { for (var i = 0; i < vals.length; i++) { - ticks[i].push({v: vals[i]}); + ticks.push({v: vals[i]}); } } else { if (axis_props && attr("logscale")) { @@ -2224,6 +2235,7 @@ Dygraph.numericTicks = function(minV, maxV, self, axis_props, vals) { // 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) ? @@ -2233,13 +2245,14 @@ Dygraph.numericTicks = function(minV, maxV, self, axis_props, vals) { var n = k*k*k*k; for (var j = 3; j >= 0; j--, n /= k) { if (absTickV >= n) { - label = (tickV / n).toPrecision(numDigits) + k_labels[j]; + label = formatter(tickV / n, numDigits) + k_labels[j]; break; } } - ticks[i].label = label; } + ticks[i].label = label; } + return {ticks: ticks, numDigits: numDigits}; }; @@ -2355,7 +2368,7 @@ Dygraph.prototype.drawGraph_ = function() { // On the log scale, points less than zero do not exist. // This will create a gap in the chart. Note that this ignores // connectSeparatedPoints. - if (point < 0) { + if (point <= 0) { point = null; } series.push([date, point]); @@ -2857,7 +2870,7 @@ Dygraph.prototype.detectTypeFromString_ = function(str) { this.attrs_.xTicker = Dygraph.dateTicker; this.attrs_.xAxisLabelFormatter = Dygraph.dateAxisFormatter; } else { - this.attrs_.xValueFormatter = this.attrs_.xValueFormatter; + this.attrs_.xValueFormatter = this.attrs_.yValueFormatter; this.attrs_.xValueParser = function(x) { return parseFloat(x); }; this.attrs_.xTicker = Dygraph.numericTicks; this.attrs_.xAxisLabelFormatter = this.attrs_.xValueFormatter; @@ -2865,6 +2878,40 @@ Dygraph.prototype.detectTypeFromString_ = function(str) { }; /** + * Parses the value as a floating point number. This is like the parseFloat() + * built-in, but with a few differences: + * - the empty string is parsed as null, rather than NaN. + * - if the string cannot be parsed at all, an error is logged. + * If the string can't be parsed, this method returns null. + * @param {String} x The string to be parsed + * @param {Number} opt_line_no The line number from which the string comes. + * @param {String} opt_line The text of the line from which the string comes. + * @private + */ + +// Parse the x as a float or return null if it's not a number. +Dygraph.prototype.parseFloat_ = function(x, opt_line_no, opt_line) { + var val = parseFloat(x); + if (!isNaN(val)) return val; + + // Try to figure out what happeend. + // If the value is the empty string, parse it as null. + if (/^ *$/.test(x)) return null; + + // If it was actually "NaN", return it as NaN. + if (/^ *nan *$/i.test(x)) return NaN; + + // Looks like a parsing error. + var msg = "Unable to parse '" + x + "' as a number"; + if (opt_line !== null && opt_line_no !== null) { + msg += " on line " + (1+opt_line_no) + " ('" + opt_line + "') of CSV."; + } + this.error(msg); + + return null; +}; + +/** * 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. @@ -2896,13 +2943,7 @@ Dygraph.prototype.parseCSV_ = function(data) { start = 1; this.attrs_.labels = lines[0].split(delim); } - - // Parse the x as a float or return null if it's not a number. - var parseFloatOrNull = function(x) { - var val = parseFloat(x); - // isFinite() returns false for NaN and +/-Infinity. - return isFinite(val) ? val : null; - }; + var line_no = 0; var xParser; var defaultParserSet = false; // attempt to auto-detect x value type @@ -2910,6 +2951,7 @@ Dygraph.prototype.parseCSV_ = function(data) { var outOfOrder = false; for (var i = start; i < lines.length; i++) { var line = lines[i]; + line_no = i; if (line.length == 0) continue; // skip blank lines if (line[0] == '#') continue; // skip comment lines var inFields = line.split(delim); @@ -2928,37 +2970,55 @@ Dygraph.prototype.parseCSV_ = function(data) { for (var j = 1; j < inFields.length; j++) { // TODO(danvk): figure out an appropriate way to flag parse errors. var vals = inFields[j].split("/"); - fields[j] = [parseFloatOrNull(vals[0]), parseFloatOrNull(vals[1])]; + fields[j] = [this.parseFloat_(vals[0], i, line), + this.parseFloat_(vals[1], i, line)]; } } else if (this.attr_("errorBars")) { // If there are error bars, values are (value, stddev) pairs for (var j = 1; j < inFields.length; j += 2) - fields[(j + 1) / 2] = [parseFloatOrNull(inFields[j]), - parseFloatOrNull(inFields[j + 1])]; + fields[(j + 1) / 2] = [this.parseFloat_(inFields[j], i, line), + this.parseFloat_(inFields[j + 1], i, line)]; } 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] = [ parseFloatOrNull(vals[0]), - parseFloatOrNull(vals[1]), - parseFloatOrNull(vals[2]) ]; + fields[j] = [ this.parseFloat_(vals[0], i, line), + this.parseFloat_(vals[1], i, line), + this.parseFloat_(vals[2], i, line) ]; } } else { // Values are just numbers for (var j = 1; j < inFields.length; j++) { - fields[j] = parseFloatOrNull(inFields[j]); + fields[j] = this.parseFloat_(inFields[j], i, line); } } if (ret.length > 0 && fields[0] < ret[ret.length - 1][0]) { outOfOrder = true; } - ret.push(fields); if (fields.length != expectedCols) { this.error("Number of columns in line " + i + " (" + fields.length + ") does not agree with number of labels (" + expectedCols + ") " + line); } + + // If the user specified the 'labels' option and none of the cells of the + // first row parsed correctly, then they probably double-specified the + // labels. We go with the values set in the option, discard this row and + // log a warning to the JS console. + if (i == 0 && this.attr_('labels')) { + var all_null = true; + for (var j = 0; all_null && j < fields.length; j++) { + if (fields[j]) all_null = false; + } + if (all_null) { + this.warn("The dygraphs 'labels' option is set, but the first row of " + + "CSV data ('" + line + "') appears to also contain labels. " + + "Will drop the CSV labels and use the option labels."); + continue; + } + } + ret.push(fields); } if (outOfOrder) {