| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582 | 
							- /**
 
-  * @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));
 
 
  |