highcharts-rails 4.0.4.1 → 4.1.0

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.
@@ -2908,7 +2908,7 @@ if (CanvasRenderingContext2D) {
2908
2908
  });
2909
2909
  }
2910
2910
  }/**
2911
- * @license Highcharts JS v4.0.4 (2014-09-02)
2911
+ * @license Highcharts JS v4.1.0 (2015-02-16)
2912
2912
  * CanVGRenderer Extension module
2913
2913
  *
2914
2914
  * (c) 2011-2012 Torstein Honsi, Erik Olsson
@@ -1,113 +1,20 @@
1
1
  /**
2
- * @license Data plugin for Highcharts
2
+ * @license Highcharts JS v4.1.0 (2015-02-16)
3
+ * Data module
3
4
  *
4
5
  * (c) 2012-2014 Torstein Honsi
5
6
  *
6
7
  * License: www.highcharts.com/license
7
8
  */
8
9
 
9
- /*
10
- * The Highcharts Data plugin is a utility to ease parsing of input sources like
11
- * CSV, HTML tables or grid views into basic configuration options for use
12
- * directly in the Highcharts constructor.
13
- *
14
- * Demo: http://jsfiddle.net/highcharts/SnLFj/
15
- *
16
- * --- OPTIONS ---
17
- *
18
- * - columns : Array<Array<Mixed>>
19
- * A two-dimensional array representing the input data on tabular form. This input can
20
- * be used when the data is already parsed, for example from a grid view component.
21
- * Each cell can be a string or number. If not switchRowsAndColumns is set, the columns
22
- * are interpreted as series. See also the rows option.
23
- *
24
- * - complete : Function(chartOptions)
25
- * The callback that is evaluated when the data is finished loading, optionally from an
26
- * external source, and parsed. The first argument passed is a finished chart options
27
- * object, containing the series. Thise options
28
- * can be extended with additional options and passed directly to the chart constructor. This is
29
- * related to the parsed callback, that goes in at an earlier stage.
30
- *
31
- * - csv : String
32
- * A comma delimited string to be parsed. Related options are startRow, endRow, startColumn
33
- * and endColumn to delimit what part of the table is used. The lineDelimiter and
34
- * itemDelimiter options define the CSV delimiter formats.
35
- *
36
- * - dateFormat: String
37
- * Which of the predefined date formats in Date.prototype.dateFormats to use to parse date
38
- * columns, for example "dd/mm/YYYY" or "YYYY-mm-dd". Defaults to a best guess based on
39
- * what format gives valid dates, and prefers ordered dates.
40
- *
41
- * - endColumn : Integer
42
- * In tabular input data, the first row (indexed by 0) to use. Defaults to the last
43
- * column containing data.
44
- *
45
- * - endRow : Integer
46
- * In tabular input data, the last row (indexed by 0) to use. Defaults to the last row
47
- * containing data.
48
- *
49
- * - googleSpreadsheetKey : String
50
- * A Google Spreadsheet key. See https://developers.google.com/gdata/samples/spreadsheet_sample
51
- * for general information on GS.
52
- *
53
- * - googleSpreadsheetWorksheet : String
54
- * The Google Spreadsheet worksheet. The available id's can be read from
55
- * https://spreadsheets.google.com/feeds/worksheets/{key}/public/basic
56
- *
57
- * - itemDelimiter : String
58
- * Item or cell delimiter for parsing CSV. Defaults to the tab character "\t" if a tab character
59
- * is found in the CSV string, if not it defaults to ",".
60
- *
61
- * - lineDelimiter : String
62
- * Line delimiter for parsing CSV. Defaults to "\n".
63
- *
64
- * - parsed : Function
65
- * A callback function to access the parsed columns, the two-dimentional input data
66
- * array directly, before they are interpreted into series data and categories. See also
67
- * the complete callback, that goes in on a later stage where the raw columns are interpreted
68
- * into a Highcharts option structure. Return false to stop completion, or call this.complete()
69
- * to continue async.
70
- *
71
- * - parseDate : Function
72
- * A callback function to parse string representations of dates into JavaScript timestamps.
73
- * Return an integer on success.
74
- *
75
- * - rows : Array<Array<Mixed>>
76
- * The same as the columns input option, but defining rows intead of columns.
77
- *
78
- * - seriesMapping : Array<Object>
79
- * An array containing object with Point property names along with what column id the
80
- * property should be taken from.
81
- *
82
- * - startColumn : Integer
83
- * In tabular input data, the first column (indexed by 0) to use.
84
- *
85
- * - startRow : Integer
86
- * In tabular input data, the first row (indexed by 0) to use.
87
- *
88
- * - switchRowsAndColumns : Boolean
89
- * Switch rows and columns of the input data, so that this.columns effectively becomes the
90
- * rows of the data set, and the rows are interpreted as series.
91
- *
92
- * - table : String|HTMLElement
93
- * A HTML table or the id of such to be parsed as input data. Related options ara startRow,
94
- * endRow, startColumn and endColumn to delimit what part of the table is used.
95
- */
96
-
97
- /*
98
- * TODO:
99
- * - Handle various date formats
100
- * - http://jsfiddle.net/highcharts/114wejdx/
101
- * - http://jsfiddle.net/highcharts/ryv67bkq/
102
- */
103
-
104
10
  // JSLint options:
105
11
  /*global jQuery, HighchartsAdapter */
106
12
 
107
- (function (Highcharts) { // docs
13
+ (function (Highcharts) {
108
14
 
109
15
  // Utilities
110
16
  var each = Highcharts.each,
17
+ pick = Highcharts.pick,
111
18
  inArray = HighchartsAdapter.inArray,
112
19
  splat = Highcharts.splat,
113
20
  SeriesBuilder;
@@ -128,6 +35,8 @@
128
35
  this.options = options;
129
36
  this.chartOptions = chartOptions;
130
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]+)$');
131
40
 
132
41
  // This is a two-dimensional array holding the raw, trimmed string values
133
42
  // with the same organisation as the columns array. It makes it possible
@@ -172,7 +81,7 @@
172
81
  globalType = chartOptions && chartOptions.chart && chartOptions.chart.type,
173
82
  individualCounts = [],
174
83
  seriesBuilders = [],
175
- seriesIndex,
84
+ seriesIndex = 0,
176
85
  i;
177
86
 
178
87
  each((chartOptions && chartOptions.series) || [], function (series) {
@@ -253,9 +162,6 @@
253
162
  // Interpret the values into right types
254
163
  this.parseTypes();
255
164
 
256
- // Use first row for series names?
257
- this.findHeaderRow();
258
-
259
165
  // Handle columns if a handleColumns callback is given
260
166
  if (this.parsed() !== false) {
261
167
 
@@ -420,26 +326,23 @@
420
326
  }
421
327
  },
422
328
 
423
- /**
424
- * Find the header row. For now, we just check whether the first row contains
425
- * numbers or strings. Later we could loop down and find the first row with
426
- * numbers.
427
- */
428
- findHeaderRow: function () {
429
- var headerRow = 0;
430
- each(this.columns, function (column) {
431
- if (column.isNumeric && typeof column[0] !== 'string') {
432
- headerRow = null;
433
- }
434
- });
435
- this.headerRow = headerRow;
436
- },
437
-
438
329
  /**
439
330
  * Trim a string from whitespace
440
331
  */
441
- trim: function (str) {
442
- return typeof str === 'string' ? str.replace(/^\s+|\s+$/g, '') : str;
332
+ trim: function (str, inside) {
333
+ if (typeof str === 'string') {
334
+ str = str.replace(/^\s+|\s+$/g, '');
335
+
336
+ // Clear white space insdie the string, like thousands separators
337
+ if (inside && /^[0-9\s]+$/.test(str)) {
338
+ str = str.replace(/\s/g, '');
339
+ }
340
+
341
+ if (this.decimalRegex) {
342
+ str = str.replace(this.decimalRegex, '$1.$2');
343
+ }
344
+ }
345
+ return str;
443
346
  },
444
347
 
445
348
  /**
@@ -447,97 +350,118 @@
447
350
  */
448
351
  parseTypes: function () {
449
352
  var columns = this.columns,
450
- rawColumns = this.rawColumns,
451
- col = columns.length,
452
- row,
353
+ col = columns.length;
354
+
355
+ while (col--) {
356
+ this.parseColumn(columns[col], col);
357
+ }
358
+
359
+ },
360
+
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,
453
368
  val,
454
369
  floatVal,
455
370
  trimVal,
456
- isXColumn,
371
+ trimInsideVal,
372
+ firstRowAsNames = this.firstRowAsNames,
373
+ isXColumn = inArray(col, this.valueCount.xColumns) !== -1,
457
374
  dateVal,
458
- descending,
459
375
  backup = [],
460
376
  diff,
461
- hasHeaderRow,
462
- forceCategory,
463
- chartOptions = this.chartOptions;
464
-
465
- while (col--) {
466
- row = columns[col].length;
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]) {
467
384
  rawColumns[col] = [];
468
- isXColumn = inArray(col, this.valueCount.xColumns) !== -1;
469
- forceCategory = isXColumn && chartOptions && chartOptions.xAxis && splat(chartOptions.xAxis)[0].type === 'category';
470
- while (row--) {
471
- val = backup[row] || columns[col][row];
472
- floatVal = parseFloat(val);
473
- trimVal = rawColumns[col][row] = this.trim(val);
474
-
475
- // Disable number or date parsing by setting the X axis type to category
476
- if (forceCategory) {
477
- columns[col][row] = trimVal;
478
-
479
- /*jslint eqeq: true*/
480
- } else if (trimVal == floatVal) { // is numeric
481
- /*jslint eqeq: false*/
482
- columns[col][row] = floatVal;
483
-
484
- // If the number is greater than milliseconds in a year, assume datetime
485
- if (floatVal > 365 * 24 * 3600 * 1000) {
486
- columns[col].isDatetime = true;
487
- } else {
488
- columns[col].isNumeric = true;
489
- }
385
+ }
386
+ while (row--) {
387
+ val = backup[row] || column[row];
388
+
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;
396
+ }
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;
490
405
 
491
- } else { // string, continue to determine if it is a date string or really a string
492
- dateVal = this.parseDate(val);
493
- // Only allow parsing of dates if this column is an x-column
494
- if (isXColumn && typeof dateVal === 'number' && !isNaN(dateVal)) { // is date
495
- backup[row] = val;
496
- columns[col][row] = dateVal;
497
- columns[col].isDatetime = true;
498
-
499
- // Check if the dates are uniformly descending or ascending. If they
500
- // are not, chances are that they are a different time format, so check
501
- // for alternative.
502
- if (columns[col][row + 1] !== undefined) {
503
- diff = dateVal > columns[col][row + 1];
504
- if (diff !== descending && descending !== undefined) {
505
- if (this.alternativeFormat) {
506
- this.dateFormat = this.alternativeFormat;
507
- row = columns[col].length;
508
- this.alternativeFormat = this.dateFormats[this.dateFormat].alternative;
509
- } else {
510
- columns[col].unsorted = true;
511
- }
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
+ }
412
+
413
+ if (column[row + 1] !== undefined) {
414
+ descending = floatVal > column[row + 1];
415
+ }
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.
429
+ 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;
512
438
  }
513
- descending = diff;
514
- }
515
-
516
- } else { // string
517
- columns[col][row] = trimVal === '' ? null : trimVal;
518
- if (row !== 0 && (columns[col].isDatetime || columns[col].isNumeric)) {
519
- columns[col].mixed = true;
520
439
  }
440
+ descending = diff;
441
+ }
442
+
443
+ } else { // string
444
+ column[row] = trimVal === '' ? null : trimVal;
445
+ if (row !== 0 && (column.isDatetime || column.isNumeric)) {
446
+ column.mixed = true;
521
447
  }
522
448
  }
523
449
  }
450
+ }
524
451
 
525
- // If strings are intermixed with numbers or dates in a parsed column, it is an indication
526
- // that parsing went wrong or the data was not intended to display as numbers or dates and
527
- // parsing is too aggressive. Fall back to categories. Demonstrated in the
528
- // highcharts/demo/column-drilldown sample.
529
- if (isXColumn && columns[col].mixed) {
530
- columns[col] = rawColumns[col];
531
- }
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];
532
458
  }
533
459
 
534
- // If the 0 column is date and descending, reverse all columns.
535
- // TODO: probably this should apply to xColumns, not 0 column alone.
536
- if (columns[0].isDatetime && descending) {
537
- hasHeaderRow = typeof columns[0][0] !== 'number';
460
+ // If the 0 column is date or number and descending, reverse all columns.
461
+ if (isXColumn && descending && this.options.sort) {
538
462
  for (col = 0; col < columns.length; col++) {
539
463
  columns[col].reverse();
540
- if (hasHeaderRow) {
464
+ if (firstRowAsNames) {
541
465
  columns[col].unshift(columns[col].pop());
542
466
  }
543
467
  }
@@ -578,7 +502,6 @@
578
502
  'mm/dd/YY': {
579
503
  regex: /^([0-9]{1,2})[\-\/\.]([0-9]{1,2})[\-\/\.]([0-9]{2})$/,
580
504
  parser: function (match) {
581
- console.log(match)
582
505
  return Date.UTC(+match[3] + 2000, match[1] - 1, +match[2]);
583
506
  }
584
507
  }
@@ -731,7 +654,7 @@
731
654
 
732
655
  // Get the names and shift the top row
733
656
  for (i = 0; i < columns.length; i++) {
734
- if (this.headerRow === 0) {
657
+ if (this.firstRowAsNames) {
735
658
  columns[i].name = columns[i].shift();
736
659
  }
737
660
  }
@@ -812,17 +735,23 @@
812
735
  if (builder.name) {
813
736
  series[seriesIndex].name = builder.name;
814
737
  }
738
+ if (type === 'category') {
739
+ series[seriesIndex].turboThreshold = 0;
740
+ }
815
741
  }
816
742
 
817
743
 
818
744
 
819
745
  // Do the callback
820
746
  chartOptions = {
821
- xAxis: {
822
- type: type
823
- },
824
747
  series: series
825
748
  };
749
+ if (type) {
750
+ chartOptions.xAxis = {
751
+ type: type
752
+ };
753
+ }
754
+
826
755
  if (options.complete) {
827
756
  options.complete(chartOptions);
828
757
  }
@@ -7,7 +7,7 @@
7
7
  * Demo: http://jsfiddle.net/highcharts/Vf3yT/
8
8
  */
9
9
 
10
- /*global HighchartsAdapter*/
10
+ /*global Highcharts,HighchartsAdapter*/
11
11
  (function (H) {
12
12
 
13
13
  "use strict";
@@ -25,18 +25,39 @@
25
25
  ColumnSeries = seriesTypes.column,
26
26
  fireEvent = HighchartsAdapter.fireEvent,
27
27
  inArray = HighchartsAdapter.inArray,
28
- dupes = [];
28
+ dupes = [],
29
+ ddSeriesId = 1;
29
30
 
30
31
  // Utilities
31
- function tweenColors(startColor, endColor, pos) {
32
- var rgba = [
33
- Math.round(startColor[0] + (endColor[0] - startColor[0]) * pos),
34
- Math.round(startColor[1] + (endColor[1] - startColor[1]) * pos),
35
- Math.round(startColor[2] + (endColor[2] - startColor[2]) * pos),
36
- startColor[3] + (endColor[3] - startColor[3]) * pos
37
- ];
38
- return 'rgba(' + rgba.join(',') + ')';
32
+ /*
33
+ * Return an intermediate color between two colors, according to pos where 0
34
+ * is the from color and 1 is the to color
35
+ */
36
+ function tweenColors(from, to, pos) {
37
+ // Check for has alpha, because rgba colors perform worse due to lack of
38
+ // support in WebKit.
39
+ var hasAlpha;
40
+
41
+ from = from.rgba;
42
+ to = to.rgba;
43
+ hasAlpha = (to[3] !== 1 || from[3] !== 1);
44
+ if (!to.length || !from.length) {
45
+ Highcharts.error(23);
46
+ }
47
+ return (hasAlpha ? 'rgba(' : 'rgb(') +
48
+ Math.round(to[0] + (from[0] - to[0]) * (1 - pos)) + ',' +
49
+ Math.round(to[1] + (from[1] - to[1]) * (1 - pos)) + ',' +
50
+ Math.round(to[2] + (from[2] - to[2]) * (1 - pos)) +
51
+ (hasAlpha ? (',' + (to[3] + (from[3] - to[3]) * (1 - pos))) : '') + ')';
39
52
  }
53
+ /**
54
+ * Handle animation of the color attributes directly
55
+ */
56
+ each(['fill', 'stroke'], function (prop) {
57
+ HighchartsAdapter.addAnimSetter(prop, function (fx) {
58
+ fx.elem.attr(prop, tweenColors(H.Color(fx.start), H.Color(fx.end), fx.pos));
59
+ });
60
+ });
40
61
 
41
62
  // Add language
42
63
  extend(defaultOptions.lang, {
@@ -101,10 +122,11 @@
101
122
  level,
102
123
  levelNumber;
103
124
 
104
- levelNumber = oldSeries.levelNumber || 0;
125
+ levelNumber = oldSeries.options._levelNumber || 0;
105
126
 
106
127
  ddOptions = extend({
107
- color: color
128
+ color: color,
129
+ _ddSeriesId: ddSeriesId++
108
130
  }, ddOptions);
109
131
  pointIndex = inArray(point, oldSeries.points);
110
132
 
@@ -112,19 +134,21 @@
112
134
  each(oldSeries.chart.series, function (series) {
113
135
  if (series.xAxis === xAxis) {
114
136
  levelSeries.push(series);
115
- levelSeriesOptions.push(series.userOptions);
116
- series.levelNumber = series.levelNumber || levelNumber; // #3182
137
+ series.options._ddSeriesId = series.options._ddSeriesId || ddSeriesId++;
138
+ series.options._colorIndex = series.userOptions._colorIndex;
139
+ levelSeriesOptions.push(series.options);
140
+ series.options._levelNumber = series.options._levelNumber || levelNumber; // #3182
117
141
  }
118
142
  });
119
143
 
120
144
  // Add a record of properties for each drilldown level
121
145
  level = {
122
146
  levelNumber: levelNumber,
123
- seriesOptions: oldSeries.userOptions,
147
+ seriesOptions: oldSeries.options,
124
148
  levelSeriesOptions: levelSeriesOptions,
125
149
  levelSeries: levelSeries,
126
150
  shapeArgs: point.shapeArgs,
127
- bBox: point.graphic.getBBox(),
151
+ bBox: point.graphic ? point.graphic.getBBox() : {}, // no graphic in line series with markers disabled
128
152
  color: color,
129
153
  lowerSeriesOptions: ddOptions,
130
154
  pointOptions: oldSeries.options.data[pointIndex],
@@ -144,7 +168,7 @@
144
168
  this.drilldownLevels.push(level);
145
169
 
146
170
  newSeries = level.lowerSeries = this.addSeries(ddOptions, false);
147
- newSeries.levelNumber = levelNumber + 1;
171
+ newSeries.options._levelNumber = levelNumber + 1;
148
172
  if (xAxis) {
149
173
  xAxis.oldPos = xAxis.pos;
150
174
  xAxis.userMin = xAxis.userMax = null;
@@ -167,7 +191,7 @@
167
191
  each(this.drilldownLevels, function (level) {
168
192
  if (level.levelNumber === levelToRemove) {
169
193
  each(level.levelSeries, function (series) {
170
- if (series.levelNumber === levelToRemove) { // Not removed, not added as part of a multi-series drilldown
194
+ if (series.options && series.options._levelNumber === levelToRemove) { // Not removed, not added as part of a multi-series drilldown
171
195
  series.remove(false);
172
196
  }
173
197
  });
@@ -233,7 +257,7 @@
233
257
  levelNumber = drilldownLevels[drilldownLevels.length - 1].levelNumber,
234
258
  i = drilldownLevels.length,
235
259
  chartSeries = chart.series,
236
- seriesI = chartSeries.length,
260
+ seriesI,
237
261
  level,
238
262
  oldSeries,
239
263
  newSeries,
@@ -241,7 +265,7 @@
241
265
  addSeries = function (seriesOptions) {
242
266
  var addedSeries;
243
267
  each(chartSeries, function (series) {
244
- if (series.userOptions === seriesOptions) {
268
+ if (series.options._ddSeriesId === seriesOptions._ddSeriesId) {
245
269
  addedSeries = series;
246
270
  }
247
271
  });
@@ -254,7 +278,7 @@
254
278
  newSeries = addedSeries;
255
279
  }
256
280
  };
257
-
281
+
258
282
  while (i--) {
259
283
 
260
284
  level = drilldownLevels[i];
@@ -264,6 +288,7 @@
264
288
  // Get the lower series by reference or id
265
289
  oldSeries = level.lowerSeries;
266
290
  if (!oldSeries.chart) { // #2786
291
+ seriesI = chartSeries.length; // #2919
267
292
  while (seriesI--) {
268
293
  if (chartSeries[seriesI].options.id === level.lowerSeriesOptions.id) {
269
294
  oldSeries = chartSeries[seriesI];
@@ -281,11 +306,11 @@
281
306
  newSeries.drilldownLevel = level;
282
307
  newSeries.options.animation = chart.options.drilldown.animation;
283
308
 
284
- if (oldSeries.animateDrillupFrom) {
309
+ if (oldSeries.animateDrillupFrom && oldSeries.chart) { // #2919
285
310
  oldSeries.animateDrillupFrom(level);
286
311
  }
287
312
  }
288
- newSeries.levelNumber = levelNumber;
313
+ newSeries.options._levelNumber = levelNumber;
289
314
 
290
315
  oldSeries.remove(false);
291
316
 
@@ -325,7 +350,9 @@
325
350
  level = newSeries.drilldownLevel;
326
351
 
327
352
  each(this.points, function (point) {
328
- point.graphic.hide();
353
+ if (point.graphic) { // #3407
354
+ point.graphic.hide();
355
+ }
329
356
  if (point.dataLabel) {
330
357
  point.dataLabel.hide();
331
358
  }
@@ -337,18 +364,22 @@
337
364
 
338
365
  // Do dummy animation on first point to get to complete
339
366
  setTimeout(function () {
340
- each(newSeries.points, function (point, i) {
341
- // Fade in other points
342
- var verb = i === (level && level.pointIndex) ? 'show' : 'fadeIn',
343
- inherit = verb === 'show' ? true : undefined;
344
- point.graphic[verb](inherit);
345
- if (point.dataLabel) {
346
- point.dataLabel[verb](inherit);
347
- }
348
- if (point.connector) {
349
- point.connector[verb](inherit);
350
- }
351
- });
367
+ if (newSeries.points) { // May be destroyed in the meantime, #3389
368
+ each(newSeries.points, function (point, i) {
369
+ // Fade in other points
370
+ var verb = i === (level && level.pointIndex) ? 'show' : 'fadeIn',
371
+ inherit = verb === 'show' ? true : undefined;
372
+ if (point.graphic) { // #3407
373
+ point.graphic[verb](inherit);
374
+ }
375
+ if (point.dataLabel) {
376
+ point.dataLabel[verb](inherit);
377
+ }
378
+ if (point.connector) {
379
+ point.connector[verb](inherit);
380
+ }
381
+ });
382
+ }
352
383
  }, Math.max(this.chart.options.drilldown.animation.duration - 50, 0));
353
384
 
354
385
  // Reset
@@ -360,23 +391,28 @@
360
391
  ColumnSeries.prototype.animateDrilldown = function (init) {
361
392
  var series = this,
362
393
  drilldownLevels = this.chart.drilldownLevels,
363
- animateFrom = this.chart.drilldownLevels[this.chart.drilldownLevels.length - 1].shapeArgs,
364
- animationOptions = this.chart.options.drilldown.animation;
394
+ animateFrom,
395
+ animationOptions = this.chart.options.drilldown.animation,
396
+ xAxis = this.xAxis;
365
397
 
366
398
  if (!init) {
367
399
  each(drilldownLevels, function (level) {
368
- if (series.userOptions === level.lowerSeriesOptions) {
400
+ if (series.options._ddSeriesId === level.lowerSeriesOptions._ddSeriesId) {
369
401
  animateFrom = level.shapeArgs;
402
+ animateFrom.fill = level.color;
370
403
  }
371
404
  });
372
405
 
373
- animateFrom.x += (this.xAxis.oldPos - this.xAxis.pos);
374
-
406
+ animateFrom.x += (pick(xAxis.oldPos, xAxis.pos) - xAxis.pos);
407
+
375
408
  each(this.points, function (point) {
376
409
  if (point.graphic) {
377
410
  point.graphic
378
411
  .attr(animateFrom)
379
- .animate(point.shapeArgs, animationOptions);
412
+ .animate(
413
+ extend(point.shapeArgs, { fill: point.color }),
414
+ animationOptions
415
+ );
380
416
  }
381
417
  if (point.dataLabel) {
382
418
  point.dataLabel.fadeIn(animationOptions);
@@ -407,8 +443,6 @@
407
443
  delete this.group;
408
444
  each(this.points, function (point) {
409
445
  var graphic = point.graphic,
410
- startColor = H.Color(point.color).rgba,
411
- endColor = H.Color(level.color).rgba,
412
446
  complete = function () {
413
447
  graphic.destroy();
414
448
  if (group) {
@@ -421,18 +455,10 @@
421
455
  delete point.graphic;
422
456
 
423
457
  if (animationOptions) {
424
- /*jslint unparam: true*/
425
- graphic.animate(level.shapeArgs, H.merge(animationOptions, {
426
- step: function (val, fx) {
427
- if (fx.prop === 'start' && startColor.length === 4 && endColor.length === 4) {
428
- this.attr({
429
- fill: tweenColors(startColor, endColor, fx.pos)
430
- });
431
- }
432
- },
433
- complete: complete
434
- }));
435
- /*jslint unparam: false*/
458
+ graphic.animate(
459
+ extend(level.shapeArgs, { fill: level.color }),
460
+ H.merge(animationOptions, { complete: complete })
461
+ );
436
462
  } else {
437
463
  graphic.attr(level.shapeArgs);
438
464
  complete();
@@ -453,28 +479,19 @@
453
479
  animateFrom = level.shapeArgs,
454
480
  start = animateFrom.start,
455
481
  angle = animateFrom.end - start,
456
- startAngle = angle / this.points.length,
457
- startColor = H.Color(level.color).rgba;
482
+ startAngle = angle / this.points.length;
458
483
 
459
484
  if (!init) {
460
485
  each(this.points, function (point, i) {
461
- var endColor = H.Color(point.color).rgba;
462
-
463
- /*jslint unparam: true*/
464
486
  point.graphic
465
487
  .attr(H.merge(animateFrom, {
466
488
  start: start + i * startAngle,
467
- end: start + (i + 1) * startAngle
468
- }))[animationOptions ? 'animate' : 'attr'](point.shapeArgs, H.merge(animationOptions, {
469
- step: function (val, fx) {
470
- if (fx.prop === 'start' && startColor.length === 4 && endColor.length === 4) {
471
- this.attr({
472
- fill: tweenColors(startColor, endColor, fx.pos)
473
- });
474
- }
475
- }
476
- }));
477
- /*jslint unparam: false*/
489
+ end: start + (i + 1) * startAngle,
490
+ fill: level.color
491
+ }))[animationOptions ? 'animate' : 'attr'](
492
+ extend(point.shapeArgs, { fill: point.color }),
493
+ animationOptions
494
+ );
478
495
  });
479
496
  this.animate = null;
480
497
  }
@@ -482,7 +499,7 @@
482
499
  });
483
500
  }
484
501
 
485
- H.Point.prototype.doDrilldown = function (_holdRedraw) {
502
+ H.Point.prototype.doDrilldown = function (_holdRedraw, category) {
486
503
  var series = this.series,
487
504
  chart = series.chart,
488
505
  drilldown = chart.options.drilldown,
@@ -500,7 +517,8 @@
500
517
  // seriesOptions, and call addSeriesAsDrilldown async if necessary.
501
518
  fireEvent(chart, 'drilldown', {
502
519
  point: this,
503
- seriesOptions: seriesOptions
520
+ seriesOptions: seriesOptions,
521
+ category: category
504
522
  });
505
523
 
506
524
  if (seriesOptions) {
@@ -510,7 +528,18 @@
510
528
  chart.addSeriesAsDrilldown(this, seriesOptions);
511
529
  }
512
530
  }
531
+ };
513
532
 
533
+ /**
534
+ * Drill down to a given category. This is the same as clicking on an axis label.
535
+ */
536
+ H.Axis.prototype.drilldownCategory = function (x) {
537
+ each(this.ticks[x].label.ddPoints, function (point) {
538
+ if (point.series && point.series.visible && point.doDrilldown) { // #3197
539
+ point.doDrilldown(true, x);
540
+ }
541
+ });
542
+ this.chart.applyDrilldown();
514
543
  };
515
544
 
516
545
  wrap(H.Point.prototype, 'init', function (proceed, series, options, x) {
@@ -543,12 +572,7 @@
543
572
  .addClass('highcharts-drilldown-axis-label')
544
573
  .css(chart.options.drilldown.activeAxisLabelStyle)
545
574
  .on('click', function () {
546
- each(tickLabel.ddPoints, function (point) {
547
- if (point.doDrilldown) {
548
- point.doDrilldown(true);
549
- }
550
- });
551
- chart.applyDrilldown();
575
+ series.xAxis.drilldownCategory(x);
552
576
  });
553
577
  if (!tickLabel.ddPoints) {
554
578
  tickLabel.ddPoints = [];