corrade-nucleus-nucleons – Diff between revs 1 and 11

Subversion Repositories:
Rev:
Only display areas with differencesIgnore whitespace
Rev 1 Rev 11
1 /** 1 /**
2 * @license Highcharts JS v5.0.10 (2017-03-31) 2 * @license Highcharts JS v5.0.12 (2017-05-24)
3 * Data module 3 * Data module
4 * 4 *
5 * (c) 2012-2017 Torstein Honsi 5 * (c) 2012-2017 Torstein Honsi
6 * 6 *
7 * License: www.highcharts.com/license 7 * License: www.highcharts.com/license
8 */ 8 */
9 'use strict'; 9 'use strict';
10 (function(factory) { 10 (function(factory) {
11 if (typeof module === 'object' && module.exports) { 11 if (typeof module === 'object' && module.exports) {
12 module.exports = factory; 12 module.exports = factory;
13 } else { 13 } else {
14 factory(Highcharts); 14 factory(Highcharts);
15 } 15 }
16 }(function(Highcharts) { 16 }(function(Highcharts) {
17 (function(Highcharts) { 17 (function(Highcharts) {
18 /** 18 /**
19 * Data module 19 * Data module
20 * 20 *
21 * (c) 2012-2017 Torstein Honsi 21 * (c) 2012-2017 Torstein Honsi
22 * 22 *
23 * License: www.highcharts.com/license 23 * License: www.highcharts.com/license
24 */ 24 */
25   25  
26 /* global jQuery */ 26 /* global jQuery */
27   27  
28 // Utilities 28 // Utilities
29 var win = Highcharts.win, 29 var win = Highcharts.win,
30 doc = win.document, 30 doc = win.document,
31 each = Highcharts.each, 31 each = Highcharts.each,
-   32 objectEach = Highcharts.objectEach,
32 pick = Highcharts.pick, 33 pick = Highcharts.pick,
33 inArray = Highcharts.inArray, 34 inArray = Highcharts.inArray,
34 isNumber = Highcharts.isNumber, 35 isNumber = Highcharts.isNumber,
35 splat = Highcharts.splat, 36 splat = Highcharts.splat,
36 SeriesBuilder; 37 SeriesBuilder;
37   38  
38   39  
39 // The Data constructor 40 // The Data constructor
40 var Data = function(dataOptions, chartOptions) { 41 var Data = function(dataOptions, chartOptions) {
41 this.init(dataOptions, chartOptions); 42 this.init(dataOptions, chartOptions);
42 }; 43 };
43   44  
44 // Set the prototype properties 45 // Set the prototype properties
45 Highcharts.extend(Data.prototype, { 46 Highcharts.extend(Data.prototype, {
46   47  
47 /** 48 /**
48 * Initialize the Data object with the given options 49 * Initialize the Data object with the given options
49 */ 50 */
50 init: function(options, chartOptions) { 51 init: function(options, chartOptions) {
51 this.options = options; 52 this.options = options;
52 this.chartOptions = chartOptions; 53 this.chartOptions = chartOptions;
53 this.columns = options.columns || this.rowsToColumns(options.rows) || []; 54 this.columns = options.columns || this.rowsToColumns(options.rows) || [];
54 this.firstRowAsNames = pick(options.firstRowAsNames, true); 55 this.firstRowAsNames = pick(options.firstRowAsNames, true);
55 this.decimalRegex = options.decimalPoint && new RegExp('^(-?[0-9]+)' + options.decimalPoint + '([0-9]+)$'); 56 this.decimalRegex = options.decimalPoint && new RegExp('^(-?[0-9]+)' + options.decimalPoint + '([0-9]+)$');
56   57  
57 // This is a two-dimensional array holding the raw, trimmed string values 58 // This is a two-dimensional array holding the raw, trimmed string values
58 // with the same organisation as the columns array. It makes it possible 59 // with the same organisation as the columns array. It makes it possible
59 // for example to revert from interpreted timestamps to string-based 60 // for example to revert from interpreted timestamps to string-based
60 // categories. 61 // categories.
61 this.rawColumns = []; 62 this.rawColumns = [];
62   63  
63 // No need to parse or interpret anything 64 // No need to parse or interpret anything
64 if (this.columns.length) { 65 if (this.columns.length) {
65 this.dataFound(); 66 this.dataFound();
66   67  
67 // Parse and interpret 68 // Parse and interpret
68 } else { 69 } else {
69   70  
70 // Parse a CSV string if options.csv is given 71 // Parse a CSV string if options.csv is given
71 this.parseCSV(); 72 this.parseCSV();
72   73  
73 // Parse a HTML table if options.table is given 74 // Parse a HTML table if options.table is given
74 this.parseTable(); 75 this.parseTable();
75   76  
76 // Parse a Google Spreadsheet 77 // Parse a Google Spreadsheet
77 this.parseGoogleSpreadsheet(); 78 this.parseGoogleSpreadsheet();
78 } 79 }
79   80  
80 }, 81 },
81   82  
82 /** 83 /**
83 * Get the column distribution. For example, a line series takes a single column for 84 * Get the column distribution. For example, a line series takes a single column for
84 * Y values. A range series takes two columns for low and high values respectively, 85 * Y values. A range series takes two columns for low and high values respectively,
85 * and an OHLC series takes four columns. 86 * and an OHLC series takes four columns.
86 */ 87 */
87 getColumnDistribution: function() { 88 getColumnDistribution: function() {
88 var chartOptions = this.chartOptions, 89 var chartOptions = this.chartOptions,
89 options = this.options, 90 options = this.options,
90 xColumns = [], 91 xColumns = [],
91 getValueCount = function(type) { 92 getValueCount = function(type) {
92 return (Highcharts.seriesTypes[type || 'line'].prototype.pointArrayMap || [0]).length; 93 return (Highcharts.seriesTypes[type || 'line'].prototype.pointArrayMap || [0]).length;
93 }, 94 },
94 getPointArrayMap = function(type) { 95 getPointArrayMap = function(type) {
95 return Highcharts.seriesTypes[type || 'line'].prototype.pointArrayMap; 96 return Highcharts.seriesTypes[type || 'line'].prototype.pointArrayMap;
96 }, 97 },
97 globalType = chartOptions && chartOptions.chart && chartOptions.chart.type, 98 globalType = chartOptions && chartOptions.chart && chartOptions.chart.type,
98 individualCounts = [], 99 individualCounts = [],
99 seriesBuilders = [], 100 seriesBuilders = [],
100 seriesIndex = 0, 101 seriesIndex = 0,
101 i; 102 i;
102   103  
103 each((chartOptions && chartOptions.series) || [], function(series) { 104 each((chartOptions && chartOptions.series) || [], function(series) {
104 individualCounts.push(getValueCount(series.type || globalType)); 105 individualCounts.push(getValueCount(series.type || globalType));
105 }); 106 });
106   107  
107 // Collect the x-column indexes from seriesMapping 108 // Collect the x-column indexes from seriesMapping
108 each((options && options.seriesMapping) || [], function(mapping) { 109 each((options && options.seriesMapping) || [], function(mapping) {
109 xColumns.push(mapping.x || 0); 110 xColumns.push(mapping.x || 0);
110 }); 111 });
111   112  
112 // If there are no defined series with x-columns, use the first column as x column 113 // If there are no defined series with x-columns, use the first column as x column
113 if (xColumns.length === 0) { 114 if (xColumns.length === 0) {
114 xColumns.push(0); 115 xColumns.push(0);
115 } 116 }
116   117  
117 // Loop all seriesMappings and constructs SeriesBuilders from 118 // Loop all seriesMappings and constructs SeriesBuilders from
118 // the mapping options. 119 // the mapping options.
119 each((options && options.seriesMapping) || [], function(mapping) { 120 each((options && options.seriesMapping) || [], function(mapping) {
120 var builder = new SeriesBuilder(), 121 var builder = new SeriesBuilder(),
121 name, -  
122 numberOfValueColumnsNeeded = individualCounts[seriesIndex] || getValueCount(globalType), 122 numberOfValueColumnsNeeded = individualCounts[seriesIndex] || getValueCount(globalType),
123 seriesArr = (chartOptions && chartOptions.series) || [], 123 seriesArr = (chartOptions && chartOptions.series) || [],
124 series = seriesArr[seriesIndex] || {}, 124 series = seriesArr[seriesIndex] || {},
125 pointArrayMap = getPointArrayMap(series.type || globalType) || ['y']; 125 pointArrayMap = getPointArrayMap(series.type || globalType) || ['y'];
126   126  
127 // Add an x reader from the x property or from an undefined column 127 // Add an x reader from the x property or from an undefined column
128 // if the property is not set. It will then be auto populated later. 128 // if the property is not set. It will then be auto populated later.
129 builder.addColumnReader(mapping.x, 'x'); 129 builder.addColumnReader(mapping.x, 'x');
130   130  
131 // Add all column mappings 131 // Add all column mappings
132 for (name in mapping) { 132 objectEach(mapping, function(val, name) {
133 if (mapping.hasOwnProperty(name) && name !== 'x') { 133 if (name !== 'x') {
134 builder.addColumnReader(mapping[name], name); 134 builder.addColumnReader(val, name);
135 } 135 }
136 } 136 });
137   137  
138 // Add missing columns 138 // Add missing columns
139 for (i = 0; i < numberOfValueColumnsNeeded; i++) { 139 for (i = 0; i < numberOfValueColumnsNeeded; i++) {
140 if (!builder.hasReader(pointArrayMap[i])) { 140 if (!builder.hasReader(pointArrayMap[i])) {
141 //builder.addNextColumnReader(pointArrayMap[i]); 141 //builder.addNextColumnReader(pointArrayMap[i]);
142 // Create and add a column reader for the next free column index 142 // Create and add a column reader for the next free column index
143 builder.addColumnReader(undefined, pointArrayMap[i]); 143 builder.addColumnReader(undefined, pointArrayMap[i]);
144 } 144 }
145 } 145 }
146   146  
147 seriesBuilders.push(builder); 147 seriesBuilders.push(builder);
148 seriesIndex++; 148 seriesIndex++;
149 }); 149 });
150   150  
151 var globalPointArrayMap = getPointArrayMap(globalType); 151 var globalPointArrayMap = getPointArrayMap(globalType);
152 if (globalPointArrayMap === undefined) { 152 if (globalPointArrayMap === undefined) {
153 globalPointArrayMap = ['y']; 153 globalPointArrayMap = ['y'];
154 } 154 }
155   155  
156 this.valueCount = { 156 this.valueCount = {
157 global: getValueCount(globalType), 157 global: getValueCount(globalType),
158 xColumns: xColumns, 158 xColumns: xColumns,
159 individual: individualCounts, 159 individual: individualCounts,
160 seriesBuilders: seriesBuilders, 160 seriesBuilders: seriesBuilders,
161 globalPointArrayMap: globalPointArrayMap 161 globalPointArrayMap: globalPointArrayMap
162 }; 162 };
163 }, 163 },
164   164  
165 /** 165 /**
166 * When the data is parsed into columns, either by CSV, table, GS or direct input, 166 * When the data is parsed into columns, either by CSV, table, GS or direct input,
167 * continue with other operations. 167 * continue with other operations.
168 */ 168 */
169 dataFound: function() { 169 dataFound: function() {
170   170  
171 if (this.options.switchRowsAndColumns) { 171 if (this.options.switchRowsAndColumns) {
172 this.columns = this.rowsToColumns(this.columns); 172 this.columns = this.rowsToColumns(this.columns);
173 } 173 }
174   174  
175 // Interpret the info about series and columns 175 // Interpret the info about series and columns
176 this.getColumnDistribution(); 176 this.getColumnDistribution();
177   177  
178 // Interpret the values into right types 178 // Interpret the values into right types
179 this.parseTypes(); 179 this.parseTypes();
180   180  
181 // Handle columns if a handleColumns callback is given 181 // Handle columns if a handleColumns callback is given
182 if (this.parsed() !== false) { 182 if (this.parsed() !== false) {
183   183  
184 // Complete if a complete callback is given 184 // Complete if a complete callback is given
185 this.complete(); 185 this.complete();
186 } 186 }
187   187  
188 }, 188 },
189   189  
190 /** 190 /**
191 * Parse a CSV input string 191 * Parse a CSV input string
192 */ 192 */
193 parseCSV: function() { 193 parseCSV: function() {
194 var self = this, 194 var self = this,
195 options = this.options, 195 options = this.options,
196 csv = options.csv, 196 csv = options.csv,
197 columns = this.columns, 197 columns = this.columns,
198 startRow = options.startRow || 0, 198 startRow = options.startRow || 0,
199 endRow = options.endRow || Number.MAX_VALUE, 199 endRow = options.endRow || Number.MAX_VALUE,
200 startColumn = options.startColumn || 0, 200 startColumn = options.startColumn || 0,
201 endColumn = options.endColumn || Number.MAX_VALUE, 201 endColumn = options.endColumn || Number.MAX_VALUE,
202 itemDelimiter, 202 itemDelimiter,
203 lines, 203 lines,
204 activeRowNo = 0; 204 activeRowNo = 0;
205   205  
206 if (csv) { 206 if (csv) {
207   207  
208 lines = csv 208 lines = csv
209 .replace(/\r\n/g, '\n') // Unix 209 .replace(/\r\n/g, '\n') // Unix
210 .replace(/\r/g, '\n') // Mac 210 .replace(/\r/g, '\n') // Mac
211 .split(options.lineDelimiter || '\n'); 211 .split(options.lineDelimiter || '\n');
212   212  
213 itemDelimiter = options.itemDelimiter || (csv.indexOf('\t') !== -1 ? '\t' : ','); 213 itemDelimiter = options.itemDelimiter || (csv.indexOf('\t') !== -1 ? '\t' : ',');
214   214  
215 each(lines, function(line, rowNo) { 215 each(lines, function(line, rowNo) {
216 var trimmed = self.trim(line), 216 var trimmed = self.trim(line),
217 isComment = trimmed.indexOf('#') === 0, 217 isComment = trimmed.indexOf('#') === 0,
218 isBlank = trimmed === '', 218 isBlank = trimmed === '',
219 items; 219 items;
220   220  
221 if (rowNo >= startRow && rowNo <= endRow && !isComment && !isBlank) { 221 if (rowNo >= startRow && rowNo <= endRow && !isComment && !isBlank) {
222 items = line.split(itemDelimiter); 222 items = line.split(itemDelimiter);
223 each(items, function(item, colNo) { 223 each(items, function(item, colNo) {
224 if (colNo >= startColumn && colNo <= endColumn) { 224 if (colNo >= startColumn && colNo <= endColumn) {
225 if (!columns[colNo - startColumn]) { 225 if (!columns[colNo - startColumn]) {
226 columns[colNo - startColumn] = []; 226 columns[colNo - startColumn] = [];
227 } 227 }
228   228  
229 columns[colNo - startColumn][activeRowNo] = item; 229 columns[colNo - startColumn][activeRowNo] = item;
230 } 230 }
231 }); 231 });
232 activeRowNo += 1; 232 activeRowNo += 1;
233 } 233 }
234 }); 234 });
235   235  
236 this.dataFound(); 236 this.dataFound();
237 } 237 }
238 }, 238 },
239   239  
240 /** 240 /**
241 * Parse a HTML table 241 * Parse a HTML table
242 */ 242 */
243 parseTable: function() { 243 parseTable: function() {
244 var options = this.options, 244 var options = this.options,
245 table = options.table, 245 table = options.table,
246 columns = this.columns, 246 columns = this.columns,
247 startRow = options.startRow || 0, 247 startRow = options.startRow || 0,
248 endRow = options.endRow || Number.MAX_VALUE, 248 endRow = options.endRow || Number.MAX_VALUE,
249 startColumn = options.startColumn || 0, 249 startColumn = options.startColumn || 0,
250 endColumn = options.endColumn || Number.MAX_VALUE; 250 endColumn = options.endColumn || Number.MAX_VALUE;
251   251  
252 if (table) { 252 if (table) {
253   253  
254 if (typeof table === 'string') { 254 if (typeof table === 'string') {
255 table = doc.getElementById(table); 255 table = doc.getElementById(table);
256 } 256 }
257   257  
258 each(table.getElementsByTagName('tr'), function(tr, rowNo) { 258 each(table.getElementsByTagName('tr'), function(tr, rowNo) {
259 if (rowNo >= startRow && rowNo <= endRow) { 259 if (rowNo >= startRow && rowNo <= endRow) {
260 each(tr.children, function(item, colNo) { 260 each(tr.children, function(item, colNo) {
261 if ((item.tagName === 'TD' || item.tagName === 'TH') && colNo >= startColumn && colNo <= endColumn) { 261 if ((item.tagName === 'TD' || item.tagName === 'TH') && colNo >= startColumn && colNo <= endColumn) {
262 if (!columns[colNo - startColumn]) { 262 if (!columns[colNo - startColumn]) {
263 columns[colNo - startColumn] = []; 263 columns[colNo - startColumn] = [];
264 } 264 }
265   265  
266 columns[colNo - startColumn][rowNo - startRow] = item.innerHTML; 266 columns[colNo - startColumn][rowNo - startRow] = item.innerHTML;
267 } 267 }
268 }); 268 });
269 } 269 }
270 }); 270 });
271   271  
272 this.dataFound(); // continue 272 this.dataFound(); // continue
273 } 273 }
274 }, 274 },
275   275  
276 /** 276 /**
277 */ 277 */
278 parseGoogleSpreadsheet: function() { 278 parseGoogleSpreadsheet: function() {
279 var self = this, 279 var self = this,
280 options = this.options, 280 options = this.options,
281 googleSpreadsheetKey = options.googleSpreadsheetKey, 281 googleSpreadsheetKey = options.googleSpreadsheetKey,
282 columns = this.columns, 282 columns = this.columns,
283 startRow = options.startRow || 0, 283 startRow = options.startRow || 0,
284 endRow = options.endRow || Number.MAX_VALUE, 284 endRow = options.endRow || Number.MAX_VALUE,
285 startColumn = options.startColumn || 0, 285 startColumn = options.startColumn || 0,
286 endColumn = options.endColumn || Number.MAX_VALUE, 286 endColumn = options.endColumn || Number.MAX_VALUE,
287 gr, // google row 287 gr, // google row
288 gc; // google column 288 gc; // google column
289   289  
290 if (googleSpreadsheetKey) { 290 if (googleSpreadsheetKey) {
291 jQuery.ajax({ 291 jQuery.ajax({
292 dataType: 'json', 292 dataType: 'json',
293 url: 'https://spreadsheets.google.com/feeds/cells/' + 293 url: 'https://spreadsheets.google.com/feeds/cells/' +
294 googleSpreadsheetKey + '/' + (options.googleSpreadsheetWorksheet || 'od6') + 294 googleSpreadsheetKey + '/' + (options.googleSpreadsheetWorksheet || 'od6') +
295 '/public/values?alt=json-in-script&callback=?', 295 '/public/values?alt=json-in-script&callback=?',
296 error: options.error, 296 error: options.error,
297 success: function(json) { 297 success: function(json) {
298 // Prepare the data from the spreadsheat 298 // Prepare the data from the spreadsheat
299 var cells = json.feed.entry, 299 var cells = json.feed.entry,
300 cell, 300 cell,
301 cellCount = cells.length, 301 cellCount = cells.length,
302 colCount = 0, 302 colCount = 0,
303 rowCount = 0, 303 rowCount = 0,
304 i; 304 i;
305   305  
306 // First, find the total number of columns and rows that 306 // First, find the total number of columns and rows that
307 // are actually filled with data 307 // are actually filled with data
308 for (i = 0; i < cellCount; i++) { 308 for (i = 0; i < cellCount; i++) {
309 cell = cells[i]; 309 cell = cells[i];
310 colCount = Math.max(colCount, cell.gs$cell.col); 310 colCount = Math.max(colCount, cell.gs$cell.col);
311 rowCount = Math.max(rowCount, cell.gs$cell.row); 311 rowCount = Math.max(rowCount, cell.gs$cell.row);
312 } 312 }
313   313  
314 // Set up arrays containing the column data 314 // Set up arrays containing the column data
315 for (i = 0; i < colCount; i++) { 315 for (i = 0; i < colCount; i++) {
316 if (i >= startColumn && i <= endColumn) { 316 if (i >= startColumn && i <= endColumn) {
317 // Create new columns with the length of either end-start or rowCount 317 // Create new columns with the length of either end-start or rowCount
318 columns[i - startColumn] = []; 318 columns[i - startColumn] = [];
319   319  
320 // Setting the length to avoid jslint warning 320 // Setting the length to avoid jslint warning
321 columns[i - startColumn].length = Math.min(rowCount, endRow - startRow); 321 columns[i - startColumn].length = Math.min(rowCount, endRow - startRow);
322 } 322 }
323 } 323 }
324   324  
325 // Loop over the cells and assign the value to the right 325 // Loop over the cells and assign the value to the right
326 // place in the column arrays 326 // place in the column arrays
327 for (i = 0; i < cellCount; i++) { 327 for (i = 0; i < cellCount; i++) {
328 cell = cells[i]; 328 cell = cells[i];
329 gr = cell.gs$cell.row - 1; // rows start at 1 329 gr = cell.gs$cell.row - 1; // rows start at 1
330 gc = cell.gs$cell.col - 1; // columns start at 1 330 gc = cell.gs$cell.col - 1; // columns start at 1
331   331  
332 // If both row and col falls inside start and end 332 // If both row and col falls inside start and end
333 // set the transposed cell value in the newly created columns 333 // set the transposed cell value in the newly created columns
334 if (gc >= startColumn && gc <= endColumn && 334 if (gc >= startColumn && gc <= endColumn &&
335 gr >= startRow && gr <= endRow) { 335 gr >= startRow && gr <= endRow) {
336 columns[gc - startColumn][gr - startRow] = cell.content.$t; 336 columns[gc - startColumn][gr - startRow] = cell.content.$t;
337 } 337 }
338 } 338 }
339   339  
340 // Insert null for empty spreadsheet cells (#5298) 340 // Insert null for empty spreadsheet cells (#5298)
341 each(columns, function(column) { 341 each(columns, function(column) {
342 for (i = 0; i < column.length; i++) { 342 for (i = 0; i < column.length; i++) {
343 if (column[i] === undefined) { 343 if (column[i] === undefined) {
344 column[i] = null; 344 column[i] = null;
345 } 345 }
346 } 346 }
347 }); 347 });
348   348  
349 self.dataFound(); 349 self.dataFound();
350 } 350 }
351 }); 351 });
352 } 352 }
353 }, 353 },
354   354  
355 /** 355 /**
356 * Trim a string from whitespace 356 * Trim a string from whitespace
357 */ 357 */
358 trim: function(str, inside) { 358 trim: function(str, inside) {
359 if (typeof str === 'string') { 359 if (typeof str === 'string') {
360 str = str.replace(/^\s+|\s+$/g, ''); 360 str = str.replace(/^\s+|\s+$/g, '');
361   361  
362 // Clear white space insdie the string, like thousands separators 362 // Clear white space insdie the string, like thousands separators
363 if (inside && /^[0-9\s]+$/.test(str)) { 363 if (inside && /^[0-9\s]+$/.test(str)) {
364 str = str.replace(/\s/g, ''); 364 str = str.replace(/\s/g, '');
365 } 365 }
366   366  
367 if (this.decimalRegex) { 367 if (this.decimalRegex) {
368 str = str.replace(this.decimalRegex, '$1.$2'); 368 str = str.replace(this.decimalRegex, '$1.$2');
369 } 369 }
370 } 370 }
371 return str; 371 return str;
372 }, 372 },
373   373  
374 /** 374 /**
375 * Parse numeric cells in to number types and date types in to true dates. 375 * Parse numeric cells in to number types and date types in to true dates.
376 */ 376 */
377 parseTypes: function() { 377 parseTypes: function() {
378 var columns = this.columns, 378 var columns = this.columns,
379 col = columns.length; 379 col = columns.length;
380   380  
381 while (col--) { 381 while (col--) {
382 this.parseColumn(columns[col], col); 382 this.parseColumn(columns[col], col);
383 } 383 }
384   384  
385 }, 385 },
386   386  
387 /** 387 /**
388 * Parse a single column. Set properties like .isDatetime and .isNumeric. 388 * Parse a single column. Set properties like .isDatetime and .isNumeric.
389 */ 389 */
390 parseColumn: function(column, col) { 390 parseColumn: function(column, col) {
391 var rawColumns = this.rawColumns, 391 var rawColumns = this.rawColumns,
392 columns = this.columns, 392 columns = this.columns,
393 row = column.length, 393 row = column.length,
394 val, 394 val,
395 floatVal, 395 floatVal,
396 trimVal, 396 trimVal,
397 trimInsideVal, 397 trimInsideVal,
398 firstRowAsNames = this.firstRowAsNames, 398 firstRowAsNames = this.firstRowAsNames,
399 isXColumn = inArray(col, this.valueCount.xColumns) !== -1, 399 isXColumn = inArray(col, this.valueCount.xColumns) !== -1,
400 dateVal, 400 dateVal,
401 backup = [], 401 backup = [],
402 diff, 402 diff,
403 chartOptions = this.chartOptions, 403 chartOptions = this.chartOptions,
404 descending, 404 descending,
405 columnTypes = this.options.columnTypes || [], 405 columnTypes = this.options.columnTypes || [],
406 columnType = columnTypes[col], 406 columnType = columnTypes[col],
407 forceCategory = isXColumn && ((chartOptions && chartOptions.xAxis && splat(chartOptions.xAxis)[0].type === 'category') || columnType === 'string'); 407 forceCategory = isXColumn && ((chartOptions && chartOptions.xAxis && splat(chartOptions.xAxis)[0].type === 'category') || columnType === 'string');
408   408  
409 if (!rawColumns[col]) { 409 if (!rawColumns[col]) {
410 rawColumns[col] = []; 410 rawColumns[col] = [];
411 } 411 }
412 while (row--) { 412 while (row--) {
413 val = backup[row] || column[row]; 413 val = backup[row] || column[row];
414   414  
415 trimVal = this.trim(val); 415 trimVal = this.trim(val);
416 trimInsideVal = this.trim(val, true); 416 trimInsideVal = this.trim(val, true);
417 floatVal = parseFloat(trimInsideVal); 417 floatVal = parseFloat(trimInsideVal);
418   418  
419 // Set it the first time 419 // Set it the first time
420 if (rawColumns[col][row] === undefined) { 420 if (rawColumns[col][row] === undefined) {
421 rawColumns[col][row] = trimVal; 421 rawColumns[col][row] = trimVal;
422 } 422 }
423   423  
424 // Disable number or date parsing by setting the X axis type to category 424 // Disable number or date parsing by setting the X axis type to category
425 if (forceCategory || (row === 0 && firstRowAsNames)) { 425 if (forceCategory || (row === 0 && firstRowAsNames)) {
426 column[row] = trimVal; 426 column[row] = trimVal;
427   427  
428 } else if (+trimInsideVal === floatVal) { // is numeric 428 } else if (+trimInsideVal === floatVal) { // is numeric
429   429  
430 column[row] = floatVal; 430 column[row] = floatVal;
431   431  
432 // If the number is greater than milliseconds in a year, assume datetime 432 // If the number is greater than milliseconds in a year, assume datetime
433 if (floatVal > 365 * 24 * 3600 * 1000 && columnType !== 'float') { 433 if (floatVal > 365 * 24 * 3600 * 1000 && columnType !== 'float') {
434 column.isDatetime = true; 434 column.isDatetime = true;
435 } else { 435 } else {
436 column.isNumeric = true; 436 column.isNumeric = true;
437 } 437 }
438   438  
439 if (column[row + 1] !== undefined) { 439 if (column[row + 1] !== undefined) {
440 descending = floatVal > column[row + 1]; 440 descending = floatVal > column[row + 1];
441 } 441 }
442   442  
443 // String, continue to determine if it is a date string or really a string 443 // String, continue to determine if it is a date string or really a string
444 } else { 444 } else {
445 dateVal = this.parseDate(val); 445 dateVal = this.parseDate(val);
446 // Only allow parsing of dates if this column is an x-column 446 // Only allow parsing of dates if this column is an x-column
447 if (isXColumn && isNumber(dateVal) && columnType !== 'float') { // is date 447 if (isXColumn && isNumber(dateVal) && columnType !== 'float') { // is date
448 backup[row] = val; 448 backup[row] = val;
449 column[row] = dateVal; 449 column[row] = dateVal;
450 column.isDatetime = true; 450 column.isDatetime = true;
451   451  
452 // Check if the dates are uniformly descending or ascending. If they 452 // Check if the dates are uniformly descending or ascending. If they
453 // are not, chances are that they are a different time format, so check 453 // are not, chances are that they are a different time format, so check
454 // for alternative. 454 // for alternative.
455 if (column[row + 1] !== undefined) { 455 if (column[row + 1] !== undefined) {
456 diff = dateVal > column[row + 1]; 456 diff = dateVal > column[row + 1];
457 if (diff !== descending && descending !== undefined) { 457 if (diff !== descending && descending !== undefined) {
458 if (this.alternativeFormat) { 458 if (this.alternativeFormat) {
459 this.dateFormat = this.alternativeFormat; 459 this.dateFormat = this.alternativeFormat;
460 row = column.length; 460 row = column.length;
461 this.alternativeFormat = this.dateFormats[this.dateFormat].alternative; 461 this.alternativeFormat = this.dateFormats[this.dateFormat].alternative;
462 } else { 462 } else {
463 column.unsorted = true; 463 column.unsorted = true;
464 } 464 }
465 } 465 }
466 descending = diff; 466 descending = diff;
467 } 467 }
468   468  
469 } else { // string 469 } else { // string
470 column[row] = trimVal === '' ? null : trimVal; 470 column[row] = trimVal === '' ? null : trimVal;
471 if (row !== 0 && (column.isDatetime || column.isNumeric)) { 471 if (row !== 0 && (column.isDatetime || column.isNumeric)) {
472 column.mixed = true; 472 column.mixed = true;
473 } 473 }
474 } 474 }
475 } 475 }
476 } 476 }
477   477  
478 // If strings are intermixed with numbers or dates in a parsed column, it is an indication 478 // If strings are intermixed with numbers or dates in a parsed column, it is an indication
479 // that parsing went wrong or the data was not intended to display as numbers or dates and 479 // that parsing went wrong or the data was not intended to display as numbers or dates and
480 // parsing is too aggressive. Fall back to categories. Demonstrated in the 480 // parsing is too aggressive. Fall back to categories. Demonstrated in the
481 // highcharts/demo/column-drilldown sample. 481 // highcharts/demo/column-drilldown sample.
482 if (isXColumn && column.mixed) { 482 if (isXColumn && column.mixed) {
483 columns[col] = rawColumns[col]; 483 columns[col] = rawColumns[col];
484 } 484 }
485   485  
486 // If the 0 column is date or number and descending, reverse all columns. 486 // If the 0 column is date or number and descending, reverse all columns.
487 if (isXColumn && descending && this.options.sort) { 487 if (isXColumn && descending && this.options.sort) {
488 for (col = 0; col < columns.length; col++) { 488 for (col = 0; col < columns.length; col++) {
489 columns[col].reverse(); 489 columns[col].reverse();
490 if (firstRowAsNames) { 490 if (firstRowAsNames) {
491 columns[col].unshift(columns[col].pop()); 491 columns[col].unshift(columns[col].pop());
492 } 492 }
493 } 493 }
494 } 494 }
495 }, 495 },
496   496  
497 /** 497 /**
498 * A collection of available date formats, extendable from the outside to support 498 * A collection of available date formats, extendable from the outside to support
499 * custom date formats. 499 * custom date formats.
500 */ 500 */
501 dateFormats: { 501 dateFormats: {
502 'YYYY-mm-dd': { 502 'YYYY-mm-dd': {
503 regex: /^([0-9]{4})[\-\/\.]([0-9]{2})[\-\/\.]([0-9]{2})$/, 503 regex: /^([0-9]{4})[\-\/\.]([0-9]{2})[\-\/\.]([0-9]{2})$/,
504 parser: function(match) { 504 parser: function(match) {
505 return Date.UTC(+match[1], match[2] - 1, +match[3]); 505 return Date.UTC(+match[1], match[2] - 1, +match[3]);
506 } 506 }
507 }, 507 },
508 'dd/mm/YYYY': { 508 'dd/mm/YYYY': {
509 regex: /^([0-9]{1,2})[\-\/\.]([0-9]{1,2})[\-\/\.]([0-9]{4})$/, 509 regex: /^([0-9]{1,2})[\-\/\.]([0-9]{1,2})[\-\/\.]([0-9]{4})$/,
510 parser: function(match) { 510 parser: function(match) {
511 return Date.UTC(+match[3], match[2] - 1, +match[1]); 511 return Date.UTC(+match[3], match[2] - 1, +match[1]);
512 }, 512 },
513 alternative: 'mm/dd/YYYY' // different format with the same regex 513 alternative: 'mm/dd/YYYY' // different format with the same regex
514 }, 514 },
515 'mm/dd/YYYY': { 515 'mm/dd/YYYY': {
516 regex: /^([0-9]{1,2})[\-\/\.]([0-9]{1,2})[\-\/\.]([0-9]{4})$/, 516 regex: /^([0-9]{1,2})[\-\/\.]([0-9]{1,2})[\-\/\.]([0-9]{4})$/,
517 parser: function(match) { 517 parser: function(match) {
518 return Date.UTC(+match[3], match[1] - 1, +match[2]); 518 return Date.UTC(+match[3], match[1] - 1, +match[2]);
519 } 519 }
520 }, 520 },
521 'dd/mm/YY': { 521 'dd/mm/YY': {
522 regex: /^([0-9]{1,2})[\-\/\.]([0-9]{1,2})[\-\/\.]([0-9]{2})$/, 522 regex: /^([0-9]{1,2})[\-\/\.]([0-9]{1,2})[\-\/\.]([0-9]{2})$/,
523 parser: function(match) { 523 parser: function(match) {
524 return Date.UTC(+match[3] + 2000, match[2] - 1, +match[1]); 524 return Date.UTC(+match[3] + 2000, match[2] - 1, +match[1]);
525 }, 525 },
526 alternative: 'mm/dd/YY' // different format with the same regex 526 alternative: 'mm/dd/YY' // different format with the same regex
527 }, 527 },
528 'mm/dd/YY': { 528 'mm/dd/YY': {
529 regex: /^([0-9]{1,2})[\-\/\.]([0-9]{1,2})[\-\/\.]([0-9]{2})$/, 529 regex: /^([0-9]{1,2})[\-\/\.]([0-9]{1,2})[\-\/\.]([0-9]{2})$/,
530 parser: function(match) { 530 parser: function(match) {
531 return Date.UTC(+match[3] + 2000, match[1] - 1, +match[2]); 531 return Date.UTC(+match[3] + 2000, match[1] - 1, +match[2]);
532 } 532 }
533 } 533 }
534 }, 534 },
535   535  
536 /** 536 /**
537 * Parse a date and return it as a number. Overridable through options.parseDate. 537 * Parse a date and return it as a number. Overridable through options.parseDate.
538 */ 538 */
539 parseDate: function(val) { 539 parseDate: function(val) {
540 var parseDate = this.options.parseDate, 540 var parseDate = this.options.parseDate,
541 ret, 541 ret,
542 key, 542 key,
543 format, 543 format,
544 dateFormat = this.options.dateFormat || this.dateFormat, 544 dateFormat = this.options.dateFormat || this.dateFormat,
545 match; 545 match;
546   546  
547 if (parseDate) { 547 if (parseDate) {
548 ret = parseDate(val); 548 ret = parseDate(val);
549   549  
550 } else if (typeof val === 'string') { 550 } else if (typeof val === 'string') {
551 // Auto-detect the date format the first time 551 // Auto-detect the date format the first time
552 if (!dateFormat) { 552 if (!dateFormat) {
553 for (key in this.dateFormats) { 553 for (key in this.dateFormats) {
554 format = this.dateFormats[key]; 554 format = this.dateFormats[key];
555 match = val.match(format.regex); 555 match = val.match(format.regex);
556 if (match) { 556 if (match) {
557 this.dateFormat = dateFormat = key; 557 this.dateFormat = dateFormat = key;
558 this.alternativeFormat = format.alternative; 558 this.alternativeFormat = format.alternative;
559 ret = format.parser(match); 559 ret = format.parser(match);
560 break; 560 break;
561 } 561 }
562 } 562 }
563 // Next time, use the one previously found 563 // Next time, use the one previously found
564 } else { 564 } else {
565 format = this.dateFormats[dateFormat]; 565 format = this.dateFormats[dateFormat];
566 match = val.match(format.regex); 566 match = val.match(format.regex);
567 if (match) { 567 if (match) {
568 ret = format.parser(match); 568 ret = format.parser(match);
569 } 569 }
570 } 570 }
571 // Fall back to Date.parse 571 // Fall back to Date.parse
572 if (!match) { 572 if (!match) {
573 match = Date.parse(val); 573 match = Date.parse(val);
574 // External tools like Date.js and MooTools extend Date object and 574 // External tools like Date.js and MooTools extend Date object and
575 // returns a date. 575 // returns a date.
576 if (typeof match === 'object' && match !== null && match.getTime) { 576 if (typeof match === 'object' && match !== null && match.getTime) {
577 ret = match.getTime() - match.getTimezoneOffset() * 60000; 577 ret = match.getTime() - match.getTimezoneOffset() * 60000;
578   578  
579 // Timestamp 579 // Timestamp
580 } else if (isNumber(match)) { 580 } else if (isNumber(match)) {
581 ret = match - (new Date(match)).getTimezoneOffset() * 60000; 581 ret = match - (new Date(match)).getTimezoneOffset() * 60000;
582 } 582 }
583 } 583 }
584 } 584 }
585 return ret; 585 return ret;
586 }, 586 },
587   587  
588 /** 588 /**
589 * Reorganize rows into columns 589 * Reorganize rows into columns
590 */ 590 */
591 rowsToColumns: function(rows) { 591 rowsToColumns: function(rows) {
592 var row, 592 var row,
593 rowsLength, 593 rowsLength,
594 col, 594 col,
595 colsLength, 595 colsLength,
596 columns; 596 columns;
597   597  
598 if (rows) { 598 if (rows) {
599 columns = []; 599 columns = [];
600 rowsLength = rows.length; 600 rowsLength = rows.length;
601 for (row = 0; row < rowsLength; row++) { 601 for (row = 0; row < rowsLength; row++) {
602 colsLength = rows[row].length; 602 colsLength = rows[row].length;
603 for (col = 0; col < colsLength; col++) { 603 for (col = 0; col < colsLength; col++) {
604 if (!columns[col]) { 604 if (!columns[col]) {
605 columns[col] = []; 605 columns[col] = [];
606 } 606 }
607 columns[col][row] = rows[row][col]; 607 columns[col][row] = rows[row][col];
608 } 608 }
609 } 609 }
610 } 610 }
611 return columns; 611 return columns;
612 }, 612 },
613   613  
614 /** 614 /**
615 * A hook for working directly on the parsed columns 615 * A hook for working directly on the parsed columns
616 */ 616 */
617 parsed: function() { 617 parsed: function() {
618 if (this.options.parsed) { 618 if (this.options.parsed) {
619 return this.options.parsed.call(this, this.columns); 619 return this.options.parsed.call(this, this.columns);
620 } 620 }
621 }, 621 },
622   622  
623 getFreeIndexes: function(numberOfColumns, seriesBuilders) { 623 getFreeIndexes: function(numberOfColumns, seriesBuilders) {
624 var s, 624 var s,
625 i, 625 i,
626 freeIndexes = [], 626 freeIndexes = [],
627 freeIndexValues = [], 627 freeIndexValues = [],
628 referencedIndexes; 628 referencedIndexes;
629   629  
630 // Add all columns as free 630 // Add all columns as free
631 for (i = 0; i < numberOfColumns; i = i + 1) { 631 for (i = 0; i < numberOfColumns; i = i + 1) {
632 freeIndexes.push(true); 632 freeIndexes.push(true);
633 } 633 }
634   634  
635 // Loop all defined builders and remove their referenced columns 635 // Loop all defined builders and remove their referenced columns
636 for (s = 0; s < seriesBuilders.length; s = s + 1) { 636 for (s = 0; s < seriesBuilders.length; s = s + 1) {
637 referencedIndexes = seriesBuilders[s].getReferencedColumnIndexes(); 637 referencedIndexes = seriesBuilders[s].getReferencedColumnIndexes();
638   638  
639 for (i = 0; i < referencedIndexes.length; i = i + 1) { 639 for (i = 0; i < referencedIndexes.length; i = i + 1) {
640 freeIndexes[referencedIndexes[i]] = false; 640 freeIndexes[referencedIndexes[i]] = false;
641 } 641 }
642 } 642 }
643   643  
644 // Collect the values for the free indexes 644 // Collect the values for the free indexes
645 for (i = 0; i < freeIndexes.length; i = i + 1) { 645 for (i = 0; i < freeIndexes.length; i = i + 1) {
646 if (freeIndexes[i]) { 646 if (freeIndexes[i]) {
647 freeIndexValues.push(i); 647 freeIndexValues.push(i);
648 } 648 }
649 } 649 }
650   650  
651 return freeIndexValues; 651 return freeIndexValues;
652 }, 652 },
653   653  
654 /** 654 /**
655 * If a complete callback function is provided in the options, interpret the 655 * If a complete callback function is provided in the options, interpret the
656 * columns into a Highcharts options object. 656 * columns into a Highcharts options object.
657 */ 657 */
658 complete: function() { 658 complete: function() {
659   659  
660 var columns = this.columns, 660 var columns = this.columns,
661 xColumns = [], 661 xColumns = [],
662 type, 662 type,
663 options = this.options, 663 options = this.options,
664 series, 664 series,
665 data, 665 data,
666 i, 666 i,
667 j, 667 j,
668 r, 668 r,
669 seriesIndex, 669 seriesIndex,
670 chartOptions, 670 chartOptions,
671 allSeriesBuilders = [], 671 allSeriesBuilders = [],
672 builder, 672 builder,
673 freeIndexes, 673 freeIndexes,
674 typeCol, 674 typeCol,
675 index; 675 index;
676   676  
677 xColumns.length = columns.length; 677 xColumns.length = columns.length;
678 if (options.complete || options.afterComplete) { 678 if (options.complete || options.afterComplete) {
679   679  
680 // Get the names and shift the top row 680 // Get the names and shift the top row
681 for (i = 0; i < columns.length; i++) { 681 for (i = 0; i < columns.length; i++) {
682 if (this.firstRowAsNames) { 682 if (this.firstRowAsNames) {
683 columns[i].name = columns[i].shift(); 683 columns[i].name = columns[i].shift();
684 } 684 }
685 } 685 }
686   686  
687 // Use the next columns for series 687 // Use the next columns for series
688 series = []; 688 series = [];
689 freeIndexes = this.getFreeIndexes(columns.length, this.valueCount.seriesBuilders); 689 freeIndexes = this.getFreeIndexes(columns.length, this.valueCount.seriesBuilders);
690   690  
691 // Populate defined series 691 // Populate defined series
692 for (seriesIndex = 0; seriesIndex < this.valueCount.seriesBuilders.length; seriesIndex++) { 692 for (seriesIndex = 0; seriesIndex < this.valueCount.seriesBuilders.length; seriesIndex++) {
693 builder = this.valueCount.seriesBuilders[seriesIndex]; 693 builder = this.valueCount.seriesBuilders[seriesIndex];
694   694  
695 // If the builder can be populated with remaining columns, then add it to allBuilders 695 // If the builder can be populated with remaining columns, then add it to allBuilders
696 if (builder.populateColumns(freeIndexes)) { 696 if (builder.populateColumns(freeIndexes)) {
697 allSeriesBuilders.push(builder); 697 allSeriesBuilders.push(builder);
698 } 698 }
699 } 699 }
700   700  
701 // Populate dynamic series 701 // Populate dynamic series
702 while (freeIndexes.length > 0) { 702 while (freeIndexes.length > 0) {
703 builder = new SeriesBuilder(); 703 builder = new SeriesBuilder();
704 builder.addColumnReader(0, 'x'); 704 builder.addColumnReader(0, 'x');
705   705  
706 // Mark index as used (not free) 706 // Mark index as used (not free)
707 index = inArray(0, freeIndexes); 707 index = inArray(0, freeIndexes);
708 if (index !== -1) { 708 if (index !== -1) {
709 freeIndexes.splice(index, 1); 709 freeIndexes.splice(index, 1);
710 } 710 }
711   711  
712 for (i = 0; i < this.valueCount.global; i++) { 712 for (i = 0; i < this.valueCount.global; i++) {
713 // Create and add a column reader for the next free column index 713 // Create and add a column reader for the next free column index
714 builder.addColumnReader(undefined, this.valueCount.globalPointArrayMap[i]); 714 builder.addColumnReader(undefined, this.valueCount.globalPointArrayMap[i]);
715 } 715 }
716   716  
717 // If the builder can be populated with remaining columns, then add it to allBuilders 717 // If the builder can be populated with remaining columns, then add it to allBuilders
718 if (builder.populateColumns(freeIndexes)) { 718 if (builder.populateColumns(freeIndexes)) {
719 allSeriesBuilders.push(builder); 719 allSeriesBuilders.push(builder);
720 } 720 }
721 } 721 }
722   722  
723 // Get the data-type from the first series x column 723 // Get the data-type from the first series x column
724 if (allSeriesBuilders.length > 0 && allSeriesBuilders[0].readers.length > 0) { 724 if (allSeriesBuilders.length > 0 && allSeriesBuilders[0].readers.length > 0) {
725 typeCol = columns[allSeriesBuilders[0].readers[0].columnIndex]; 725 typeCol = columns[allSeriesBuilders[0].readers[0].columnIndex];
726 if (typeCol !== undefined) { 726 if (typeCol !== undefined) {
727 if (typeCol.isDatetime) { 727 if (typeCol.isDatetime) {
728 type = 'datetime'; 728 type = 'datetime';
729 } else if (!typeCol.isNumeric) { 729 } else if (!typeCol.isNumeric) {
730 type = 'category'; 730 type = 'category';
731 } 731 }
732 } 732 }
733 } 733 }
734 // Axis type is category, then the "x" column should be called "name" 734 // Axis type is category, then the "x" column should be called "name"
735 if (type === 'category') { 735 if (type === 'category') {
736 for (seriesIndex = 0; seriesIndex < allSeriesBuilders.length; seriesIndex++) { 736 for (seriesIndex = 0; seriesIndex < allSeriesBuilders.length; seriesIndex++) {
737 builder = allSeriesBuilders[seriesIndex]; 737 builder = allSeriesBuilders[seriesIndex];
738 for (r = 0; r < builder.readers.length; r++) { 738 for (r = 0; r < builder.readers.length; r++) {
739 if (builder.readers[r].configName === 'x') { 739 if (builder.readers[r].configName === 'x') {
740 builder.readers[r].configName = 'name'; 740 builder.readers[r].configName = 'name';
741 } 741 }
742 } 742 }
743 } 743 }
744 } 744 }
745   745  
746 // Read data for all builders 746 // Read data for all builders
747 for (seriesIndex = 0; seriesIndex < allSeriesBuilders.length; seriesIndex++) { 747 for (seriesIndex = 0; seriesIndex < allSeriesBuilders.length; seriesIndex++) {
748 builder = allSeriesBuilders[seriesIndex]; 748 builder = allSeriesBuilders[seriesIndex];
749   749  
750 // Iterate down the cells of each column and add data to the series 750 // Iterate down the cells of each column and add data to the series
751 data = []; 751 data = [];
752 for (j = 0; j < columns[0].length; j++) { 752 for (j = 0; j < columns[0].length; j++) {
753 data[j] = builder.read(columns, j); 753 data[j] = builder.read(columns, j);
754 } 754 }
755   755  
756 // Add the series 756 // Add the series
757 series[seriesIndex] = { 757 series[seriesIndex] = {
758 data: data 758 data: data
759 }; 759 };
760 if (builder.name) { 760 if (builder.name) {
761 series[seriesIndex].name = builder.name; 761 series[seriesIndex].name = builder.name;
762 } 762 }
763 if (type === 'category') { 763 if (type === 'category') {
764 series[seriesIndex].turboThreshold = 0; 764 series[seriesIndex].turboThreshold = 0;
765 } 765 }
766 } 766 }
767   767  
768   768  
769   769  
770 // Do the callback 770 // Do the callback
771 chartOptions = { 771 chartOptions = {
772 series: series 772 series: series
773 }; 773 };
774 if (type) { 774 if (type) {
775 chartOptions.xAxis = { 775 chartOptions.xAxis = {
776 type: type 776 type: type
777 }; 777 };
778 if (type === 'category') { 778 if (type === 'category') {
779 chartOptions.xAxis.uniqueNames = false; 779 chartOptions.xAxis.uniqueNames = false;
780 } 780 }
781 } 781 }
782   782  
783 if (options.complete) { 783 if (options.complete) {
784 options.complete(chartOptions); 784 options.complete(chartOptions);
785 } 785 }
786   786  
787 // The afterComplete hook is used internally to avoid conflict with the externally 787 // The afterComplete hook is used internally to avoid conflict with the externally
788 // available complete option. 788 // available complete option.
789 if (options.afterComplete) { 789 if (options.afterComplete) {
790 options.afterComplete(chartOptions); 790 options.afterComplete(chartOptions);
791 } 791 }
792 } 792 }
793 } 793 }
794 }); 794 });
795   795  
796 // Register the Data prototype and data function on Highcharts 796 // Register the Data prototype and data function on Highcharts
797 Highcharts.Data = Data; 797 Highcharts.Data = Data;
798 Highcharts.data = function(options, chartOptions) { 798 Highcharts.data = function(options, chartOptions) {
799 return new Data(options, chartOptions); 799 return new Data(options, chartOptions);
800 }; 800 };
801   801  
802 // Extend Chart.init so that the Chart constructor accepts a new configuration 802 // Extend Chart.init so that the Chart constructor accepts a new configuration
803 // option group, data. 803 // option group, data.
804 Highcharts.wrap(Highcharts.Chart.prototype, 'init', function(proceed, userOptions, callback) { 804 Highcharts.wrap(Highcharts.Chart.prototype, 'init', function(proceed, userOptions, callback) {
805 var chart = this; 805 var chart = this;
806   806  
807 if (userOptions && userOptions.data) { 807 if (userOptions && userOptions.data) {
808 Highcharts.data(Highcharts.extend(userOptions.data, { 808 Highcharts.data(Highcharts.extend(userOptions.data, {
809   809  
810 afterComplete: function(dataOptions) { 810 afterComplete: function(dataOptions) {
811 var i, series; 811 var i, series;
812   812  
813 // Merge series configs 813 // Merge series configs
814 if (userOptions.hasOwnProperty('series')) { 814 if (userOptions.hasOwnProperty('series')) {
815 if (typeof userOptions.series === 'object') { 815 if (typeof userOptions.series === 'object') {
816 i = Math.max(userOptions.series.length, dataOptions.series.length); 816 i = Math.max(userOptions.series.length, dataOptions.series.length);
817 while (i--) { 817 while (i--) {
818 series = userOptions.series[i] || {}; 818 series = userOptions.series[i] || {};
819 userOptions.series[i] = Highcharts.merge(series, dataOptions.series[i]); 819 userOptions.series[i] = Highcharts.merge(series, dataOptions.series[i]);
820 } 820 }
821 } else { // Allow merging in dataOptions.series (#2856) 821 } else { // Allow merging in dataOptions.series (#2856)
822 delete userOptions.series; 822 delete userOptions.series;
823 } 823 }
824 } 824 }
825   825  
826 // Do the merge 826 // Do the merge
827 userOptions = Highcharts.merge(dataOptions, userOptions); 827 userOptions = Highcharts.merge(dataOptions, userOptions);
828   828  
829 proceed.call(chart, userOptions, callback); 829 proceed.call(chart, userOptions, callback);
830 } 830 }
831 }), userOptions); 831 }), userOptions);
832 } else { 832 } else {
833 proceed.call(chart, userOptions, callback); 833 proceed.call(chart, userOptions, callback);
834 } 834 }
835 }); 835 });
836   836  
837 /** 837 /**
838 * Creates a new SeriesBuilder. A SeriesBuilder consists of a number 838 * Creates a new SeriesBuilder. A SeriesBuilder consists of a number
839 * of ColumnReaders that reads columns and give them a name. 839 * of ColumnReaders that reads columns and give them a name.
840 * Ex: A series builder can be constructed to read column 3 as 'x' and 840 * Ex: A series builder can be constructed to read column 3 as 'x' and
841 * column 7 and 8 as 'y1' and 'y2'. 841 * column 7 and 8 as 'y1' and 'y2'.
842 * The output would then be points/rows of the form {x: 11, y1: 22, y2: 33} 842 * The output would then be points/rows of the form {x: 11, y1: 22, y2: 33}
843 * 843 *
844 * The name of the builder is taken from the second column. In the above 844 * The name of the builder is taken from the second column. In the above
845 * example it would be the column with index 7. 845 * example it would be the column with index 7.
846 * @constructor 846 * @constructor
847 */ 847 */
848 SeriesBuilder = function() { 848 SeriesBuilder = function() {
849 this.readers = []; 849 this.readers = [];
850 this.pointIsArray = true; 850 this.pointIsArray = true;
851 }; 851 };
852   852  
853 /** 853 /**
854 * Populates readers with column indexes. A reader can be added without 854 * Populates readers with column indexes. A reader can be added without
855 * a specific index and for those readers the index is taken sequentially 855 * a specific index and for those readers the index is taken sequentially
856 * from the free columns (this is handled by the ColumnCursor instance). 856 * from the free columns (this is handled by the ColumnCursor instance).
857 * @returns {boolean} 857 * @returns {boolean}
858 */ 858 */
859 SeriesBuilder.prototype.populateColumns = function(freeIndexes) { 859 SeriesBuilder.prototype.populateColumns = function(freeIndexes) {
860 var builder = this, 860 var builder = this,
861 enoughColumns = true; 861 enoughColumns = true;
862   862  
863 // Loop each reader and give it an index if its missing. 863 // Loop each reader and give it an index if its missing.
864 // The freeIndexes.shift() will return undefined if there 864 // The freeIndexes.shift() will return undefined if there
865 // are no more columns. 865 // are no more columns.
866 each(builder.readers, function(reader) { 866 each(builder.readers, function(reader) {
867 if (reader.columnIndex === undefined) { 867 if (reader.columnIndex === undefined) {
868 reader.columnIndex = freeIndexes.shift(); 868 reader.columnIndex = freeIndexes.shift();
869 } 869 }
870 }); 870 });
871   871  
872 // Now, all readers should have columns mapped. If not 872 // Now, all readers should have columns mapped. If not
873 // then return false to signal that this series should 873 // then return false to signal that this series should
874 // not be added. 874 // not be added.
875 each(builder.readers, function(reader) { 875 each(builder.readers, function(reader) {
876 if (reader.columnIndex === undefined) { 876 if (reader.columnIndex === undefined) {
877 enoughColumns = false; 877 enoughColumns = false;
878 } 878 }
879 }); 879 });
880   880  
881 return enoughColumns; 881 return enoughColumns;
882 }; 882 };
883   883  
884 /** 884 /**
885 * Reads a row from the dataset and returns a point or array depending 885 * Reads a row from the dataset and returns a point or array depending
886 * on the names of the readers. 886 * on the names of the readers.
887 * @param columns 887 * @param columns
888 * @param rowIndex 888 * @param rowIndex
889 * @returns {Array | Object} 889 * @returns {Array | Object}
890 */ 890 */
891 SeriesBuilder.prototype.read = function(columns, rowIndex) { 891 SeriesBuilder.prototype.read = function(columns, rowIndex) {
892 var builder = this, 892 var builder = this,
893 pointIsArray = builder.pointIsArray, 893 pointIsArray = builder.pointIsArray,
894 point = pointIsArray ? [] : {}, 894 point = pointIsArray ? [] : {},
895 columnIndexes; 895 columnIndexes;
896   896  
897 // Loop each reader and ask it to read its value. 897 // Loop each reader and ask it to read its value.
898 // Then, build an array or point based on the readers names. 898 // Then, build an array or point based on the readers names.
899 each(builder.readers, function(reader) { 899 each(builder.readers, function(reader) {
900 var value = columns[reader.columnIndex][rowIndex]; 900 var value = columns[reader.columnIndex][rowIndex];
901 if (pointIsArray) { 901 if (pointIsArray) {
902 point.push(value); 902 point.push(value);
903 } else { 903 } else {
904 point[reader.configName] = value; 904 point[reader.configName] = value;
905 } 905 }
906 }); 906 });
907   907  
908 // The name comes from the first column (excluding the x column) 908 // The name comes from the first column (excluding the x column)
909 if (this.name === undefined && builder.readers.length >= 2) { 909 if (this.name === undefined && builder.readers.length >= 2) {
910 columnIndexes = builder.getReferencedColumnIndexes(); 910 columnIndexes = builder.getReferencedColumnIndexes();
911 if (columnIndexes.length >= 2) { 911 if (columnIndexes.length >= 2) {
912 // remove the first one (x col) 912 // remove the first one (x col)
913 columnIndexes.shift(); 913 columnIndexes.shift();
914   914  
915 // Sort the remaining 915 // Sort the remaining
916 columnIndexes.sort(); 916 columnIndexes.sort();
917   917  
918 // Now use the lowest index as name column 918 // Now use the lowest index as name column
919 this.name = columns[columnIndexes.shift()].name; 919 this.name = columns[columnIndexes.shift()].name;
920 } 920 }
921 } 921 }
922   922  
923 return point; 923 return point;
924 }; 924 };
925   925  
926 /** 926 /**
927 * Creates and adds ColumnReader from the given columnIndex and configName. 927 * Creates and adds ColumnReader from the given columnIndex and configName.
928 * ColumnIndex can be undefined and in that case the reader will be given 928 * ColumnIndex can be undefined and in that case the reader will be given
929 * an index when columns are populated. 929 * an index when columns are populated.
930 * @param columnIndex {Number | undefined} 930 * @param columnIndex {Number | undefined}
931 * @param configName 931 * @param configName
932 */ 932 */
933 SeriesBuilder.prototype.addColumnReader = function(columnIndex, configName) { 933 SeriesBuilder.prototype.addColumnReader = function(columnIndex, configName) {
934 this.readers.push({ 934 this.readers.push({
935 columnIndex: columnIndex, 935 columnIndex: columnIndex,
936 configName: configName 936 configName: configName
937 }); 937 });
938   938  
939 if (!(configName === 'x' || configName === 'y' || configName === undefined)) { 939 if (!(configName === 'x' || configName === 'y' || configName === undefined)) {
940 this.pointIsArray = false; 940 this.pointIsArray = false;
941 } 941 }
942 }; 942 };
943   943  
944 /** 944 /**
945 * Returns an array of column indexes that the builder will use when 945 * Returns an array of column indexes that the builder will use when
946 * reading data. 946 * reading data.
947 * @returns {Array} 947 * @returns {Array}
948 */ 948 */
949 SeriesBuilder.prototype.getReferencedColumnIndexes = function() { 949 SeriesBuilder.prototype.getReferencedColumnIndexes = function() {
950 var i, 950 var i,
951 referencedColumnIndexes = [], 951 referencedColumnIndexes = [],
952 columnReader; 952 columnReader;
953   953  
954 for (i = 0; i < this.readers.length; i = i + 1) { 954 for (i = 0; i < this.readers.length; i = i + 1) {
955 columnReader = this.readers[i]; 955 columnReader = this.readers[i];
956 if (columnReader.columnIndex !== undefined) { 956 if (columnReader.columnIndex !== undefined) {
957 referencedColumnIndexes.push(columnReader.columnIndex); 957 referencedColumnIndexes.push(columnReader.columnIndex);
958 } 958 }
959 } 959 }
960   960  
961 return referencedColumnIndexes; 961 return referencedColumnIndexes;
962 }; 962 };
963   963  
964 /** 964 /**
965 * Returns true if the builder has a reader for the given configName. 965 * Returns true if the builder has a reader for the given configName.
966 * @param configName 966 * @param configName
967 * @returns {boolean} 967 * @returns {boolean}
968 */ 968 */
969 SeriesBuilder.prototype.hasReader = function(configName) { 969 SeriesBuilder.prototype.hasReader = function(configName) {
970 var i, columnReader; 970 var i, columnReader;
971 for (i = 0; i < this.readers.length; i = i + 1) { 971 for (i = 0; i < this.readers.length; i = i + 1) {
972 columnReader = this.readers[i]; 972 columnReader = this.readers[i];
973 if (columnReader.configName === configName) { 973 if (columnReader.configName === configName) {
974 return true; 974 return true;
975 } 975 }
976 } 976 }
977 // Else return undefined 977 // Else return undefined
978 }; 978 };
979   979  
980 }(Highcharts)); 980 }(Highcharts));
981 })); 981 }));
982   982