yujian
2020-01-18 f4a0f2acc63d7785eab108419a4e16f5f688cb95
fanli/src/main/webapp/admin/new/js/third-party/highcharts/modules/data.src.js
@@ -1,582 +1,582 @@
/**
 * @license Data plugin for Highcharts
 *
 * (c) 2012-2013 Torstein Hønsi
 * Last revision 2013-06-07
 *
 * License: www.highcharts.com/license
 */
/*
 * The Highcharts Data plugin is a utility to ease parsing of input sources like
 * CSV, HTML tables or grid views into basic configuration options for use
 * directly in the Highcharts constructor.
 *
 * Demo: http://jsfiddle.net/highcharts/SnLFj/
 *
 * --- OPTIONS ---
 *
 * - columns : Array<Array<Mixed>>
 * A two-dimensional array representing the input data on tabular form. This input can
 * be used when the data is already parsed, for example from a grid view component.
 * Each cell can be a string or number. If not switchRowsAndColumns is set, the columns
 * are interpreted as series. See also the rows option.
 *
 * - complete : Function(chartOptions)
 * The callback that is evaluated when the data is finished loading, optionally from an
 * external source, and parsed. The first argument passed is a finished chart options
 * object, containing series and an xAxis with categories if applicable. Thise options
 * can be extended with additional options and passed directly to the chart constructor.
 *
 * - csv : String
 * A comma delimited string to be parsed. Related options are startRow, endRow, startColumn
 * and endColumn to delimit what part of the table is used. The lineDelimiter and
 * itemDelimiter options define the CSV delimiter formats.
 *
 * - endColumn : Integer
 * In tabular input data, the first row (indexed by 0) to use. Defaults to the last
 * column containing data.
 *
 * - endRow : Integer
 * In tabular input data, the last row (indexed by 0) to use. Defaults to the last row
 * containing data.
 *
 * - googleSpreadsheetKey : String
 * A Google Spreadsheet key. See https://developers.google.com/gdata/samples/spreadsheet_sample
 * for general information on GS.
 *
 * - googleSpreadsheetWorksheet : String
 * The Google Spreadsheet worksheet. The available id's can be read from
 * https://spreadsheets.google.com/feeds/worksheets/{key}/public/basic
 *
 * - itemDelimiter : String
 * Item or cell delimiter for parsing CSV. Defaults to ",".
 *
 * - lineDelimiter : String
 * Line delimiter for parsing CSV. Defaults to "\n".
 *
 * - parsed : Function
 * A callback function to access the parsed columns, the two-dimentional input data
 * array directly, before they are interpreted into series data and categories.
 *
 * - parseDate : Function
 * A callback function to parse string representations of dates into JavaScript timestamps.
 * Return an integer on success.
 *
 * - rows : Array<Array<Mixed>>
 * The same as the columns input option, but defining rows intead of columns.
 *
 * - startColumn : Integer
 * In tabular input data, the first column (indexed by 0) to use.
 *
 * - startRow : Integer
 * In tabular input data, the first row (indexed by 0) to use.
 *
 * - table : String|HTMLElement
 * A HTML table or the id of such to be parsed as input data. Related options ara startRow,
 * endRow, startColumn and endColumn to delimit what part of the table is used.
 */
// JSLint options:
/*global jQuery */
(function (Highcharts) {
   // Utilities
   var each = Highcharts.each;
   // The Data constructor
   var Data = function (dataOptions, chartOptions) {
      this.init(dataOptions, chartOptions);
   };
   // Set the prototype properties
   Highcharts.extend(Data.prototype, {
   /**
    * Initialize the Data object with the given options
    */
   init: function (options, chartOptions) {
      this.options = options;
      this.chartOptions = chartOptions;
      this.columns = options.columns || this.rowsToColumns(options.rows) || [];
      // No need to parse or interpret anything
      if (this.columns.length) {
         this.dataFound();
      // Parse and interpret
      } else {
         // Parse a CSV string if options.csv is given
         this.parseCSV();
         // Parse a HTML table if options.table is given
         this.parseTable();
         // Parse a Google Spreadsheet
         this.parseGoogleSpreadsheet();
      }
   },
   /**
    * Get the column distribution. For example, a line series takes a single column for
    * Y values. A range series takes two columns for low and high values respectively,
    * and an OHLC series takes four columns.
    */
   getColumnDistribution: function () {
      var chartOptions = this.chartOptions,
         getValueCount = function (type) {
            return (Highcharts.seriesTypes[type || 'line'].prototype.pointArrayMap || [0]).length;
         },
         globalType = chartOptions && chartOptions.chart && chartOptions.chart.type,
         individualCounts = [];
      each((chartOptions && chartOptions.series) || [], function (series) {
         individualCounts.push(getValueCount(series.type || globalType));
      });
      this.valueCount = {
         global: getValueCount(globalType),
         individual: individualCounts
      };
   },
   dataFound: function () {
      // Interpret the values into right types
      this.parseTypes();
      // Use first row for series names?
      this.findHeaderRow();
      // Handle columns if a handleColumns callback is given
      this.parsed();
      // Complete if a complete callback is given
      this.complete();
   },
   /**
    * Parse a CSV input string
    */
   parseCSV: function () {
      var self = this,
         options = this.options,
         csv = options.csv,
         columns = this.columns,
         startRow = options.startRow || 0,
         endRow = options.endRow || Number.MAX_VALUE,
         startColumn = options.startColumn || 0,
         endColumn = options.endColumn || Number.MAX_VALUE,
         lines,
         activeRowNo = 0;
      if (csv) {
         lines = csv
            .replace(/\r\n/g, "\n") // Unix
            .replace(/\r/g, "\n") // Mac
            .split(options.lineDelimiter || "\n");
         each(lines, function (line, rowNo) {
            var trimmed = self.trim(line),
               isComment = trimmed.indexOf('#') === 0,
               isBlank = trimmed === '',
               items;
            if (rowNo >= startRow && rowNo <= endRow && !isComment && !isBlank) {
               items = line.split(options.itemDelimiter || ',');
               each(items, function (item, colNo) {
                  if (colNo >= startColumn && colNo <= endColumn) {
                     if (!columns[colNo - startColumn]) {
                        columns[colNo - startColumn] = [];
                     }
                     columns[colNo - startColumn][activeRowNo] = item;
                  }
               });
               activeRowNo += 1;
            }
         });
         this.dataFound();
      }
   },
   /**
    * Parse a HTML table
    */
   parseTable: function () {
      var options = this.options,
         table = options.table,
         columns = this.columns,
         startRow = options.startRow || 0,
         endRow = options.endRow || Number.MAX_VALUE,
         startColumn = options.startColumn || 0,
         endColumn = options.endColumn || Number.MAX_VALUE,
         colNo;
      if (table) {
         if (typeof table === 'string') {
            table = document.getElementById(table);
         }
         each(table.getElementsByTagName('tr'), function (tr, rowNo) {
            colNo = 0;
            if (rowNo >= startRow && rowNo <= endRow) {
               each(tr.childNodes, function (item) {
                  if ((item.tagName === 'TD' || item.tagName === 'TH') && colNo >= startColumn && colNo <= endColumn) {
                     if (!columns[colNo]) {
                        columns[colNo] = [];
                     }
                     columns[colNo][rowNo - startRow] = item.innerHTML;
                     colNo += 1;
                  }
               });
            }
         });
         this.dataFound(); // continue
      }
   },
   /**
    * TODO:
    * - switchRowsAndColumns
    */
   parseGoogleSpreadsheet: function () {
      var self = this,
         options = this.options,
         googleSpreadsheetKey = options.googleSpreadsheetKey,
         columns = this.columns,
         startRow = options.startRow || 0,
         endRow = options.endRow || Number.MAX_VALUE,
         startColumn = options.startColumn || 0,
         endColumn = options.endColumn || Number.MAX_VALUE,
         gr, // google row
         gc; // google column
      if (googleSpreadsheetKey) {
         jQuery.getJSON('https://spreadsheets.google.com/feeds/cells/' +
              googleSpreadsheetKey + '/' + (options.googleSpreadsheetWorksheet || 'od6') +
                 '/public/values?alt=json-in-script&callback=?',
                 function (json) {
            // Prepare the data from the spreadsheat
            var cells = json.feed.entry,
               cell,
               cellCount = cells.length,
               colCount = 0,
               rowCount = 0,
               i;
            // First, find the total number of columns and rows that
            // are actually filled with data
            for (i = 0; i < cellCount; i++) {
               cell = cells[i];
               colCount = Math.max(colCount, cell.gs$cell.col);
               rowCount = Math.max(rowCount, cell.gs$cell.row);
            }
            // Set up arrays containing the column data
            for (i = 0; i < colCount; i++) {
               if (i >= startColumn && i <= endColumn) {
                  // Create new columns with the length of either end-start or rowCount
                  columns[i - startColumn] = [];
                  // Setting the length to avoid jslint warning
                  columns[i - startColumn].length = Math.min(rowCount, endRow - startRow);
               }
            }
            // Loop over the cells and assign the value to the right
            // place in the column arrays
            for (i = 0; i < cellCount; i++) {
               cell = cells[i];
               gr = cell.gs$cell.row - 1; // rows start at 1
               gc = cell.gs$cell.col - 1; // columns start at 1
               // If both row and col falls inside start and end
               // set the transposed cell value in the newly created columns
               if (gc >= startColumn && gc <= endColumn &&
                  gr >= startRow && gr <= endRow) {
                  columns[gc - startColumn][gr - startRow] = cell.content.$t;
               }
            }
            self.dataFound();
         });
      }
   },
   /**
    * Find the header row. For now, we just check whether the first row contains
    * numbers or strings. Later we could loop down and find the first row with
    * numbers.
    */
   findHeaderRow: function () {
      var headerRow = 0;
      each(this.columns, function (column) {
         if (typeof column[0] !== 'string') {
            headerRow = null;
         }
      });
      this.headerRow = 0;
   },
   /**
    * Trim a string from whitespace
    */
   trim: function (str) {
      return typeof str === 'string' ? str.replace(/^\s+|\s+$/g, '') : str;
   },
   /**
    * Parse numeric cells in to number types and date types in to true dates.
    * @param {Object} columns
    */
   parseTypes: function () {
      var columns = this.columns,
         col = columns.length,
         row,
         val,
         floatVal,
         trimVal,
         dateVal;
      while (col--) {
         row = columns[col].length;
         while (row--) {
            val = columns[col][row];
            floatVal = parseFloat(val);
            trimVal = this.trim(val);
            /*jslint eqeq: true*/
            if (trimVal == floatVal) { // is numeric
            /*jslint eqeq: false*/
               columns[col][row] = floatVal;
               // If the number is greater than milliseconds in a year, assume datetime
               if (floatVal > 365 * 24 * 3600 * 1000) {
                  columns[col].isDatetime = true;
               } else {
                  columns[col].isNumeric = true;
               }
            } else { // string, continue to determine if it is a date string or really a string
               dateVal = this.parseDate(val);
               if (col === 0 && typeof dateVal === 'number' && !isNaN(dateVal)) { // is date
                  columns[col][row] = dateVal;
                  columns[col].isDatetime = true;
               } else { // string
                  columns[col][row] = trimVal === '' ? null : trimVal;
               }
            }
         }
      }
   },
   //*
   dateFormats: {
      'YYYY-mm-dd': {
         regex: '^([0-9]{4})-([0-9]{2})-([0-9]{2})$',
         parser: function (match) {
            return Date.UTC(+match[1], match[2] - 1, +match[3]);
         }
      }
   },
   // */
   /**
    * Parse a date and return it as a number. Overridable through options.parseDate.
    */
   parseDate: function (val) {
      var parseDate = this.options.parseDate,
         ret,
         key,
         format,
         match;
      if (parseDate) {
         ret = parseDate(val);
      }
      if (typeof val === 'string') {
         for (key in this.dateFormats) {
            format = this.dateFormats[key];
            match = val.match(format.regex);
            if (match) {
               ret = format.parser(match);
            }
         }
      }
      return ret;
   },
   /**
    * Reorganize rows into columns
    */
   rowsToColumns: function (rows) {
      var row,
         rowsLength,
         col,
         colsLength,
         columns;
      if (rows) {
         columns = [];
         rowsLength = rows.length;
         for (row = 0; row < rowsLength; row++) {
            colsLength = rows[row].length;
            for (col = 0; col < colsLength; col++) {
               if (!columns[col]) {
                  columns[col] = [];
               }
               columns[col][row] = rows[row][col];
            }
         }
      }
      return columns;
   },
   /**
    * A hook for working directly on the parsed columns
    */
   parsed: function () {
      if (this.options.parsed) {
         this.options.parsed.call(this, this.columns);
      }
   },
   /**
    * If a complete callback function is provided in the options, interpret the
    * columns into a Highcharts options object.
    */
   complete: function () {
      var columns = this.columns,
         firstCol,
         type,
         options = this.options,
         valueCount,
         series,
         data,
         i,
         j,
         seriesIndex;
      if (options.complete) {
         this.getColumnDistribution();
         // Use first column for X data or categories?
         if (columns.length > 1) {
            firstCol = columns.shift();
            if (this.headerRow === 0) {
               firstCol.shift(); // remove the first cell
            }
            if (firstCol.isDatetime) {
               type = 'datetime';
            } else if (!firstCol.isNumeric) {
               type = 'category';
            }
         }
         // Get the names and shift the top row
         for (i = 0; i < columns.length; i++) {
            if (this.headerRow === 0) {
               columns[i].name = columns[i].shift();
            }
         }
         // Use the next columns for series
         series = [];
         for (i = 0, seriesIndex = 0; i < columns.length; seriesIndex++) {
            // This series' value count
            valueCount = Highcharts.pick(this.valueCount.individual[seriesIndex], this.valueCount.global);
            // Iterate down the cells of each column and add data to the series
            data = [];
            for (j = 0; j < columns[i].length; j++) {
               data[j] = [
                  firstCol[j],
                  columns[i][j] !== undefined ? columns[i][j] : null
               ];
               if (valueCount > 1) {
                  data[j].push(columns[i + 1][j] !== undefined ? columns[i + 1][j] : null);
               }
               if (valueCount > 2) {
                  data[j].push(columns[i + 2][j] !== undefined ? columns[i + 2][j] : null);
               }
               if (valueCount > 3) {
                  data[j].push(columns[i + 3][j] !== undefined ? columns[i + 3][j] : null);
               }
               if (valueCount > 4) {
                  data[j].push(columns[i + 4][j] !== undefined ? columns[i + 4][j] : null);
               }
            }
            // Add the series
            series[seriesIndex] = {
               name: columns[i].name,
               data: data
            };
            i += valueCount;
         }
         // Do the callback
         options.complete({
            xAxis: {
               type: type
            },
            series: series
         });
      }
   }
   });
   // Register the Data prototype and data function on Highcharts
   Highcharts.Data = Data;
   Highcharts.data = function (options, chartOptions) {
      return new Data(options, chartOptions);
   };
   // Extend Chart.init so that the Chart constructor accepts a new configuration
   // option group, data.
   Highcharts.wrap(Highcharts.Chart.prototype, 'init', function (proceed, userOptions, callback) {
      var chart = this;
      if (userOptions && userOptions.data) {
         Highcharts.data(Highcharts.extend(userOptions.data, {
            complete: function (dataOptions) {
               // Merge series configs
               if (userOptions.series) {
                  each(userOptions.series, function (series, i) {
                     userOptions.series[i] = Highcharts.merge(series, dataOptions.series[i]);
                  });
               }
               // Do the merge
               userOptions = Highcharts.merge(dataOptions, userOptions);
               proceed.call(chart, userOptions, callback);
            }
         }), userOptions);
      } else {
         proceed.call(chart, userOptions, callback);
      }
   });
}(Highcharts));
/**
 * @license Data plugin for Highcharts
 *
 * (c) 2012-2013 Torstein Hønsi
 * Last revision 2013-06-07
 *
 * License: www.highcharts.com/license
 */
/*
 * The Highcharts Data plugin is a utility to ease parsing of input sources like
 * CSV, HTML tables or grid views into basic configuration options for use
 * directly in the Highcharts constructor.
 *
 * Demo: http://jsfiddle.net/highcharts/SnLFj/
 *
 * --- OPTIONS ---
 *
 * - columns : Array<Array<Mixed>>
 * A two-dimensional array representing the input data on tabular form. This input can
 * be used when the data is already parsed, for example from a grid view component.
 * Each cell can be a string or number. If not switchRowsAndColumns is set, the columns
 * are interpreted as series. See also the rows option.
 *
 * - complete : Function(chartOptions)
 * The callback that is evaluated when the data is finished loading, optionally from an
 * external source, and parsed. The first argument passed is a finished chart options
 * object, containing series and an xAxis with categories if applicable. Thise options
 * can be extended with additional options and passed directly to the chart constructor.
 *
 * - csv : String
 * A comma delimited string to be parsed. Related options are startRow, endRow, startColumn
 * and endColumn to delimit what part of the table is used. The lineDelimiter and
 * itemDelimiter options define the CSV delimiter formats.
 *
 * - endColumn : Integer
 * In tabular input data, the first row (indexed by 0) to use. Defaults to the last
 * column containing data.
 *
 * - endRow : Integer
 * In tabular input data, the last row (indexed by 0) to use. Defaults to the last row
 * containing data.
 *
 * - googleSpreadsheetKey : String
 * A Google Spreadsheet key. See https://developers.google.com/gdata/samples/spreadsheet_sample
 * for general information on GS.
 *
 * - googleSpreadsheetWorksheet : String
 * The Google Spreadsheet worksheet. The available id's can be read from
 * https://spreadsheets.google.com/feeds/worksheets/{key}/public/basic
 *
 * - itemDelimiter : String
 * Item or cell delimiter for parsing CSV. Defaults to ",".
 *
 * - lineDelimiter : String
 * Line delimiter for parsing CSV. Defaults to "\n".
 *
 * - parsed : Function
 * A callback function to access the parsed columns, the two-dimentional input data
 * array directly, before they are interpreted into series data and categories.
 *
 * - parseDate : Function
 * A callback function to parse string representations of dates into JavaScript timestamps.
 * Return an integer on success.
 *
 * - rows : Array<Array<Mixed>>
 * The same as the columns input option, but defining rows intead of columns.
 *
 * - startColumn : Integer
 * In tabular input data, the first column (indexed by 0) to use.
 *
 * - startRow : Integer
 * In tabular input data, the first row (indexed by 0) to use.
 *
 * - table : String|HTMLElement
 * A HTML table or the id of such to be parsed as input data. Related options ara startRow,
 * endRow, startColumn and endColumn to delimit what part of the table is used.
 */
// JSLint options:
/*global jQuery */
(function (Highcharts) {
   // Utilities
   var each = Highcharts.each;
   // The Data constructor
   var Data = function (dataOptions, chartOptions) {
      this.init(dataOptions, chartOptions);
   };
   // Set the prototype properties
   Highcharts.extend(Data.prototype, {
   /**
    * Initialize the Data object with the given options
    */
   init: function (options, chartOptions) {
      this.options = options;
      this.chartOptions = chartOptions;
      this.columns = options.columns || this.rowsToColumns(options.rows) || [];
      // No need to parse or interpret anything
      if (this.columns.length) {
         this.dataFound();
      // Parse and interpret
      } else {
         // Parse a CSV string if options.csv is given
         this.parseCSV();
         // Parse a HTML table if options.table is given
         this.parseTable();
         // Parse a Google Spreadsheet
         this.parseGoogleSpreadsheet();
      }
   },
   /**
    * Get the column distribution. For example, a line series takes a single column for
    * Y values. A range series takes two columns for low and high values respectively,
    * and an OHLC series takes four columns.
    */
   getColumnDistribution: function () {
      var chartOptions = this.chartOptions,
         getValueCount = function (type) {
            return (Highcharts.seriesTypes[type || 'line'].prototype.pointArrayMap || [0]).length;
         },
         globalType = chartOptions && chartOptions.chart && chartOptions.chart.type,
         individualCounts = [];
      each((chartOptions && chartOptions.series) || [], function (series) {
         individualCounts.push(getValueCount(series.type || globalType));
      });
      this.valueCount = {
         global: getValueCount(globalType),
         individual: individualCounts
      };
   },
   dataFound: function () {
      // Interpret the values into right types
      this.parseTypes();
      // Use first row for series names?
      this.findHeaderRow();
      // Handle columns if a handleColumns callback is given
      this.parsed();
      // Complete if a complete callback is given
      this.complete();
   },
   /**
    * Parse a CSV input string
    */
   parseCSV: function () {
      var self = this,
         options = this.options,
         csv = options.csv,
         columns = this.columns,
         startRow = options.startRow || 0,
         endRow = options.endRow || Number.MAX_VALUE,
         startColumn = options.startColumn || 0,
         endColumn = options.endColumn || Number.MAX_VALUE,
         lines,
         activeRowNo = 0;
      if (csv) {
         lines = csv
            .replace(/\r\n/g, "\n") // Unix
            .replace(/\r/g, "\n") // Mac
            .split(options.lineDelimiter || "\n");
         each(lines, function (line, rowNo) {
            var trimmed = self.trim(line),
               isComment = trimmed.indexOf('#') === 0,
               isBlank = trimmed === '',
               items;
            if (rowNo >= startRow && rowNo <= endRow && !isComment && !isBlank) {
               items = line.split(options.itemDelimiter || ',');
               each(items, function (item, colNo) {
                  if (colNo >= startColumn && colNo <= endColumn) {
                     if (!columns[colNo - startColumn]) {
                        columns[colNo - startColumn] = [];
                     }
                     columns[colNo - startColumn][activeRowNo] = item;
                  }
               });
               activeRowNo += 1;
            }
         });
         this.dataFound();
      }
   },
   /**
    * Parse a HTML table
    */
   parseTable: function () {
      var options = this.options,
         table = options.table,
         columns = this.columns,
         startRow = options.startRow || 0,
         endRow = options.endRow || Number.MAX_VALUE,
         startColumn = options.startColumn || 0,
         endColumn = options.endColumn || Number.MAX_VALUE,
         colNo;
      if (table) {
         if (typeof table === 'string') {
            table = document.getElementById(table);
         }
         each(table.getElementsByTagName('tr'), function (tr, rowNo) {
            colNo = 0;
            if (rowNo >= startRow && rowNo <= endRow) {
               each(tr.childNodes, function (item) {
                  if ((item.tagName === 'TD' || item.tagName === 'TH') && colNo >= startColumn && colNo <= endColumn) {
                     if (!columns[colNo]) {
                        columns[colNo] = [];
                     }
                     columns[colNo][rowNo - startRow] = item.innerHTML;
                     colNo += 1;
                  }
               });
            }
         });
         this.dataFound(); // continue
      }
   },
   /**
    * TODO:
    * - switchRowsAndColumns
    */
   parseGoogleSpreadsheet: function () {
      var self = this,
         options = this.options,
         googleSpreadsheetKey = options.googleSpreadsheetKey,
         columns = this.columns,
         startRow = options.startRow || 0,
         endRow = options.endRow || Number.MAX_VALUE,
         startColumn = options.startColumn || 0,
         endColumn = options.endColumn || Number.MAX_VALUE,
         gr, // google row
         gc; // google column
      if (googleSpreadsheetKey) {
         jQuery.getJSON('https://spreadsheets.google.com/feeds/cells/' +
              googleSpreadsheetKey + '/' + (options.googleSpreadsheetWorksheet || 'od6') +
                 '/public/values?alt=json-in-script&callback=?',
                 function (json) {
            // Prepare the data from the spreadsheat
            var cells = json.feed.entry,
               cell,
               cellCount = cells.length,
               colCount = 0,
               rowCount = 0,
               i;
            // First, find the total number of columns and rows that
            // are actually filled with data
            for (i = 0; i < cellCount; i++) {
               cell = cells[i];
               colCount = Math.max(colCount, cell.gs$cell.col);
               rowCount = Math.max(rowCount, cell.gs$cell.row);
            }
            // Set up arrays containing the column data
            for (i = 0; i < colCount; i++) {
               if (i >= startColumn && i <= endColumn) {
                  // Create new columns with the length of either end-start or rowCount
                  columns[i - startColumn] = [];
                  // Setting the length to avoid jslint warning
                  columns[i - startColumn].length = Math.min(rowCount, endRow - startRow);
               }
            }
            // Loop over the cells and assign the value to the right
            // place in the column arrays
            for (i = 0; i < cellCount; i++) {
               cell = cells[i];
               gr = cell.gs$cell.row - 1; // rows start at 1
               gc = cell.gs$cell.col - 1; // columns start at 1
               // If both row and col falls inside start and end
               // set the transposed cell value in the newly created columns
               if (gc >= startColumn && gc <= endColumn &&
                  gr >= startRow && gr <= endRow) {
                  columns[gc - startColumn][gr - startRow] = cell.content.$t;
               }
            }
            self.dataFound();
         });
      }
   },
   /**
    * Find the header row. For now, we just check whether the first row contains
    * numbers or strings. Later we could loop down and find the first row with
    * numbers.
    */
   findHeaderRow: function () {
      var headerRow = 0;
      each(this.columns, function (column) {
         if (typeof column[0] !== 'string') {
            headerRow = null;
         }
      });
      this.headerRow = 0;
   },
   /**
    * Trim a string from whitespace
    */
   trim: function (str) {
      return typeof str === 'string' ? str.replace(/^\s+|\s+$/g, '') : str;
   },
   /**
    * Parse numeric cells in to number types and date types in to true dates.
    * @param {Object} columns
    */
   parseTypes: function () {
      var columns = this.columns,
         col = columns.length,
         row,
         val,
         floatVal,
         trimVal,
         dateVal;
      while (col--) {
         row = columns[col].length;
         while (row--) {
            val = columns[col][row];
            floatVal = parseFloat(val);
            trimVal = this.trim(val);
            /*jslint eqeq: true*/
            if (trimVal == floatVal) { // is numeric
            /*jslint eqeq: false*/
               columns[col][row] = floatVal;
               // If the number is greater than milliseconds in a year, assume datetime
               if (floatVal > 365 * 24 * 3600 * 1000) {
                  columns[col].isDatetime = true;
               } else {
                  columns[col].isNumeric = true;
               }
            } else { // string, continue to determine if it is a date string or really a string
               dateVal = this.parseDate(val);
               if (col === 0 && typeof dateVal === 'number' && !isNaN(dateVal)) { // is date
                  columns[col][row] = dateVal;
                  columns[col].isDatetime = true;
               } else { // string
                  columns[col][row] = trimVal === '' ? null : trimVal;
               }
            }
         }
      }
   },
   //*
   dateFormats: {
      'YYYY-mm-dd': {
         regex: '^([0-9]{4})-([0-9]{2})-([0-9]{2})$',
         parser: function (match) {
            return Date.UTC(+match[1], match[2] - 1, +match[3]);
         }
      }
   },
   // */
   /**
    * Parse a date and return it as a number. Overridable through options.parseDate.
    */
   parseDate: function (val) {
      var parseDate = this.options.parseDate,
         ret,
         key,
         format,
         match;
      if (parseDate) {
         ret = parseDate(val);
      }
      if (typeof val === 'string') {
         for (key in this.dateFormats) {
            format = this.dateFormats[key];
            match = val.match(format.regex);
            if (match) {
               ret = format.parser(match);
            }
         }
      }
      return ret;
   },
   /**
    * Reorganize rows into columns
    */
   rowsToColumns: function (rows) {
      var row,
         rowsLength,
         col,
         colsLength,
         columns;
      if (rows) {
         columns = [];
         rowsLength = rows.length;
         for (row = 0; row < rowsLength; row++) {
            colsLength = rows[row].length;
            for (col = 0; col < colsLength; col++) {
               if (!columns[col]) {
                  columns[col] = [];
               }
               columns[col][row] = rows[row][col];
            }
         }
      }
      return columns;
   },
   /**
    * A hook for working directly on the parsed columns
    */
   parsed: function () {
      if (this.options.parsed) {
         this.options.parsed.call(this, this.columns);
      }
   },
   /**
    * If a complete callback function is provided in the options, interpret the
    * columns into a Highcharts options object.
    */
   complete: function () {
      var columns = this.columns,
         firstCol,
         type,
         options = this.options,
         valueCount,
         series,
         data,
         i,
         j,
         seriesIndex;
      if (options.complete) {
         this.getColumnDistribution();
         // Use first column for X data or categories?
         if (columns.length > 1) {
            firstCol = columns.shift();
            if (this.headerRow === 0) {
               firstCol.shift(); // remove the first cell
            }
            if (firstCol.isDatetime) {
               type = 'datetime';
            } else if (!firstCol.isNumeric) {
               type = 'category';
            }
         }
         // Get the names and shift the top row
         for (i = 0; i < columns.length; i++) {
            if (this.headerRow === 0) {
               columns[i].name = columns[i].shift();
            }
         }
         // Use the next columns for series
         series = [];
         for (i = 0, seriesIndex = 0; i < columns.length; seriesIndex++) {
            // This series' value count
            valueCount = Highcharts.pick(this.valueCount.individual[seriesIndex], this.valueCount.global);
            // Iterate down the cells of each column and add data to the series
            data = [];
            for (j = 0; j < columns[i].length; j++) {
               data[j] = [
                  firstCol[j],
                  columns[i][j] !== undefined ? columns[i][j] : null
               ];
               if (valueCount > 1) {
                  data[j].push(columns[i + 1][j] !== undefined ? columns[i + 1][j] : null);
               }
               if (valueCount > 2) {
                  data[j].push(columns[i + 2][j] !== undefined ? columns[i + 2][j] : null);
               }
               if (valueCount > 3) {
                  data[j].push(columns[i + 3][j] !== undefined ? columns[i + 3][j] : null);
               }
               if (valueCount > 4) {
                  data[j].push(columns[i + 4][j] !== undefined ? columns[i + 4][j] : null);
               }
            }
            // Add the series
            series[seriesIndex] = {
               name: columns[i].name,
               data: data
            };
            i += valueCount;
         }
         // Do the callback
         options.complete({
            xAxis: {
               type: type
            },
            series: series
         });
      }
   }
   });
   // Register the Data prototype and data function on Highcharts
   Highcharts.Data = Data;
   Highcharts.data = function (options, chartOptions) {
      return new Data(options, chartOptions);
   };
   // Extend Chart.init so that the Chart constructor accepts a new configuration
   // option group, data.
   Highcharts.wrap(Highcharts.Chart.prototype, 'init', function (proceed, userOptions, callback) {
      var chart = this;
      if (userOptions && userOptions.data) {
         Highcharts.data(Highcharts.extend(userOptions.data, {
            complete: function (dataOptions) {
               // Merge series configs
               if (userOptions.series) {
                  each(userOptions.series, function (series, i) {
                     userOptions.series[i] = Highcharts.merge(series, dataOptions.series[i]);
                  });
               }
               // Do the merge
               userOptions = Highcharts.merge(dataOptions, userOptions);
               proceed.call(chart, userOptions, callback);
            }
         }), userOptions);
      } else {
         proceed.call(chart, userOptions, callback);
      }
   });
}(Highcharts));