+ * 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.
+ */
+Dygraph.prototype.parseArray_ = function(data) {
+ // Peek at the first x value to see if it's numeric.
+ if (data.length == 0) {
+ this.error("Can't plot empty data set");
+ return null;
+ }
+ if (data[0].length == 0) {
+ this.error("Data set cannot contain an empty row");
+ return null;
+ }
+
+ if (this.attr_("labels") == null) {
+ this.warn("Using default labels. Set labels explicitly via 'labels' " +
+ "in the options parameter");
+ this.attrs_.labels = [ "X" ];
+ for (var i = 1; i < data[0].length; i++) {
+ this.attrs_.labels.push("Y" + i);
+ }
+ }
+
+ if (Dygraph.isDateLike(data[0][0])) {
+ // Some intelligent defaults for a date x-axis.
+ this.attrs_.xValueFormatter = Dygraph.dateString_;
+ this.attrs_.xAxisLabelFormatter = Dygraph.dateAxisFormatter;
+ this.attrs_.xTicker = Dygraph.dateTicker;
+
+ // Assume they're all dates.
+ var parsedData = Dygraph.clone(data);
+ for (var i = 0; i < data.length; i++) {
+ if (parsedData[i].length == 0) {
+ this.error("Row " + (1 + i) + " of data is empty");
+ return null;
+ }
+ if (parsedData[i][0] == null
+ || typeof(parsedData[i][0].getTime) != 'function'
+ || isNaN(parsedData[i][0].getTime())) {
+ this.error("x value in row " + (1 + i) + " is not a Date");
+ return null;
+ }
+ parsedData[i][0] = parsedData[i][0].getTime();
+ }
+ return parsedData;
+ } else {
+ // Some intelligent defaults for a numeric x-axis.
+ this.attrs_.xValueFormatter = this.attrs_.yValueFormatter;
+ this.attrs_.xTicker = Dygraph.numericTicks;
+ return data;
+ }
+};
+
+/**
+ * Parses a DataTable object from gviz.
+ * The data is expected to have a first column that is either a date or a
+ * 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.
+ * @private
+ */
+Dygraph.prototype.parseDataTable_ = function(data) {
+ var cols = data.getNumberOfColumns();
+ var rows = data.getNumberOfRows();
+
+ var indepType = data.getColumnType(0);
+ if (indepType == 'date' || indepType == 'datetime') {
+ this.attrs_.xValueFormatter = Dygraph.dateString_;
+ this.attrs_.xValueParser = Dygraph.dateParser;
+ this.attrs_.xTicker = Dygraph.dateTicker;
+ this.attrs_.xAxisLabelFormatter = Dygraph.dateAxisFormatter;
+ } else if (indepType == 'number') {
+ 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;
+ } else {
+ this.error("only 'date', 'datetime' and 'number' types are supported for " +
+ "column 1 of DataTable input (Got '" + indepType + "')");
+ return null;
+ }
+
+ // Array of the column indices which contain data (and not annotations).
+ var colIdx = [];
+ var annotationCols = {}; // data index -> [annotation cols]
+ var hasAnnotations = false;
+ for (var i = 1; i < cols; i++) {
+ var type = data.getColumnType(i);
+ if (type == 'number') {
+ colIdx.push(i);
+ } else if (type == 'string' && this.attr_('displayAnnotations')) {
+ // This is OK -- it's an annotation column.
+ var dataIdx = colIdx[colIdx.length - 1];
+ if (!annotationCols.hasOwnProperty(dataIdx)) {
+ annotationCols[dataIdx] = [i];
+ } else {
+ annotationCols[dataIdx].push(i);
+ }
+ hasAnnotations = true;
+ } else {
+ this.error("Only 'number' is supported as a dependent type with Gviz." +
+ " 'string' is only supported if displayAnnotations is true");
+ }
+ }
+
+ // Read column labels
+ // TODO(danvk): add support back for errorBars
+ var labels = [data.getColumnLabel(0)];
+ for (var i = 0; i < colIdx.length; i++) {
+ labels.push(data.getColumnLabel(colIdx[i]));
+ if (this.attr_("errorBars")) i += 1;
+ }
+ this.attrs_.labels = labels;
+ cols = labels.length;
+
+ var ret = [];
+ var outOfOrder = false;
+ var annotations = [];
+ for (var i = 0; i < rows; i++) {
+ var row = [];
+ if (typeof(data.getValue(i, 0)) === 'undefined' ||
+ data.getValue(i, 0) === null) {
+ this.warn("Ignoring row " + i +
+ " of DataTable because of undefined or null first column.");
+ continue;
+ }
+
+ if (indepType == 'date' || indepType == 'datetime') {
+ row.push(data.getValue(i, 0).getTime());
+ } else {
+ row.push(data.getValue(i, 0));
+ }
+ if (!this.attr_("errorBars")) {
+ for (var j = 0; j < colIdx.length; j++) {
+ var col = colIdx[j];
+ row.push(data.getValue(i, col));
+ if (hasAnnotations &&
+ annotationCols.hasOwnProperty(col) &&
+ data.getValue(i, annotationCols[col][0]) != null) {
+ var ann = {};
+ ann.series = data.getColumnLabel(col);
+ ann.xval = row[0];
+ ann.shortText = String.fromCharCode(65 /* A */ + annotations.length)
+ ann.text = '';
+ for (var k = 0; k < annotationCols[col].length; k++) {
+ if (k) ann.text += "\n";
+ ann.text += data.getValue(i, annotationCols[col][k]);
+ }
+ annotations.push(ann);
+ }
+ }
+ } else {
+ for (var j = 0; j < cols - 1; j++) {
+ row.push([ data.getValue(i, 1 + 2 * j), data.getValue(i, 2 + 2 * j) ]);
+ }
+ }
+ if (ret.length > 0 && row[0] < ret[ret.length - 1][0]) {
+ outOfOrder = true;
+ }
+
+ // Strip out infinities, which give dygraphs problems later on.
+ for (var j = 0; j < row.length; j++) {
+ if (!isFinite(row[j])) row[j] = null;
+ }
+ ret.push(row);
+ }
+
+ if (outOfOrder) {
+ this.warn("DataTable is out of order; order it correctly to speed loading.");
+ ret.sort(function(a,b) { return a[0] - b[0] });
+ }
+ this.rawData_ = ret;
+
+ if (annotations.length > 0) {
+ this.setAnnotations(annotations, true);
+ }
+}
+
+// These functions are all based on MochiKit.
+Dygraph.update = function (self, o) {
+ if (typeof(o) != 'undefined' && o !== null) {
+ for (var k in o) {
+ if (o.hasOwnProperty(k)) {
+ self[k] = o[k];
+ }
+ }
+ }
+ return self;
+};
+
+Dygraph.isArrayLike = function (o) {
+ var typ = typeof(o);
+ if (
+ (typ != 'object' && !(typ == 'function' &&
+ typeof(o.item) == 'function')) ||
+ o === null ||
+ typeof(o.length) != 'number' ||
+ o.nodeType === 3
+ ) {
+ return false;
+ }
+ return true;
+};
+
+Dygraph.isDateLike = function (o) {
+ if (typeof(o) != "object" || o === null ||
+ typeof(o.getTime) != 'function') {
+ return false;
+ }
+ return true;
+};
+
+Dygraph.clone = function(o) {
+ // TODO(danvk): figure out how MochiKit's version works
+ var r = [];
+ for (var i = 0; i < o.length; i++) {
+ if (Dygraph.isArrayLike(o[i])) {
+ r.push(Dygraph.clone(o[i]));
+ } else {
+ r.push(o[i]);
+ }
+ }
+ return r;
+};
+
+
+/**