X-Git-Url: https://adrianiainlam.tk/git/?a=blobdiff_plain;f=dygraph.js;h=4f9216d3c0c848e518f5d4dab32dfcf75d6fd08e;hb=e9fe4a2ff545eaaf9e350c03673f825db2a5269e;hp=656d7a82adabe045e7a7dfa93ce7772ee747e995;hpb=7aedf6fee4cad9a3fc6de49476fc176e48a090fa;p=dygraphs.git diff --git a/dygraph.js b/dygraph.js index 656d7a8..4f9216d 100644 --- a/dygraph.js +++ b/dygraph.js @@ -1553,6 +1553,32 @@ Dygraph.prototype.idxToRow_ = function(idx) { return -1; }; +Dygraph.isOK = function(x) { + return x && !isNaN(x); +}; + +Dygraph.prototype.generateLegendHTML_ = function(x, sel_points) { + var displayDigits = this.numXDigits_ + this.numExtraDigits_; + var html = this.attr_('xValueFormatter')(x, displayDigits) + ":"; + + var fmtFunc = this.attr_('yValueFormatter'); + var showZeros = this.attr_("labelsShowZeroValues"); + var sepLines = this.attr_("labelsSeparateLines"); + for (var i = 0; i < this.selPoints_.length; i++) { + var pt = this.selPoints_[i]; + if (pt.yval == 0 && !showZeros) continue; + if (!Dygraph.isOK(pt.canvasy)) continue; + if (sepLines) html += "
"; + + var c = new RGBColor(this.plotter_.colors[pt.name]); + var yval = fmtFunc(pt.yval, displayDigits); + html += " " + + pt.name + ":" + + yval; + } + return html; +}; + /** * Draw dots over the selectied points in the data series. This function * takes care of cleanup of previously-drawn dots. @@ -1574,46 +1600,24 @@ Dygraph.prototype.updateSelection_ = function() { 2 * maxCircleSize + 2, this.height_); } - var isOK = function(x) { return x && !isNaN(x); }; - if (this.selPoints_.length > 0) { - var canvasx = this.selPoints_[0].canvasx; - // Set the status message to indicate the selected point(s) - var replace = this.attr_('xValueFormatter')( - this.lastx_, this.numXDigits_ + this.numExtraDigits_) + ":"; - var fmtFunc = this.attr_('yValueFormatter'); - var clen = this.colors_.length; - if (this.attr_('showLabelsOnHighlight')) { - // Set the status message to indicate the selected point(s) - for (var i = 0; i < this.selPoints_.length; i++) { - if (!this.attr_("labelsShowZeroValues") && this.selPoints_[i].yval == 0) continue; - if (!isOK(this.selPoints_[i].canvasy)) continue; - if (this.attr_("labelsSeparateLines")) { - replace += "
"; - } - var point = this.selPoints_[i]; - var c = new RGBColor(this.plotter_.colors[point.name]); - var yval = fmtFunc(point.yval, this.numYDigits_ + this.numExtraDigits_); - replace += " " - + point.name + ":" - + yval; - } - - this.attr_("labelsDiv").innerHTML = replace; + var html = this.generateLegendHTML_(this.lastx_, this.selPoints_); + this.attr_("labelsDiv").innerHTML = html; } // Draw colored circles over the center of each selected point + var canvasx = this.selPoints_[0].canvasx; ctx.save(); for (var i = 0; i < this.selPoints_.length; i++) { - if (!isOK(this.selPoints_[i].canvasy)) continue; - var circleSize = - this.attr_('highlightCircleSize', this.selPoints_[i].name); + var pt = this.selPoints_[i]; + if (!Dygraph.isOK(pt.canvasy)) continue; + + var circleSize = this.attr_('highlightCircleSize', pt.name); ctx.beginPath(); - ctx.fillStyle = this.plotter_.colors[this.selPoints_[i].name]; - ctx.arc(canvasx, this.selPoints_[i].canvasy, circleSize, - 0, 2 * Math.PI, false); + ctx.fillStyle = this.plotter_.colors[pt.name]; + ctx.arc(canvasx, pt.canvasy, circleSize, 0, 2 * Math.PI, false); ctx.fill(); } ctx.restore(); @@ -2870,7 +2874,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; @@ -2878,6 +2882,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. @@ -2909,13 +2947,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 @@ -2923,6 +2955,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); @@ -2941,37 +2974,68 @@ 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])]; + if (vals.length != 2) { + this.error('Expected fractional "num/den" values in CSV data ' + + "but found a value '" + inFields[j] + "' on line " + + (1 + i) + " ('" + line + "') which is not of this form."); + fields[j] = [0, 0]; + } else { + 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])]; + if (inFields.length % 2 != 1) { + this.error('Expected alternating (value, stdev.) pairs in CSV data ' + + 'but line ' + (1 + i) + ' has an odd number of values (' + + (inFields.length - 1) + "): '" + line + "'"); + } + for (var j = 1; j < inFields.length; j += 2) { + 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) {