corrade-nucleus-nucleons – Blame information for rev 20

Subversion Repositories:
Rev:
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 }));