highcharts-rails 4.0.4.1 → 4.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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 = [];