highcharts-rails 4.1.9 → 4.1.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -42,35 +42,42 @@
42
42
  * number of points drawn gets higher, and you may want to set the threshold lower in order to
43
43
  * use optimizations.
44
44
  */
45
- /*global document, Highcharts, HighchartsAdapter, setTimeout */
46
- (function (H, HA) {
45
+
46
+ /* eslint indent: [2, 4] */
47
+ (function (factory) {
48
+ if (typeof module === 'object' && module.exports) {
49
+ module.exports = factory;
50
+ } else {
51
+ factory(Highcharts);
52
+ }
53
+ }(function (H) {
47
54
 
48
55
  'use strict';
49
56
 
50
- var noop = function () { return undefined; },
57
+ var noop = function () {},
51
58
  Color = H.Color,
52
59
  Series = H.Series,
53
60
  seriesTypes = H.seriesTypes,
54
61
  each = H.each,
55
62
  extend = H.extend,
56
- addEvent = HA.addEvent,
57
- fireEvent = HA.fireEvent,
63
+ addEvent = H.addEvent,
64
+ fireEvent = H.fireEvent,
58
65
  merge = H.merge,
59
66
  pick = H.pick,
60
67
  wrap = H.wrap,
61
68
  plotOptions = H.getOptions().plotOptions,
62
69
  CHUNK_SIZE = 50000;
63
70
 
64
- function eachAsync(arr, fn, callback, chunkSize, i) {
71
+ function eachAsync(arr, fn, finalFunc, chunkSize, i) {
65
72
  i = i || 0;
66
73
  chunkSize = chunkSize || CHUNK_SIZE;
67
74
  each(arr.slice(i, i + chunkSize), fn);
68
75
  if (i + chunkSize < arr.length) {
69
76
  setTimeout(function () {
70
- eachAsync(arr, fn, callback, chunkSize, i + chunkSize);
77
+ eachAsync(arr, fn, finalFunc, chunkSize, i + chunkSize);
71
78
  });
72
- } else if (callback) {
73
- callback();
79
+ } else if (finalFunc) {
80
+ finalFunc();
74
81
  }
75
82
  }
76
83
 
@@ -161,14 +168,16 @@
161
168
  point,
162
169
  i;
163
170
 
164
- for (i = 0; i < points.length; i = i + 1) {
165
- point = points[i];
166
- if (point && point.graphic) {
167
- point.graphic = point.graphic.destroy();
171
+ if (points) {
172
+ for (i = 0; i < points.length; i = i + 1) {
173
+ point = points[i];
174
+ if (point && point.graphic) {
175
+ point.graphic = point.graphic.destroy();
176
+ }
168
177
  }
169
178
  }
170
179
 
171
- each(['graph', 'area'], function (prop) {
180
+ each(['graph', 'area', 'tracker'], function (prop) {
172
181
  if (series[prop]) {
173
182
  series[prop] = series[prop].destroy();
174
183
  }
@@ -180,15 +189,25 @@
180
189
  * to an SVG image element.
181
190
  */
182
191
  getContext: function () {
183
- var width = this.chart.plotWidth,
184
- height = this.chart.plotHeight;
192
+ var chart = this.chart,
193
+ width = chart.plotWidth,
194
+ height = chart.plotHeight,
195
+ ctx = this.ctx,
196
+ swapXY = function (proceed, x, y, a, b, c, d) {
197
+ proceed.call(this, y, x, a, b, c, d);
198
+ };
185
199
 
186
200
  if (!this.canvas) {
187
201
  this.canvas = document.createElement('canvas');
188
- this.image = this.chart.renderer.image('', 0, 0, width, height).add(this.group);
189
- this.ctx = this.canvas.getContext('2d');
202
+ this.image = chart.renderer.image('', 0, 0, width, height).add(this.group);
203
+ this.ctx = ctx = this.canvas.getContext('2d');
204
+ if (chart.inverted) {
205
+ each(['moveTo', 'lineTo', 'rect', 'arc'], function (fn) {
206
+ wrap(ctx, fn, swapXY);
207
+ });
208
+ }
190
209
  } else {
191
- this.ctx.clearRect(0, 0, width, height);
210
+ ctx.clearRect(0, 0, width, height);
192
211
  }
193
212
 
194
213
  this.canvas.setAttribute('width', width);
@@ -198,7 +217,7 @@
198
217
  height: height
199
218
  });
200
219
 
201
- return this.ctx;
220
+ return ctx;
202
221
  },
203
222
 
204
223
  /**
@@ -308,18 +327,24 @@
308
327
  // The k-d tree requires series points. Reduce the amount of points, since the time to build the
309
328
  // tree increases exponentially.
310
329
  if (enableMouseTracking && !pointTaken[clientX + ',' + plotY]) {
330
+ pointTaken[clientX + ',' + plotY] = true;
331
+
332
+ if (chart.inverted) {
333
+ clientX = xAxis.len - clientX;
334
+ plotY = yAxis.len - plotY;
335
+ }
336
+
311
337
  points.push({
312
338
  clientX: clientX,
313
339
  plotX: clientX,
314
340
  plotY: plotY,
315
341
  i: cropStart + i
316
342
  });
317
- pointTaken[clientX + ',' + plotY] = true;
318
343
  }
319
344
  };
320
345
 
321
346
  // If we are zooming out from SVG mode, destroy the graphics
322
- if (this.points) {
347
+ if (this.points || this.graph) {
323
348
  this.destroyGraphics();
324
349
  }
325
350
 
@@ -488,7 +513,7 @@
488
513
  }
489
514
 
490
515
  // Pass tests in Pointer.
491
- // TODO: Replace this with a single property, and replace when zooming in
516
+ // Replace this with a single property, and replace when zooming in
492
517
  // below boostThreshold.
493
518
  series.directTouch = false;
494
519
  series.options.stickyTracking = true;
@@ -535,20 +560,32 @@
535
560
  sampling: true
536
561
  });
537
562
 
563
+ /**
564
+ * Return a full Point object based on the index. The boost module uses stripped point objects
565
+ * for performance reasons.
566
+ * @param {Number} boostPoint A stripped-down point object
567
+ * @returns {Object} A Point object as per http://api.highcharts.com/highcharts#Point
568
+ */
569
+ Series.prototype.getPoint = function (boostPoint) {
570
+ var point = boostPoint;
571
+
572
+ if (boostPoint && !(boostPoint instanceof this.pointClass)) {
573
+ point = (new this.pointClass()).init(this, this.options.data[boostPoint.i]);
574
+ point.dist = boostPoint.dist;
575
+ point.category = point.x;
576
+ point.plotX = boostPoint.plotX;
577
+ point.plotY = boostPoint.plotY;
578
+ }
579
+
580
+ return point;
581
+ };
582
+
538
583
  /**
539
584
  * Return a point instance from the k-d-tree
540
585
  */
541
586
  wrap(Series.prototype, 'searchPoint', function (proceed) {
542
- var point = proceed.apply(this, [].slice.call(arguments, 1)),
543
- ret = point;
544
-
545
- if (point && !(point instanceof this.pointClass)) {
546
- ret = (new this.pointClass()).init(this, this.options.data[point.i]);
547
- ret.dist = point.dist;
548
- ret.category = ret.x;
549
- ret.plotX = point.plotX;
550
- ret.plotY = point.plotY;
551
- }
552
- return ret;
587
+ return this.getPoint(
588
+ proceed.apply(this, [].slice.call(arguments, 1))
589
+ );
553
590
  });
554
- }(Highcharts, HighchartsAdapter));
591
+ }));
@@ -1,20 +1,25 @@
1
1
  /**
2
- * Highcharts JS v4.1.9 (2015-10-07)
2
+ * Highcharts JS v4.1.10 (2015-12-07)
3
3
  * Highcharts Broken Axis module
4
4
  *
5
- * Author: Stephane Vanraes, Torstein Honsi
6
5
  * License: www.highcharts.com/license
7
6
  */
8
7
 
9
- /*global HighchartsAdapter*/
10
- (function (H) {
8
+ (function (factory) {
9
+ if (typeof module === 'object' && module.exports) {
10
+ module.exports = factory;
11
+ } else {
12
+ factory(Highcharts);
13
+ }
14
+ }(function (H) {
11
15
 
12
- "use strict";
16
+ 'use strict';
13
17
 
14
18
  var pick = H.pick,
15
19
  wrap = H.wrap,
20
+ each = H.each,
16
21
  extend = H.extend,
17
- fireEvent = HighchartsAdapter.fireEvent,
22
+ fireEvent = H.fireEvent,
18
23
  Axis = H.Axis,
19
24
  Series = H.Series;
20
25
 
@@ -207,11 +212,13 @@
207
212
  }
208
213
 
209
214
  breakArrayT.sort(function (a, b) {
215
+ var ret;
210
216
  if (a.value === b.value) {
211
- return (a.move === 'in' ? 0 : 1) - (b.move === 'in' ? 0 : 1);
217
+ ret = (a.move === 'in' ? 0 : 1) - (b.move === 'in' ? 0 : 1);
212
218
  } else {
213
- return a.value - b.value;
219
+ ret = a.value - b.value;
214
220
  }
221
+ return ret;
215
222
  });
216
223
 
217
224
  // Simplify the breaks
@@ -277,38 +284,44 @@
277
284
 
278
285
  });
279
286
 
280
- wrap(H.seriesTypes.column.prototype, 'drawPoints', function (proceed) {
287
+ function drawPointsWrapped(proceed) {
281
288
  proceed.apply(this);
289
+ this.drawBreaks();
290
+ }
282
291
 
292
+ H.Series.prototype.drawBreaks = function () {
283
293
  var series = this,
284
294
  points = series.points,
285
- yAxis = series.yAxis,
286
- breaks = yAxis.breakArray || [],
287
- threshold = pick(this.options.threshold, yAxis.min),
295
+ axis,
296
+ breaks,
297
+ threshold,
298
+ axisName = 'Axis',
288
299
  eventName,
289
- point,
290
- brk,
291
- i,
292
- j,
293
300
  y;
294
301
 
295
- for (i = 0; i < points.length; i++) {
296
- point = points[i];
297
- y = point.stackY || point.y;
298
- for (j = 0; j < breaks.length; j++) {
299
- brk = breaks[j];
300
- eventName = false;
302
+ each(['y', 'x'], function (key) {
303
+ axis = series[key + axisName];
304
+ breaks = axis.breakArray || [];
305
+ threshold = axis.isXAxis ? axis.min : pick(series.options.threshold, axis.min);
306
+ each(points, function (point) {
307
+ y = pick(point['stack' + key.toUpperCase()], point[key]);
308
+ each(breaks, function (brk) {
309
+ eventName = false;
301
310
 
302
- if ((threshold < brk.from && y > brk.to) || (threshold > brk.from && y < brk.from)) {
311
+ if ((threshold < brk.from && y > brk.to) || (threshold > brk.from && y < brk.from)) {
303
312
  eventName = 'pointBreak';
304
- } else if ((threshold < brk.from && y > brk.from && y < brk.to) || (threshold > brk.from && y > brk.to && y < brk.from)) { // point falls inside the break
313
+ } else if ((threshold < brk.from && y > brk.from && y < brk.to) || (threshold > brk.from && y > brk.to && y < brk.from)) { // point falls inside the break
305
314
  eventName = 'pointInBreak'; // docs
306
- }
307
- if (eventName) {
308
- fireEvent(yAxis, eventName, {point: point, brk: brk});
309
- }
310
- }
311
- }
315
+ }
316
+ if (eventName) {
317
+ fireEvent(axis, eventName, { point: point, brk: brk });
318
+ }
319
+ });
320
+ });
321
+ });
322
+ };
312
323
 
313
- });
314
- }(Highcharts));
324
+ wrap(H.seriesTypes.column.prototype, 'drawPoints', drawPointsWrapped);
325
+ wrap(H.Series.prototype, 'drawPoints', drawPointsWrapped);
326
+
327
+ }));
@@ -2908,7 +2908,7 @@ if (CanvasRenderingContext2D) {
2908
2908
  });
2909
2909
  }
2910
2910
  }/**
2911
- * @license Highcharts JS v4.1.9 (2015-10-07)
2911
+ * @license Highcharts JS v4.1.10 (2015-12-07)
2912
2912
  * CanVGRenderer Extension module
2913
2913
  *
2914
2914
  * (c) 2011-2012 Torstein Honsi, Erik Olsson
@@ -2916,10 +2916,7 @@ if (CanvasRenderingContext2D) {
2916
2916
  * License: www.highcharts.com/license
2917
2917
  */
2918
2918
 
2919
- // JSLint options:
2920
- /*global Highcharts */
2921
-
2922
- (function (Highcharts) { // encapsulate
2919
+ (function (Highcharts) {
2923
2920
  var UNDEFINED,
2924
2921
  DIV = 'div',
2925
2922
  ABSOLUTE = 'absolute',
@@ -2955,7 +2952,7 @@ if (CanvasRenderingContext2D) {
2955
2952
  canvas,
2956
2953
  initialHiddenStyle = { visibility: HIDDEN, position: ABSOLUTE };
2957
2954
 
2958
- this.init.apply(this, [container, chartWidth, chartHeight]);
2955
+ this.init(container, chartWidth, chartHeight);
2959
2956
 
2960
2957
  // add the canvas above it
2961
2958
  canvas = createElement('canvas', {
@@ -3033,7 +3030,11 @@ if (CanvasRenderingContext2D) {
3033
3030
  tooltipDiv.innerHTML = args.text;
3034
3031
 
3035
3032
  // Compute the best position for the tooltip based on the divs size and container size.
3036
- position = chart.tooltip.getPosition(tooltipDiv.offsetWidth, tooltipDiv.offsetHeight, {plotX: args.x, plotY: args.y});
3033
+ position = chart.tooltip.getPosition(
3034
+ tooltipDiv.offsetWidth,
3035
+ tooltipDiv.offsetHeight,
3036
+ { plotX: args.x, plotY: args.y }
3037
+ );
3037
3038
 
3038
3039
  css(tooltipDiv, {
3039
3040
  visibility: VISIBLE,
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @license Highcharts JS v4.1.9 (2015-10-07)
2
+ * @license Highcharts JS v4.1.10 (2015-12-07)
3
3
  * Data module
4
4
  *
5
5
  * (c) 2012-2014 Torstein Honsi
@@ -7,15 +7,19 @@
7
7
  * License: www.highcharts.com/license
8
8
  */
9
9
 
10
- // JSLint options:
11
- /*global jQuery, HighchartsAdapter */
12
-
13
- (function (Highcharts) {
10
+ /*global jQuery */
11
+ (function (factory) {
12
+ if (typeof module === 'object' && module.exports) {
13
+ module.exports = factory;
14
+ } else {
15
+ factory(Highcharts);
16
+ }
17
+ }(function (Highcharts) {
14
18
 
15
19
  // Utilities
16
20
  var each = Highcharts.each,
17
21
  pick = Highcharts.pick,
18
- inArray = HighchartsAdapter.inArray,
22
+ inArray = Highcharts.inArray,
19
23
  splat = Highcharts.splat,
20
24
  SeriesBuilder;
21
25
 
@@ -28,740 +32,740 @@
28
32
  // Set the prototype properties
29
33
  Highcharts.extend(Data.prototype, {
30
34
 
31
- /**
32
- * Initialize the Data object with the given options
33
- */
34
- init: function (options, chartOptions) {
35
- this.options = options;
36
- this.chartOptions = chartOptions;
37
- this.columns = options.columns || this.rowsToColumns(options.rows) || [];
38
- this.firstRowAsNames = pick(options.firstRowAsNames, true);
39
- this.decimalRegex = options.decimalPoint && new RegExp('^([0-9]+)' + options.decimalPoint + '([0-9]+)$');
40
-
41
- // This is a two-dimensional array holding the raw, trimmed string values
42
- // with the same organisation as the columns array. It makes it possible
43
- // for example to revert from interpreted timestamps to string-based
44
- // categories.
45
- this.rawColumns = [];
46
-
47
- // No need to parse or interpret anything
48
- if (this.columns.length) {
49
- this.dataFound();
50
-
51
- // Parse and interpret
52
- } else {
35
+ /**
36
+ * Initialize the Data object with the given options
37
+ */
38
+ init: function (options, chartOptions) {
39
+ this.options = options;
40
+ this.chartOptions = chartOptions;
41
+ this.columns = options.columns || this.rowsToColumns(options.rows) || [];
42
+ this.firstRowAsNames = pick(options.firstRowAsNames, true);
43
+ this.decimalRegex = options.decimalPoint && new RegExp('^(-?[0-9]+)' + options.decimalPoint + '([0-9]+)$');
44
+
45
+ // This is a two-dimensional array holding the raw, trimmed string values
46
+ // with the same organisation as the columns array. It makes it possible
47
+ // for example to revert from interpreted timestamps to string-based
48
+ // categories.
49
+ this.rawColumns = [];
50
+
51
+ // No need to parse or interpret anything
52
+ if (this.columns.length) {
53
+ this.dataFound();
54
+
55
+ // Parse and interpret
56
+ } else {
53
57
 
54
- // Parse a CSV string if options.csv is given
55
- this.parseCSV();
56
-
57
- // Parse a HTML table if options.table is given
58
- this.parseTable();
58
+ // Parse a CSV string if options.csv is given
59
+ this.parseCSV();
60
+
61
+ // Parse a HTML table if options.table is given
62
+ this.parseTable();
59
63
 
60
- // Parse a Google Spreadsheet
61
- this.parseGoogleSpreadsheet();
62
- }
64
+ // Parse a Google Spreadsheet
65
+ this.parseGoogleSpreadsheet();
66
+ }
63
67
 
64
- },
68
+ },
65
69
 
66
- /**
67
- * Get the column distribution. For example, a line series takes a single column for
68
- * Y values. A range series takes two columns for low and high values respectively,
69
- * and an OHLC series takes four columns.
70
- */
71
- getColumnDistribution: function () {
72
- var chartOptions = this.chartOptions,
73
- options = this.options,
74
- xColumns = [],
75
- getValueCount = function (type) {
76
- return (Highcharts.seriesTypes[type || 'line'].prototype.pointArrayMap || [0]).length;
77
- },
78
- getPointArrayMap = function (type) {
79
- return Highcharts.seriesTypes[type || 'line'].prototype.pointArrayMap;
80
- },
81
- globalType = chartOptions && chartOptions.chart && chartOptions.chart.type,
82
- individualCounts = [],
83
- seriesBuilders = [],
84
- seriesIndex = 0,
85
- i;
86
-
87
- each((chartOptions && chartOptions.series) || [], function (series) {
88
- individualCounts.push(getValueCount(series.type || globalType));
89
- });
70
+ /**
71
+ * Get the column distribution. For example, a line series takes a single column for
72
+ * Y values. A range series takes two columns for low and high values respectively,
73
+ * and an OHLC series takes four columns.
74
+ */
75
+ getColumnDistribution: function () {
76
+ var chartOptions = this.chartOptions,
77
+ options = this.options,
78
+ xColumns = [],
79
+ getValueCount = function (type) {
80
+ return (Highcharts.seriesTypes[type || 'line'].prototype.pointArrayMap || [0]).length;
81
+ },
82
+ getPointArrayMap = function (type) {
83
+ return Highcharts.seriesTypes[type || 'line'].prototype.pointArrayMap;
84
+ },
85
+ globalType = chartOptions && chartOptions.chart && chartOptions.chart.type,
86
+ individualCounts = [],
87
+ seriesBuilders = [],
88
+ seriesIndex = 0,
89
+ i;
90
+
91
+ each((chartOptions && chartOptions.series) || [], function (series) {
92
+ individualCounts.push(getValueCount(series.type || globalType));
93
+ });
90
94
 
91
- // Collect the x-column indexes from seriesMapping
92
- each((options && options.seriesMapping) || [], function (mapping) {
93
- xColumns.push(mapping.x || 0);
94
- });
95
+ // Collect the x-column indexes from seriesMapping
96
+ each((options && options.seriesMapping) || [], function (mapping) {
97
+ xColumns.push(mapping.x || 0);
98
+ });
95
99
 
96
- // If there are no defined series with x-columns, use the first column as x column
97
- if (xColumns.length === 0) {
98
- xColumns.push(0);
99
- }
100
+ // If there are no defined series with x-columns, use the first column as x column
101
+ if (xColumns.length === 0) {
102
+ xColumns.push(0);
103
+ }
100
104
 
101
- // Loop all seriesMappings and constructs SeriesBuilders from
102
- // the mapping options.
103
- each((options && options.seriesMapping) || [], function (mapping) {
104
- var builder = new SeriesBuilder(),
105
- name,
106
- numberOfValueColumnsNeeded = individualCounts[seriesIndex] || getValueCount(globalType),
107
- seriesArr = (chartOptions && chartOptions.series) || [],
108
- series = seriesArr[seriesIndex] || {},
109
- pointArrayMap = getPointArrayMap(series.type || globalType) || ['y'];
110
-
111
- // Add an x reader from the x property or from an undefined column
112
- // if the property is not set. It will then be auto populated later.
113
- builder.addColumnReader(mapping.x, 'x');
114
-
115
- // Add all column mappings
116
- for (name in mapping) {
117
- if (mapping.hasOwnProperty(name) && name !== 'x') {
118
- builder.addColumnReader(mapping[name], name);
105
+ // Loop all seriesMappings and constructs SeriesBuilders from
106
+ // the mapping options.
107
+ each((options && options.seriesMapping) || [], function (mapping) {
108
+ var builder = new SeriesBuilder(),
109
+ name,
110
+ numberOfValueColumnsNeeded = individualCounts[seriesIndex] || getValueCount(globalType),
111
+ seriesArr = (chartOptions && chartOptions.series) || [],
112
+ series = seriesArr[seriesIndex] || {},
113
+ pointArrayMap = getPointArrayMap(series.type || globalType) || ['y'];
114
+
115
+ // Add an x reader from the x property or from an undefined column
116
+ // if the property is not set. It will then be auto populated later.
117
+ builder.addColumnReader(mapping.x, 'x');
118
+
119
+ // Add all column mappings
120
+ for (name in mapping) {
121
+ if (mapping.hasOwnProperty(name) && name !== 'x') {
122
+ builder.addColumnReader(mapping[name], name);
123
+ }
119
124
  }
120
- }
121
125
 
122
- // Add missing columns
123
- for (i = 0; i < numberOfValueColumnsNeeded; i++) {
124
- if (!builder.hasReader(pointArrayMap[i])) {
125
- //builder.addNextColumnReader(pointArrayMap[i]);
126
- // Create and add a column reader for the next free column index
127
- builder.addColumnReader(undefined, pointArrayMap[i]);
126
+ // Add missing columns
127
+ for (i = 0; i < numberOfValueColumnsNeeded; i++) {
128
+ if (!builder.hasReader(pointArrayMap[i])) {
129
+ //builder.addNextColumnReader(pointArrayMap[i]);
130
+ // Create and add a column reader for the next free column index
131
+ builder.addColumnReader(undefined, pointArrayMap[i]);
132
+ }
128
133
  }
129
- }
130
134
 
131
- seriesBuilders.push(builder);
132
- seriesIndex++;
133
- });
135
+ seriesBuilders.push(builder);
136
+ seriesIndex++;
137
+ });
134
138
 
135
- var globalPointArrayMap = getPointArrayMap(globalType);
136
- if (globalPointArrayMap === undefined) {
137
- globalPointArrayMap = ['y'];
138
- }
139
+ var globalPointArrayMap = getPointArrayMap(globalType);
140
+ if (globalPointArrayMap === undefined) {
141
+ globalPointArrayMap = ['y'];
142
+ }
139
143
 
140
- this.valueCount = {
141
- global: getValueCount(globalType),
142
- xColumns: xColumns,
143
- individual: individualCounts,
144
- seriesBuilders: seriesBuilders,
145
- globalPointArrayMap: globalPointArrayMap
146
- };
147
- },
144
+ this.valueCount = {
145
+ global: getValueCount(globalType),
146
+ xColumns: xColumns,
147
+ individual: individualCounts,
148
+ seriesBuilders: seriesBuilders,
149
+ globalPointArrayMap: globalPointArrayMap
150
+ };
151
+ },
148
152
 
149
- /**
150
- * When the data is parsed into columns, either by CSV, table, GS or direct input,
151
- * continue with other operations.
152
- */
153
- dataFound: function () {
154
-
155
- if (this.options.switchRowsAndColumns) {
156
- this.columns = this.rowsToColumns(this.columns);
157
- }
153
+ /**
154
+ * When the data is parsed into columns, either by CSV, table, GS or direct input,
155
+ * continue with other operations.
156
+ */
157
+ dataFound: function () {
158
+
159
+ if (this.options.switchRowsAndColumns) {
160
+ this.columns = this.rowsToColumns(this.columns);
161
+ }
158
162
 
159
- // Interpret the info about series and columns
160
- this.getColumnDistribution();
163
+ // Interpret the info about series and columns
164
+ this.getColumnDistribution();
161
165
 
162
- // Interpret the values into right types
163
- this.parseTypes();
164
-
165
- // Handle columns if a handleColumns callback is given
166
- if (this.parsed() !== false) {
167
-
168
- // Complete if a complete callback is given
169
- this.complete();
170
- }
171
-
172
- },
173
-
174
- /**
175
- * Parse a CSV input string
176
- */
177
- parseCSV: function () {
178
- var self = this,
179
- options = this.options,
180
- csv = options.csv,
181
- columns = this.columns,
182
- startRow = options.startRow || 0,
183
- endRow = options.endRow || Number.MAX_VALUE,
184
- startColumn = options.startColumn || 0,
185
- endColumn = options.endColumn || Number.MAX_VALUE,
186
- itemDelimiter,
187
- lines,
188
- activeRowNo = 0;
166
+ // Interpret the values into right types
167
+ this.parseTypes();
189
168
 
190
- if (csv) {
169
+ // Handle columns if a handleColumns callback is given
170
+ if (this.parsed() !== false) {
191
171
 
192
- lines = csv
193
- .replace(/\r\n/g, "\n") // Unix
194
- .replace(/\r/g, "\n") // Mac
195
- .split(options.lineDelimiter || "\n");
196
-
197
- itemDelimiter = options.itemDelimiter || (csv.indexOf('\t') !== -1 ? '\t' : ',');
172
+ // Complete if a complete callback is given
173
+ this.complete();
174
+ }
198
175
 
199
- each(lines, function (line, rowNo) {
200
- var trimmed = self.trim(line),
201
- isComment = trimmed.indexOf('#') === 0,
202
- isBlank = trimmed === '',
203
- items;
176
+ },
177
+
178
+ /**
179
+ * Parse a CSV input string
180
+ */
181
+ parseCSV: function () {
182
+ var self = this,
183
+ options = this.options,
184
+ csv = options.csv,
185
+ columns = this.columns,
186
+ startRow = options.startRow || 0,
187
+ endRow = options.endRow || Number.MAX_VALUE,
188
+ startColumn = options.startColumn || 0,
189
+ endColumn = options.endColumn || Number.MAX_VALUE,
190
+ itemDelimiter,
191
+ lines,
192
+ activeRowNo = 0;
193
+
194
+ if (csv) {
195
+
196
+ lines = csv
197
+ .replace(/\r\n/g, '\n') // Unix
198
+ .replace(/\r/g, '\n') // Mac
199
+ .split(options.lineDelimiter || '\n');
200
+
201
+ itemDelimiter = options.itemDelimiter || (csv.indexOf('\t') !== -1 ? '\t' : ',');
204
202
 
205
- if (rowNo >= startRow && rowNo <= endRow && !isComment && !isBlank) {
206
- items = line.split(itemDelimiter);
207
- each(items, function (item, colNo) {
208
- if (colNo >= startColumn && colNo <= endColumn) {
209
- if (!columns[colNo - startColumn]) {
210
- columns[colNo - startColumn] = [];
203
+ each(lines, function (line, rowNo) {
204
+ var trimmed = self.trim(line),
205
+ isComment = trimmed.indexOf('#') === 0,
206
+ isBlank = trimmed === '',
207
+ items;
208
+
209
+ if (rowNo >= startRow && rowNo <= endRow && !isComment && !isBlank) {
210
+ items = line.split(itemDelimiter);
211
+ each(items, function (item, colNo) {
212
+ if (colNo >= startColumn && colNo <= endColumn) {
213
+ if (!columns[colNo - startColumn]) {
214
+ columns[colNo - startColumn] = [];
215
+ }
216
+
217
+ columns[colNo - startColumn][activeRowNo] = item;
211
218
  }
212
-
213
- columns[colNo - startColumn][activeRowNo] = item;
214
- }
215
- });
216
- activeRowNo += 1;
217
- }
218
- });
219
+ });
220
+ activeRowNo += 1;
221
+ }
222
+ });
219
223
 
220
- this.dataFound();
221
- }
222
- },
223
-
224
- /**
225
- * Parse a HTML table
226
- */
227
- parseTable: function () {
228
- var options = this.options,
229
- table = options.table,
230
- columns = this.columns,
231
- startRow = options.startRow || 0,
232
- endRow = options.endRow || Number.MAX_VALUE,
233
- startColumn = options.startColumn || 0,
234
- endColumn = options.endColumn || Number.MAX_VALUE;
235
-
236
- if (table) {
237
-
238
- if (typeof table === 'string') {
239
- table = document.getElementById(table);
224
+ this.dataFound();
240
225
  }
241
-
242
- each(table.getElementsByTagName('tr'), function (tr, rowNo) {
243
- if (rowNo >= startRow && rowNo <= endRow) {
244
- each(tr.children, function (item, colNo) {
245
- if ((item.tagName === 'TD' || item.tagName === 'TH') && colNo >= startColumn && colNo <= endColumn) {
246
- if (!columns[colNo - startColumn]) {
247
- columns[colNo - startColumn] = [];
248
- }
249
-
250
- columns[colNo - startColumn][rowNo - startRow] = item.innerHTML;
251
- }
252
- });
226
+ },
227
+
228
+ /**
229
+ * Parse a HTML table
230
+ */
231
+ parseTable: function () {
232
+ var options = this.options,
233
+ table = options.table,
234
+ columns = this.columns,
235
+ startRow = options.startRow || 0,
236
+ endRow = options.endRow || Number.MAX_VALUE,
237
+ startColumn = options.startColumn || 0,
238
+ endColumn = options.endColumn || Number.MAX_VALUE;
239
+
240
+ if (table) {
241
+
242
+ if (typeof table === 'string') {
243
+ table = document.getElementById(table);
253
244
  }
254
- });
255
-
256
- this.dataFound(); // continue
257
- }
258
- },
259
-
260
- /**
261
- */
262
- parseGoogleSpreadsheet: function () {
263
- var self = this,
264
- options = this.options,
265
- googleSpreadsheetKey = options.googleSpreadsheetKey,
266
- columns = this.columns,
267
- startRow = options.startRow || 0,
268
- endRow = options.endRow || Number.MAX_VALUE,
269
- startColumn = options.startColumn || 0,
270
- endColumn = options.endColumn || Number.MAX_VALUE,
271
- gr, // google row
272
- gc; // google column
273
-
274
- if (googleSpreadsheetKey) {
275
- jQuery.ajax({
276
- dataType: 'json',
277
- url: 'https://spreadsheets.google.com/feeds/cells/' +
278
- googleSpreadsheetKey + '/' + (options.googleSpreadsheetWorksheet || 'od6') +
279
- '/public/values?alt=json-in-script&callback=?',
280
- error: options.error,
281
- success: function (json) {
282
- // Prepare the data from the spreadsheat
283
- var cells = json.feed.entry,
284
- cell,
285
- cellCount = cells.length,
286
- colCount = 0,
287
- rowCount = 0,
288
- i;
289
245
 
290
- // First, find the total number of columns and rows that
291
- // are actually filled with data
292
- for (i = 0; i < cellCount; i++) {
293
- cell = cells[i];
294
- colCount = Math.max(colCount, cell.gs$cell.col);
295
- rowCount = Math.max(rowCount, cell.gs$cell.row);
246
+ each(table.getElementsByTagName('tr'), function (tr, rowNo) {
247
+ if (rowNo >= startRow && rowNo <= endRow) {
248
+ each(tr.children, function (item, colNo) {
249
+ if ((item.tagName === 'TD' || item.tagName === 'TH') && colNo >= startColumn && colNo <= endColumn) {
250
+ if (!columns[colNo - startColumn]) {
251
+ columns[colNo - startColumn] = [];
252
+ }
253
+
254
+ columns[colNo - startColumn][rowNo - startRow] = item.innerHTML;
255
+ }
256
+ });
296
257
  }
297
-
298
- // Set up arrays containing the column data
299
- for (i = 0; i < colCount; i++) {
300
- if (i >= startColumn && i <= endColumn) {
301
- // Create new columns with the length of either end-start or rowCount
302
- columns[i - startColumn] = [];
303
-
304
- // Setting the length to avoid jslint warning
305
- columns[i - startColumn].length = Math.min(rowCount, endRow - startRow);
258
+ });
259
+
260
+ this.dataFound(); // continue
261
+ }
262
+ },
263
+
264
+ /**
265
+ */
266
+ parseGoogleSpreadsheet: function () {
267
+ var self = this,
268
+ options = this.options,
269
+ googleSpreadsheetKey = options.googleSpreadsheetKey,
270
+ columns = this.columns,
271
+ startRow = options.startRow || 0,
272
+ endRow = options.endRow || Number.MAX_VALUE,
273
+ startColumn = options.startColumn || 0,
274
+ endColumn = options.endColumn || Number.MAX_VALUE,
275
+ gr, // google row
276
+ gc; // google column
277
+
278
+ if (googleSpreadsheetKey) {
279
+ jQuery.ajax({
280
+ dataType: 'json',
281
+ url: 'https://spreadsheets.google.com/feeds/cells/' +
282
+ googleSpreadsheetKey + '/' + (options.googleSpreadsheetWorksheet || 'od6') +
283
+ '/public/values?alt=json-in-script&callback=?',
284
+ error: options.error,
285
+ success: function (json) {
286
+ // Prepare the data from the spreadsheat
287
+ var cells = json.feed.entry,
288
+ cell,
289
+ cellCount = cells.length,
290
+ colCount = 0,
291
+ rowCount = 0,
292
+ i;
293
+
294
+ // First, find the total number of columns and rows that
295
+ // are actually filled with data
296
+ for (i = 0; i < cellCount; i++) {
297
+ cell = cells[i];
298
+ colCount = Math.max(colCount, cell.gs$cell.col);
299
+ rowCount = Math.max(rowCount, cell.gs$cell.row);
306
300
  }
307
- }
308
301
 
309
- // Loop over the cells and assign the value to the right
310
- // place in the column arrays
311
- for (i = 0; i < cellCount; i++) {
312
- cell = cells[i];
313
- gr = cell.gs$cell.row - 1; // rows start at 1
314
- gc = cell.gs$cell.col - 1; // columns start at 1
315
-
316
- // If both row and col falls inside start and end
317
- // set the transposed cell value in the newly created columns
318
- if (gc >= startColumn && gc <= endColumn &&
319
- gr >= startRow && gr <= endRow) {
320
- columns[gc - startColumn][gr - startRow] = cell.content.$t;
302
+ // Set up arrays containing the column data
303
+ for (i = 0; i < colCount; i++) {
304
+ if (i >= startColumn && i <= endColumn) {
305
+ // Create new columns with the length of either end-start or rowCount
306
+ columns[i - startColumn] = [];
307
+
308
+ // Setting the length to avoid jslint warning
309
+ columns[i - startColumn].length = Math.min(rowCount, endRow - startRow);
310
+ }
311
+ }
312
+
313
+ // Loop over the cells and assign the value to the right
314
+ // place in the column arrays
315
+ for (i = 0; i < cellCount; i++) {
316
+ cell = cells[i];
317
+ gr = cell.gs$cell.row - 1; // rows start at 1
318
+ gc = cell.gs$cell.col - 1; // columns start at 1
319
+
320
+ // If both row and col falls inside start and end
321
+ // set the transposed cell value in the newly created columns
322
+ if (gc >= startColumn && gc <= endColumn &&
323
+ gr >= startRow && gr <= endRow) {
324
+ columns[gc - startColumn][gr - startRow] = cell.content.$t;
325
+ }
321
326
  }
327
+ self.dataFound();
322
328
  }
323
- self.dataFound();
329
+ });
330
+ }
331
+ },
332
+
333
+ /**
334
+ * Trim a string from whitespace
335
+ */
336
+ trim: function (str, inside) {
337
+ if (typeof str === 'string') {
338
+ str = str.replace(/^\s+|\s+$/g, '');
339
+
340
+ // Clear white space insdie the string, like thousands separators
341
+ if (inside && /^[0-9\s]+$/.test(str)) {
342
+ str = str.replace(/\s/g, '');
324
343
  }
325
- });
326
- }
327
- },
328
-
329
- /**
330
- * Trim a string from whitespace
331
- */
332
- trim: function (str, inside) {
333
- if (typeof str === 'string') {
334
- str = str.replace(/^\s+|\s+$/g, '');
335
344
 
336
- // Clear white space insdie the string, like thousands separators
337
- if (inside && /^[0-9\s]+$/.test(str)) {
338
- str = str.replace(/\s/g, '');
345
+ if (this.decimalRegex) {
346
+ str = str.replace(this.decimalRegex, '$1.$2');
347
+ }
339
348
  }
340
-
341
- if (this.decimalRegex) {
342
- str = str.replace(this.decimalRegex, '$1.$2');
349
+ return str;
350
+ },
351
+
352
+ /**
353
+ * Parse numeric cells in to number types and date types in to true dates.
354
+ */
355
+ parseTypes: function () {
356
+ var columns = this.columns,
357
+ col = columns.length;
358
+
359
+ while (col--) {
360
+ this.parseColumn(columns[col], col);
343
361
  }
344
- }
345
- return str;
346
- },
347
-
348
- /**
349
- * Parse numeric cells in to number types and date types in to true dates.
350
- */
351
- parseTypes: function () {
352
- var columns = this.columns,
353
- col = columns.length;
354
-
355
- while (col--) {
356
- this.parseColumn(columns[col], col);
357
- }
358
362
 
359
- },
363
+ },
360
364
 
361
- /**
362
- * Parse a single column. Set properties like .isDatetime and .isNumeric.
363
- */
364
- parseColumn: function (column, col) {
365
- var rawColumns = this.rawColumns,
366
- columns = this.columns,
367
- row = column.length,
368
- val,
369
- floatVal,
370
- trimVal,
371
- trimInsideVal,
372
- firstRowAsNames = this.firstRowAsNames,
373
- isXColumn = inArray(col, this.valueCount.xColumns) !== -1,
374
- dateVal,
375
- backup = [],
376
- diff,
377
- chartOptions = this.chartOptions,
378
- descending,
379
- columnTypes = this.options.columnTypes || [],
380
- columnType = columnTypes[col],
381
- forceCategory = isXColumn && ((chartOptions && chartOptions.xAxis && splat(chartOptions.xAxis)[0].type === 'category') || columnType === 'string');
382
-
383
- if (!rawColumns[col]) {
384
- rawColumns[col] = [];
385
- }
386
- while (row--) {
387
- val = backup[row] || column[row];
365
+ /**
366
+ * Parse a single column. Set properties like .isDatetime and .isNumeric.
367
+ */
368
+ parseColumn: function (column, col) {
369
+ var rawColumns = this.rawColumns,
370
+ columns = this.columns,
371
+ row = column.length,
372
+ val,
373
+ floatVal,
374
+ trimVal,
375
+ trimInsideVal,
376
+ firstRowAsNames = this.firstRowAsNames,
377
+ isXColumn = inArray(col, this.valueCount.xColumns) !== -1,
378
+ dateVal,
379
+ backup = [],
380
+ diff,
381
+ chartOptions = this.chartOptions,
382
+ descending,
383
+ columnTypes = this.options.columnTypes || [],
384
+ columnType = columnTypes[col],
385
+ forceCategory = isXColumn && ((chartOptions && chartOptions.xAxis && splat(chartOptions.xAxis)[0].type === 'category') || columnType === 'string');
388
386
 
389
- trimVal = this.trim(val);
390
- trimInsideVal = this.trim(val, true);
391
- floatVal = parseFloat(trimInsideVal);
392
-
393
- // Set it the first time
394
- if (rawColumns[col][row] === undefined) {
395
- rawColumns[col][row] = trimVal;
387
+ if (!rawColumns[col]) {
388
+ rawColumns[col] = [];
396
389
  }
397
-
398
- // Disable number or date parsing by setting the X axis type to category
399
- if (forceCategory || (row === 0 && firstRowAsNames)) {
400
- column[row] = trimVal;
401
-
402
- } else if (+trimInsideVal === floatVal) { // is numeric
403
-
404
- column[row] = floatVal;
390
+ while (row--) {
391
+ val = backup[row] || column[row];
405
392
 
406
- // If the number is greater than milliseconds in a year, assume datetime
407
- if (floatVal > 365 * 24 * 3600 * 1000 && columnType !== 'float') {
408
- column.isDatetime = true;
409
- } else {
410
- column.isNumeric = true;
411
- }
393
+ trimVal = this.trim(val);
394
+ trimInsideVal = this.trim(val, true);
395
+ floatVal = parseFloat(trimInsideVal);
412
396
 
413
- if (column[row + 1] !== undefined) {
414
- descending = floatVal > column[row + 1];
397
+ // Set it the first time
398
+ if (rawColumns[col][row] === undefined) {
399
+ rawColumns[col][row] = trimVal;
415
400
  }
416
-
417
- // String, continue to determine if it is a date string or really a string
418
- } else {
419
- dateVal = this.parseDate(val);
420
- // Only allow parsing of dates if this column is an x-column
421
- if (isXColumn && typeof dateVal === 'number' && !isNaN(dateVal) && columnType !== 'float') { // is date
422
- backup[row] = val;
423
- column[row] = dateVal;
424
- column.isDatetime = true;
425
-
426
- // Check if the dates are uniformly descending or ascending. If they
427
- // are not, chances are that they are a different time format, so check
428
- // for alternative.
401
+
402
+ // Disable number or date parsing by setting the X axis type to category
403
+ if (forceCategory || (row === 0 && firstRowAsNames)) {
404
+ column[row] = trimVal;
405
+
406
+ } else if (+trimInsideVal === floatVal) { // is numeric
407
+
408
+ column[row] = floatVal;
409
+
410
+ // If the number is greater than milliseconds in a year, assume datetime
411
+ if (floatVal > 365 * 24 * 3600 * 1000 && columnType !== 'float') {
412
+ column.isDatetime = true;
413
+ } else {
414
+ column.isNumeric = true;
415
+ }
416
+
429
417
  if (column[row + 1] !== undefined) {
430
- diff = dateVal > column[row + 1];
431
- if (diff !== descending && descending !== undefined) {
432
- if (this.alternativeFormat) {
433
- this.dateFormat = this.alternativeFormat;
434
- row = column.length;
435
- this.alternativeFormat = this.dateFormats[this.dateFormat].alternative;
436
- } else {
437
- column.unsorted = true;
438
- }
439
- }
440
- descending = diff;
418
+ descending = floatVal > column[row + 1];
441
419
  }
442
420
 
443
- } else { // string
444
- column[row] = trimVal === '' ? null : trimVal;
445
- if (row !== 0 && (column.isDatetime || column.isNumeric)) {
446
- column.mixed = true;
421
+ // String, continue to determine if it is a date string or really a string
422
+ } else {
423
+ dateVal = this.parseDate(val);
424
+ // Only allow parsing of dates if this column is an x-column
425
+ if (isXColumn && typeof dateVal === 'number' && !isNaN(dateVal) && columnType !== 'float') { // is date
426
+ backup[row] = val;
427
+ column[row] = dateVal;
428
+ column.isDatetime = true;
429
+
430
+ // Check if the dates are uniformly descending or ascending. If they
431
+ // are not, chances are that they are a different time format, so check
432
+ // for alternative.
433
+ if (column[row + 1] !== undefined) {
434
+ diff = dateVal > column[row + 1];
435
+ if (diff !== descending && descending !== undefined) {
436
+ if (this.alternativeFormat) {
437
+ this.dateFormat = this.alternativeFormat;
438
+ row = column.length;
439
+ this.alternativeFormat = this.dateFormats[this.dateFormat].alternative;
440
+ } else {
441
+ column.unsorted = true;
442
+ }
443
+ }
444
+ descending = diff;
445
+ }
446
+
447
+ } else { // string
448
+ column[row] = trimVal === '' ? null : trimVal;
449
+ if (row !== 0 && (column.isDatetime || column.isNumeric)) {
450
+ column.mixed = true;
451
+ }
447
452
  }
448
453
  }
449
454
  }
450
- }
451
455
 
452
- // If strings are intermixed with numbers or dates in a parsed column, it is an indication
453
- // that parsing went wrong or the data was not intended to display as numbers or dates and
454
- // parsing is too aggressive. Fall back to categories. Demonstrated in the
455
- // highcharts/demo/column-drilldown sample.
456
- if (isXColumn && column.mixed) {
457
- columns[col] = rawColumns[col];
458
- }
456
+ // If strings are intermixed with numbers or dates in a parsed column, it is an indication
457
+ // that parsing went wrong or the data was not intended to display as numbers or dates and
458
+ // parsing is too aggressive. Fall back to categories. Demonstrated in the
459
+ // highcharts/demo/column-drilldown sample.
460
+ if (isXColumn && column.mixed) {
461
+ columns[col] = rawColumns[col];
462
+ }
459
463
 
460
- // If the 0 column is date or number and descending, reverse all columns.
461
- if (isXColumn && descending && this.options.sort) {
462
- for (col = 0; col < columns.length; col++) {
463
- columns[col].reverse();
464
- if (firstRowAsNames) {
465
- columns[col].unshift(columns[col].pop());
464
+ // If the 0 column is date or number and descending, reverse all columns.
465
+ if (isXColumn && descending && this.options.sort) {
466
+ for (col = 0; col < columns.length; col++) {
467
+ columns[col].reverse();
468
+ if (firstRowAsNames) {
469
+ columns[col].unshift(columns[col].pop());
470
+ }
466
471
  }
467
472
  }
468
- }
469
- },
470
-
471
- /**
472
- * A collection of available date formats, extendable from the outside to support
473
- * custom date formats.
474
- */
475
- dateFormats: {
476
- 'YYYY-mm-dd': {
477
- regex: /^([0-9]{4})[\-\/\.]([0-9]{2})[\-\/\.]([0-9]{2})$/,
478
- parser: function (match) {
479
- return Date.UTC(+match[1], match[2] - 1, +match[3]);
480
- }
481
473
  },
482
- 'dd/mm/YYYY': {
483
- regex: /^([0-9]{1,2})[\-\/\.]([0-9]{1,2})[\-\/\.]([0-9]{4})$/,
484
- parser: function (match) {
485
- return Date.UTC(+match[3], match[2] - 1, +match[1]);
474
+
475
+ /**
476
+ * A collection of available date formats, extendable from the outside to support
477
+ * custom date formats.
478
+ */
479
+ dateFormats: {
480
+ 'YYYY-mm-dd': {
481
+ regex: /^([0-9]{4})[\-\/\.]([0-9]{2})[\-\/\.]([0-9]{2})$/,
482
+ parser: function (match) {
483
+ return Date.UTC(+match[1], match[2] - 1, +match[3]);
484
+ }
486
485
  },
487
- alternative: 'mm/dd/YYYY' // different format with the same regex
488
- },
489
- 'mm/dd/YYYY': {
490
- regex: /^([0-9]{1,2})[\-\/\.]([0-9]{1,2})[\-\/\.]([0-9]{4})$/,
491
- parser: function (match) {
492
- return Date.UTC(+match[3], match[1] - 1, +match[2]);
493
- }
494
- },
495
- 'dd/mm/YY': {
496
- regex: /^([0-9]{1,2})[\-\/\.]([0-9]{1,2})[\-\/\.]([0-9]{2})$/,
497
- parser: function (match) {
498
- return Date.UTC(+match[3] + 2000, match[2] - 1, +match[1]);
486
+ 'dd/mm/YYYY': {
487
+ regex: /^([0-9]{1,2})[\-\/\.]([0-9]{1,2})[\-\/\.]([0-9]{4})$/,
488
+ parser: function (match) {
489
+ return Date.UTC(+match[3], match[2] - 1, +match[1]);
490
+ },
491
+ alternative: 'mm/dd/YYYY' // different format with the same regex
499
492
  },
500
- alternative: 'mm/dd/YY' // different format with the same regex
501
- },
502
- 'mm/dd/YY': {
503
- regex: /^([0-9]{1,2})[\-\/\.]([0-9]{1,2})[\-\/\.]([0-9]{2})$/,
504
- parser: function (match) {
505
- return Date.UTC(+match[3] + 2000, match[1] - 1, +match[2]);
493
+ 'mm/dd/YYYY': {
494
+ regex: /^([0-9]{1,2})[\-\/\.]([0-9]{1,2})[\-\/\.]([0-9]{4})$/,
495
+ parser: function (match) {
496
+ return Date.UTC(+match[3], match[1] - 1, +match[2]);
497
+ }
498
+ },
499
+ 'dd/mm/YY': {
500
+ regex: /^([0-9]{1,2})[\-\/\.]([0-9]{1,2})[\-\/\.]([0-9]{2})$/,
501
+ parser: function (match) {
502
+ return Date.UTC(+match[3] + 2000, match[2] - 1, +match[1]);
503
+ },
504
+ alternative: 'mm/dd/YY' // different format with the same regex
505
+ },
506
+ 'mm/dd/YY': {
507
+ regex: /^([0-9]{1,2})[\-\/\.]([0-9]{1,2})[\-\/\.]([0-9]{2})$/,
508
+ parser: function (match) {
509
+ return Date.UTC(+match[3] + 2000, match[1] - 1, +match[2]);
510
+ }
506
511
  }
507
- }
508
- },
509
-
510
- /**
511
- * Parse a date and return it as a number. Overridable through options.parseDate.
512
- */
513
- parseDate: function (val) {
514
- var parseDate = this.options.parseDate,
515
- ret,
516
- key,
517
- format,
518
- dateFormat = this.options.dateFormat || this.dateFormat,
519
- match;
520
-
521
- if (parseDate) {
522
- ret = parseDate(val);
512
+ },
523
513
 
524
- } else if (typeof val === 'string') {
525
- // Auto-detect the date format the first time
526
- if (!dateFormat) {
527
- for (key in this.dateFormats) {
528
- format = this.dateFormats[key];
514
+ /**
515
+ * Parse a date and return it as a number. Overridable through options.parseDate.
516
+ */
517
+ parseDate: function (val) {
518
+ var parseDate = this.options.parseDate,
519
+ ret,
520
+ key,
521
+ format,
522
+ dateFormat = this.options.dateFormat || this.dateFormat,
523
+ match;
524
+
525
+ if (parseDate) {
526
+ ret = parseDate(val);
527
+
528
+ } else if (typeof val === 'string') {
529
+ // Auto-detect the date format the first time
530
+ if (!dateFormat) {
531
+ for (key in this.dateFormats) {
532
+ format = this.dateFormats[key];
533
+ match = val.match(format.regex);
534
+ if (match) {
535
+ this.dateFormat = dateFormat = key;
536
+ this.alternativeFormat = format.alternative;
537
+ ret = format.parser(match);
538
+ break;
539
+ }
540
+ }
541
+ // Next time, use the one previously found
542
+ } else {
543
+ format = this.dateFormats[dateFormat];
529
544
  match = val.match(format.regex);
530
545
  if (match) {
531
- this.dateFormat = dateFormat = key;
532
- this.alternativeFormat = format.alternative;
533
546
  ret = format.parser(match);
534
- break;
535
547
  }
536
548
  }
537
- // Next time, use the one previously found
538
- } else {
539
- format = this.dateFormats[dateFormat];
540
- match = val.match(format.regex);
541
- if (match) {
542
- ret = format.parser(match);
543
- }
544
- }
545
- // Fall back to Date.parse
546
- if (!match) {
547
- match = Date.parse(val);
548
- // External tools like Date.js and MooTools extend Date object and
549
- // returns a date.
550
- if (typeof match === 'object' && match !== null && match.getTime) {
551
- ret = match.getTime() - match.getTimezoneOffset() * 60000;
552
-
553
- // Timestamp
554
- } else if (typeof match === 'number' && !isNaN(match)) {
555
- ret = match - (new Date(match)).getTimezoneOffset() * 60000;
549
+ // Fall back to Date.parse
550
+ if (!match) {
551
+ match = Date.parse(val);
552
+ // External tools like Date.js and MooTools extend Date object and
553
+ // returns a date.
554
+ if (typeof match === 'object' && match !== null && match.getTime) {
555
+ ret = match.getTime() - match.getTimezoneOffset() * 60000;
556
+
557
+ // Timestamp
558
+ } else if (typeof match === 'number' && !isNaN(match)) {
559
+ ret = match - (new Date(match)).getTimezoneOffset() * 60000;
560
+ }
556
561
  }
557
562
  }
558
- }
559
- return ret;
560
- },
561
-
562
- /**
563
- * Reorganize rows into columns
564
- */
565
- rowsToColumns: function (rows) {
566
- var row,
567
- rowsLength,
568
- col,
569
- colsLength,
570
- columns;
571
-
572
- if (rows) {
573
- columns = [];
574
- rowsLength = rows.length;
575
- for (row = 0; row < rowsLength; row++) {
576
- colsLength = rows[row].length;
577
- for (col = 0; col < colsLength; col++) {
578
- if (!columns[col]) {
579
- columns[col] = [];
563
+ return ret;
564
+ },
565
+
566
+ /**
567
+ * Reorganize rows into columns
568
+ */
569
+ rowsToColumns: function (rows) {
570
+ var row,
571
+ rowsLength,
572
+ col,
573
+ colsLength,
574
+ columns;
575
+
576
+ if (rows) {
577
+ columns = [];
578
+ rowsLength = rows.length;
579
+ for (row = 0; row < rowsLength; row++) {
580
+ colsLength = rows[row].length;
581
+ for (col = 0; col < colsLength; col++) {
582
+ if (!columns[col]) {
583
+ columns[col] = [];
584
+ }
585
+ columns[col][row] = rows[row][col];
580
586
  }
581
- columns[col][row] = rows[row][col];
582
587
  }
583
588
  }
584
- }
585
- return columns;
586
- },
587
-
588
- /**
589
- * A hook for working directly on the parsed columns
590
- */
591
- parsed: function () {
592
- if (this.options.parsed) {
593
- return this.options.parsed.call(this, this.columns);
594
- }
595
- },
596
-
597
- getFreeIndexes: function (numberOfColumns, seriesBuilders) {
598
- var s,
599
- i,
600
- freeIndexes = [],
601
- freeIndexValues = [],
602
- referencedIndexes;
603
-
604
- // Add all columns as free
605
- for (i = 0; i < numberOfColumns; i = i + 1) {
606
- freeIndexes.push(true);
607
- }
589
+ return columns;
590
+ },
591
+
592
+ /**
593
+ * A hook for working directly on the parsed columns
594
+ */
595
+ parsed: function () {
596
+ if (this.options.parsed) {
597
+ return this.options.parsed.call(this, this.columns);
598
+ }
599
+ },
608
600
 
609
- // Loop all defined builders and remove their referenced columns
610
- for (s = 0; s < seriesBuilders.length; s = s + 1) {
611
- referencedIndexes = seriesBuilders[s].getReferencedColumnIndexes();
601
+ getFreeIndexes: function (numberOfColumns, seriesBuilders) {
602
+ var s,
603
+ i,
604
+ freeIndexes = [],
605
+ freeIndexValues = [],
606
+ referencedIndexes;
612
607
 
613
- for (i = 0; i < referencedIndexes.length; i = i + 1) {
614
- freeIndexes[referencedIndexes[i]] = false;
608
+ // Add all columns as free
609
+ for (i = 0; i < numberOfColumns; i = i + 1) {
610
+ freeIndexes.push(true);
615
611
  }
616
- }
617
612
 
618
- // Collect the values for the free indexes
619
- for (i = 0; i < freeIndexes.length; i = i + 1) {
620
- if (freeIndexes[i]) {
621
- freeIndexValues.push(i);
622
- }
623
- }
613
+ // Loop all defined builders and remove their referenced columns
614
+ for (s = 0; s < seriesBuilders.length; s = s + 1) {
615
+ referencedIndexes = seriesBuilders[s].getReferencedColumnIndexes();
624
616
 
625
- return freeIndexValues;
626
- },
627
-
628
- /**
629
- * If a complete callback function is provided in the options, interpret the
630
- * columns into a Highcharts options object.
631
- */
632
- complete: function () {
633
-
634
- var columns = this.columns,
635
- xColumns = [],
636
- type,
637
- options = this.options,
638
- series,
639
- data,
640
- i,
641
- j,
642
- r,
643
- seriesIndex,
644
- chartOptions,
645
- allSeriesBuilders = [],
646
- builder,
647
- freeIndexes,
648
- typeCol,
649
- index;
650
-
651
- xColumns.length = columns.length;
652
- if (options.complete || options.afterComplete) {
653
-
654
- // Get the names and shift the top row
655
- for (i = 0; i < columns.length; i++) {
656
- if (this.firstRowAsNames) {
657
- columns[i].name = columns[i].shift();
617
+ for (i = 0; i < referencedIndexes.length; i = i + 1) {
618
+ freeIndexes[referencedIndexes[i]] = false;
658
619
  }
659
620
  }
660
-
661
- // Use the next columns for series
662
- series = [];
663
- freeIndexes = this.getFreeIndexes(columns.length, this.valueCount.seriesBuilders);
664
621
 
665
- // Populate defined series
666
- for (seriesIndex = 0; seriesIndex < this.valueCount.seriesBuilders.length; seriesIndex++) {
667
- builder = this.valueCount.seriesBuilders[seriesIndex];
668
-
669
- // If the builder can be populated with remaining columns, then add it to allBuilders
670
- if (builder.populateColumns(freeIndexes)) {
671
- allSeriesBuilders.push(builder);
622
+ // Collect the values for the free indexes
623
+ for (i = 0; i < freeIndexes.length; i = i + 1) {
624
+ if (freeIndexes[i]) {
625
+ freeIndexValues.push(i);
672
626
  }
673
627
  }
674
628
 
675
- // Populate dynamic series
676
- while (freeIndexes.length > 0) {
677
- builder = new SeriesBuilder();
678
- builder.addColumnReader(0, 'x');
679
-
680
- // Mark index as used (not free)
681
- index = inArray(0, freeIndexes);
682
- if (index !== -1) {
683
- freeIndexes.splice(index, 1);
629
+ return freeIndexValues;
630
+ },
631
+
632
+ /**
633
+ * If a complete callback function is provided in the options, interpret the
634
+ * columns into a Highcharts options object.
635
+ */
636
+ complete: function () {
637
+
638
+ var columns = this.columns,
639
+ xColumns = [],
640
+ type,
641
+ options = this.options,
642
+ series,
643
+ data,
644
+ i,
645
+ j,
646
+ r,
647
+ seriesIndex,
648
+ chartOptions,
649
+ allSeriesBuilders = [],
650
+ builder,
651
+ freeIndexes,
652
+ typeCol,
653
+ index;
654
+
655
+ xColumns.length = columns.length;
656
+ if (options.complete || options.afterComplete) {
657
+
658
+ // Get the names and shift the top row
659
+ for (i = 0; i < columns.length; i++) {
660
+ if (this.firstRowAsNames) {
661
+ columns[i].name = columns[i].shift();
662
+ }
684
663
  }
664
+
665
+ // Use the next columns for series
666
+ series = [];
667
+ freeIndexes = this.getFreeIndexes(columns.length, this.valueCount.seriesBuilders);
685
668
 
686
- for (i = 0; i < this.valueCount.global; i++) {
687
- // Create and add a column reader for the next free column index
688
- builder.addColumnReader(undefined, this.valueCount.globalPointArrayMap[i]);
669
+ // Populate defined series
670
+ for (seriesIndex = 0; seriesIndex < this.valueCount.seriesBuilders.length; seriesIndex++) {
671
+ builder = this.valueCount.seriesBuilders[seriesIndex];
672
+
673
+ // If the builder can be populated with remaining columns, then add it to allBuilders
674
+ if (builder.populateColumns(freeIndexes)) {
675
+ allSeriesBuilders.push(builder);
676
+ }
689
677
  }
690
678
 
691
- // If the builder can be populated with remaining columns, then add it to allBuilders
692
- if (builder.populateColumns(freeIndexes)) {
693
- allSeriesBuilders.push(builder);
679
+ // Populate dynamic series
680
+ while (freeIndexes.length > 0) {
681
+ builder = new SeriesBuilder();
682
+ builder.addColumnReader(0, 'x');
683
+
684
+ // Mark index as used (not free)
685
+ index = inArray(0, freeIndexes);
686
+ if (index !== -1) {
687
+ freeIndexes.splice(index, 1);
688
+ }
689
+
690
+ for (i = 0; i < this.valueCount.global; i++) {
691
+ // Create and add a column reader for the next free column index
692
+ builder.addColumnReader(undefined, this.valueCount.globalPointArrayMap[i]);
693
+ }
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
+ }
694
699
  }
695
- }
696
700
 
697
- // Get the data-type from the first series x column
698
- if (allSeriesBuilders.length > 0 && allSeriesBuilders[0].readers.length > 0) {
699
- typeCol = columns[allSeriesBuilders[0].readers[0].columnIndex];
700
- if (typeCol !== undefined) {
701
- if (typeCol.isDatetime) {
702
- type = 'datetime';
703
- } else if (!typeCol.isNumeric) {
704
- type = 'category';
701
+ // Get the data-type from the first series x column
702
+ if (allSeriesBuilders.length > 0 && allSeriesBuilders[0].readers.length > 0) {
703
+ typeCol = columns[allSeriesBuilders[0].readers[0].columnIndex];
704
+ if (typeCol !== undefined) {
705
+ if (typeCol.isDatetime) {
706
+ type = 'datetime';
707
+ } else if (!typeCol.isNumeric) {
708
+ type = 'category';
709
+ }
705
710
  }
706
711
  }
707
- }
708
- // Axis type is category, then the "x" column should be called "name"
709
- if (type === 'category') {
710
- for (seriesIndex = 0; seriesIndex < allSeriesBuilders.length; seriesIndex++) {
711
- builder = allSeriesBuilders[seriesIndex];
712
- for (r = 0; r < builder.readers.length; r++) {
713
- if (builder.readers[r].configName === 'x') {
714
- builder.readers[r].configName = 'name';
712
+ // Axis type is category, then the "x" column should be called "name"
713
+ if (type === 'category') {
714
+ for (seriesIndex = 0; seriesIndex < allSeriesBuilders.length; seriesIndex++) {
715
+ builder = allSeriesBuilders[seriesIndex];
716
+ for (r = 0; r < builder.readers.length; r++) {
717
+ if (builder.readers[r].configName === 'x') {
718
+ builder.readers[r].configName = 'name';
719
+ }
715
720
  }
716
721
  }
717
722
  }
718
- }
719
723
 
720
- // Read data for all builders
721
- for (seriesIndex = 0; seriesIndex < allSeriesBuilders.length; seriesIndex++) {
722
- builder = allSeriesBuilders[seriesIndex];
724
+ // Read data for all builders
725
+ for (seriesIndex = 0; seriesIndex < allSeriesBuilders.length; seriesIndex++) {
726
+ builder = allSeriesBuilders[seriesIndex];
723
727
 
724
- // Iterate down the cells of each column and add data to the series
725
- data = [];
726
- for (j = 0; j < columns[0].length; j++) { // TODO: which column's length should we use here
727
- data[j] = builder.read(columns, j);
728
- }
728
+ // Iterate down the cells of each column and add data to the series
729
+ data = [];
730
+ for (j = 0; j < columns[0].length; j++) {
731
+ data[j] = builder.read(columns, j);
732
+ }
729
733
 
730
- // Add the series
731
- series[seriesIndex] = {
732
- data: data
733
- };
734
- if (builder.name) {
735
- series[seriesIndex].name = builder.name;
736
- }
737
- if (type === 'category') {
738
- series[seriesIndex].turboThreshold = 0;
734
+ // Add the series
735
+ series[seriesIndex] = {
736
+ data: data
737
+ };
738
+ if (builder.name) {
739
+ series[seriesIndex].name = builder.name;
740
+ }
741
+ if (type === 'category') {
742
+ series[seriesIndex].turboThreshold = 0;
743
+ }
739
744
  }
740
- }
741
745
 
742
746
 
743
747
 
744
- // Do the callback
745
- chartOptions = {
746
- series: series
747
- };
748
- if (type) {
749
- chartOptions.xAxis = {
750
- type: type
748
+ // Do the callback
749
+ chartOptions = {
750
+ series: series
751
751
  };
752
- }
753
-
754
- if (options.complete) {
755
- options.complete(chartOptions);
756
- }
752
+ if (type) {
753
+ chartOptions.xAxis = {
754
+ type: type
755
+ };
756
+ }
757
+
758
+ if (options.complete) {
759
+ options.complete(chartOptions);
760
+ }
757
761
 
758
- // The afterComplete hook is used internally to avoid conflict with the externally
759
- // available complete option.
760
- if (options.afterComplete) {
761
- options.afterComplete(chartOptions);
762
+ // The afterComplete hook is used internally to avoid conflict with the externally
763
+ // available complete option.
764
+ if (options.afterComplete) {
765
+ options.afterComplete(chartOptions);
766
+ }
762
767
  }
763
768
  }
764
- }
765
769
  });
766
770
 
767
771
  // Register the Data prototype and data function on Highcharts
@@ -950,4 +954,4 @@
950
954
 
951
955
 
952
956
 
953
- }(Highcharts));
957
+ }));