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