highcharts-rails 5.0.6 → 5.0.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (25) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.markdown +54 -0
  3. data/app/assets/javascripts/highcharts.js +542 -324
  4. data/app/assets/javascripts/highcharts/highcharts-3d.js +12 -2
  5. data/app/assets/javascripts/highcharts/highcharts-more.js +41 -47
  6. data/app/assets/javascripts/highcharts/modules/accessibility.js +20 -4
  7. data/app/assets/javascripts/highcharts/modules/annotations.js +1 -1
  8. data/app/assets/javascripts/highcharts/modules/boost.js +9 -3
  9. data/app/assets/javascripts/highcharts/modules/broken-axis.js +11 -13
  10. data/app/assets/javascripts/highcharts/modules/data.js +1 -1
  11. data/app/assets/javascripts/highcharts/modules/drilldown.js +28 -13
  12. data/app/assets/javascripts/highcharts/modules/exporting.js +1 -1
  13. data/app/assets/javascripts/highcharts/modules/funnel.js +1 -1
  14. data/app/assets/javascripts/highcharts/modules/grid-axis.js +1 -1
  15. data/app/assets/javascripts/highcharts/modules/heatmap.js +12 -7
  16. data/app/assets/javascripts/highcharts/modules/no-data-to-display.js +1 -1
  17. data/app/assets/javascripts/highcharts/modules/offline-exporting.js +71 -18
  18. data/app/assets/javascripts/highcharts/modules/overlapping-datalabels.js +1 -1
  19. data/app/assets/javascripts/highcharts/modules/series-label.js +1 -1
  20. data/app/assets/javascripts/highcharts/modules/solid-gauge.js +1 -1
  21. data/app/assets/javascripts/highcharts/modules/stock.js +6360 -0
  22. data/app/assets/javascripts/highcharts/modules/treemap.js +88 -90
  23. data/app/assets/javascripts/highcharts/modules/xrange-series.js +5 -1
  24. data/lib/highcharts/version.rb +1 -1
  25. metadata +2 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d5cdeffa8d95f333a687263dfcf02665e9de7eb6
4
- data.tar.gz: 4e1dc3bed55b4ca55e4397f58d789e1259bf3dee
3
+ metadata.gz: 607c053da0b9fc8e51b7392df545aa6e5d297a34
4
+ data.tar.gz: 2e1f5f66fe81be42f4a96afdf21ca8ad5bc12697
5
5
  SHA512:
6
- metadata.gz: bdbdf6c8ebf4f3f5c9869d2092ed2106b6134a19927b5c603236a171d559d1affe3497b047af6f3671a628636e32d3f00e2991eb8d9b03dcdbc885c0871cbae7
7
- data.tar.gz: eb1275d9eba5ec4e50416f92575aa4ae092c1ab055bfcc06fea46c4c38cc453fe2703a3efb6f531489477981df1822eb8cacd44da6f2bfb00972d65e1d9a024c
6
+ metadata.gz: abfc4475e26229dca060b337a77bf1564bfb55c66ed2d13ce8e2d156ec81811977117f3fe623a5c4bf147552a7653426c96e4e607be266ba8413e60e0af62e6c
7
+ data.tar.gz: a8be44f809d4987373a16f6a2383667f066ab4c9c82b6862922e5da10ba7955ca5c1e2a3bdc6482b32c7e9cf176e970dd5b6908810ff75b5e37977b283512885
@@ -1,3 +1,57 @@
1
+ # 5.0.7 / 2017-01-25
2
+
3
+ * Updated Highcharts to 5.0.7 (2017-01-17)
4
+ * Added Legend keyboard navigation to accessibility module.
5
+ * Added chart render and predraw events needed by the new Boost module.
6
+ * Added new option, global.timezone, as a convenient shortcut to timezones defined with moment.js.
7
+ * Added optional redraw to drillToNode, related to #6180.
8
+ * Added support for marker.symbol setting on bubble charts.
9
+ * Changed the Highcharts.addEvent function to return a callback to be used to remove the same event.
10
+ * Changed the Highcharts.error function to handle strings.
11
+ * Bug fixes
12
+ * Fixed compliance with Checkmarx security checker.
13
+ * Fixed #6108, issue with big data in offline exporting.
14
+ * Fixed #5672, X values in tooltip in Boost module was wrong.
15
+ * Fixed #5553, disabled trackByArea did not hide tooltip, when moving mouse out of the area shape.
16
+ * Fixed #5757, empty chart throws error with accessibility module.
17
+ * Fixed #5766, halo was not rendered for shared tooltip when density of points was high.
18
+ * Fixed #5818, render halo in a series group, not under all of the groups.
19
+ * Fixed #5819, crosshair width can now be set on category axes also.
20
+ * Fixed #5833, split tooltip tried to remove series tooltip again after destruction.
21
+ * Fixed #5835, exported SVG didn't validate, width and height attributes were set on group elements.
22
+ * Fixed #5837, could not style null points with CSS.
23
+ * Fixed #5855, split tooltip was not destroyed properly.
24
+ * Fixed #5862, wrong hover point, when tooltip was shared.
25
+ * Fixed #5866, treemap point.isNull was always true.
26
+ * Fixed #5868, dataLabels.softConnector was always set to true.
27
+ * Fixed #5873, bubbles were hidden in small charts when maxSize was percentage.
28
+ * Fixed #5884, optimizing addSeries.
29
+ * Fixed #6085, floating point errors on Y axis labels.
30
+ * Fixed #6088, rounding issue in tooltips.
31
+ * Fixed #6107, error with negative width in xrange study.
32
+ * Fixed #6112, issue and regression with swapping series indexes.
33
+ * Fixed #6113, long legend items were unresponsive due to heavy HTML parsing in SVG. Implemented caching.
34
+ * Fixed #6127, pie chart data label overlapped after drill up.
35
+ * Fixed #6128, tooltip caret was not drawn after updating from shared to non-shared.
36
+ * Fixed #6130, Chart.update didn't trigger responsive rules to be re-evaluated.
37
+ * Fixed #6138, reset colorCounter and symbolCounter when all series are removed.
38
+ * Fixed #6147, error in Chart.get when called between redraws.
39
+ * Fixed #6157, offline exporting was not working for Edge.
40
+ * Fixed #6158, duplicate ID of navigator series.
41
+ * Fixed #6171, drawDataLabels did not use updated options when data label already existed.
42
+ * Fixed #6173, no inline CSS should be allowed in styled mode.
43
+ * Fixed #6180, treemap crashed when the root node did not exist.
44
+ * Fixed #6184, polar arearange was misshaped when connectEnds was not set.
45
+ * Fixed #6196, dead clip path references.
46
+ * Fixed #6200, modified treemap setPointValues to only add crisping correction when needed.
47
+ * Fixed #6202, legend icons overflowed legend with large radius.
48
+ * Fixed #6207, when point X was given, point names did not show on category axis after hiding and showing series.
49
+ * Fixed #6208, error on responsive rules for plotOptions.series and series.xAxis.
50
+ * Fixed #6213, wrong text bounding box reported in Chrome for Windows, resulting in asymmetric label padding.
51
+ * Fixed #6217, container sizes below 20px height needed explicit chart height to be set.
52
+ * Fixed issue in offline exporting where libURL option was not picked up.
53
+ * Fixed issue with duplicate highcharts-negative class. Ref #6114.
54
+
1
55
  # 5.0.6 / 2017-01-25
2
56
 
3
57
  * Updated Highcharts to 5.0.6 (2016-12-07)
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @license Highcharts JS v5.0.6 (2016-12-07)
2
+ * @license Highcharts JS v5.0.7 (2017-01-17)
3
3
  *
4
4
  * (c) 2009-2016 Torstein Honsi
5
5
  *
@@ -36,7 +36,7 @@
36
36
 
37
37
  var Highcharts = win.Highcharts ? win.Highcharts.error(16, true) : {
38
38
  product: 'Highcharts',
39
- version: '5.0.6',
39
+ version: '5.0.7',
40
40
  deg2rad: Math.PI * 2 / 360,
41
41
  doc: doc,
42
42
  hasBidiBug: hasBidiBug,
@@ -1165,8 +1165,11 @@
1165
1165
  }
1166
1166
  }
1167
1167
 
1168
- // multiply back to the correct magnitude
1169
- retInterval *= magnitude;
1168
+ // Multiply back to the correct magnitude. Correct floats to appropriate
1169
+ // precision (#6085).
1170
+ retInterval = H.correctFloat(
1171
+ retInterval * magnitude, -Math.round(Math.log(0.001) / Math.LN10)
1172
+ );
1170
1173
 
1171
1174
  return retInterval;
1172
1175
  };
@@ -1370,7 +1373,8 @@
1370
1373
  * @function #numberFormat
1371
1374
  * @memberOf Highcharts
1372
1375
  * @param {Number} number - The input number to format.
1373
- * @param {Number} decimals - The amount of decimals.
1376
+ * @param {Number} decimals - The amount of decimals. A value of -1 preserves
1377
+ * the amount in the input number.
1374
1378
  * @param {String} [decimalPoint] - The decimal point, defaults to the one given
1375
1379
  * in the lang options.
1376
1380
  * @param {String} [thousandsSep] - The thousands separator, defaults to the one
@@ -1378,17 +1382,15 @@
1378
1382
  * @returns {String} The formatted number.
1379
1383
  */
1380
1384
  H.numberFormat = function(number, decimals, decimalPoint, thousandsSep) {
1381
-
1382
1385
  number = +number || 0;
1383
1386
  decimals = +decimals;
1384
1387
 
1385
1388
  var lang = H.defaultOptions.lang,
1386
1389
  origDec = (number.toString().split('.')[1] || '').length,
1387
- decimalComponent,
1388
1390
  strinteger,
1389
1391
  thousands,
1390
- absNumber = Math.abs(number),
1391
- ret;
1392
+ ret,
1393
+ roundedNumber;
1392
1394
 
1393
1395
  if (decimals === -1) {
1394
1396
  // Preserve decimals. Not huge numbers (#3793).
@@ -1397,8 +1399,14 @@
1397
1399
  decimals = 2;
1398
1400
  }
1399
1401
 
1402
+ // Add another decimal to avoid rounding errors of float numbers. (#4573)
1403
+ // Then use toFixed to handle rounding.
1404
+ roundedNumber = (
1405
+ Math.abs(number) + Math.pow(10, -Math.max(decimals, origDec) - 1)
1406
+ ).toFixed(decimals);
1407
+
1400
1408
  // A string containing the positive integer component of the number
1401
- strinteger = String(H.pInt(absNumber.toFixed(decimals)));
1409
+ strinteger = String(H.pInt(roundedNumber));
1402
1410
 
1403
1411
  // Leftover after grouping into thousands. Can be 0, 1 or 3.
1404
1412
  thousands = strinteger.length > 3 ? strinteger.length % 3 : 0;
@@ -1421,11 +1429,8 @@
1421
1429
 
1422
1430
  // Add the decimal point and the decimal component
1423
1431
  if (decimals) {
1424
- // Get the decimal component, and add power to avoid rounding errors
1425
- // with float numbers (#4573)
1426
- decimalComponent = Math.abs(absNumber - strinteger +
1427
- Math.pow(10, -Math.max(decimals, origDec) - 1));
1428
- ret += decimalPoint + decimalComponent.toFixed(decimals).slice(2);
1432
+ // Get the decimal component
1433
+ ret += decimalPoint + roundedNumber.slice(-decimals);
1429
1434
  }
1430
1435
 
1431
1436
  return ret;
@@ -2280,6 +2285,7 @@
2280
2285
  erase = H.erase,
2281
2286
  grep = H.grep,
2282
2287
  hasTouch = H.hasTouch,
2288
+ inArray = H.inArray,
2283
2289
  isArray = H.isArray,
2284
2290
  isFirefox = H.isFirefox,
2285
2291
  isMS = H.isMS,
@@ -2948,7 +2954,12 @@
2948
2954
  n,
2949
2955
  serializedCss = '',
2950
2956
  hyphenate,
2951
- hasNew = !oldStyles;
2957
+ hasNew = !oldStyles,
2958
+ // These CSS properties are interpreted internally by the SVG
2959
+ // renderer, but are not supported by SVG and should not be added to
2960
+ // the DOM. In styled mode, no CSS should find its way to the DOM
2961
+ // whatsoever (#6173).
2962
+ svgPseudoProps = ['textOverflow', 'width'];
2952
2963
 
2953
2964
  // convert legacy
2954
2965
  if (styles && styles.color) {
@@ -2992,9 +3003,15 @@
2992
3003
  return '-' + b.toLowerCase();
2993
3004
  };
2994
3005
  for (n in styles) {
2995
- serializedCss += n.replace(/([A-Z])/g, hyphenate) + ':' + styles[n] + ';';
3006
+ if (inArray(n, svgPseudoProps) === -1) {
3007
+ serializedCss +=
3008
+ n.replace(/([A-Z])/g, hyphenate) + ':' +
3009
+ styles[n] + ';';
3010
+ }
3011
+ }
3012
+ if (serializedCss) {
3013
+ attr(elem, 'style', serializedCss); // #1881
2996
3014
  }
2997
- attr(elem, 'style', serializedCss); // #1881
2998
3015
  }
2999
3016
 
3000
3017
 
@@ -3138,8 +3155,8 @@
3138
3155
 
3139
3156
  // flipping affects translate as adjustment for flipping around the group's axis
3140
3157
  if (inverted) {
3141
- translateX += wrapper.attr('width');
3142
- translateY += wrapper.attr('height');
3158
+ translateX += wrapper.width;
3159
+ translateY += wrapper.height;
3143
3160
  }
3144
3161
 
3145
3162
  // Apply translate. Nearly all transformed elements have translation, so instead
@@ -3326,8 +3343,8 @@
3326
3343
  '',
3327
3344
  rotation || 0,
3328
3345
  fontSize,
3329
- element.style.width,
3330
- element.style['text-overflow'] // #5968
3346
+ styles && styles.width,
3347
+ styles && styles.textOverflow // #5968
3331
3348
  ]
3332
3349
  .join(',');
3333
3350
 
@@ -3360,9 +3377,9 @@
3360
3377
  bBox = element.getBBox ?
3361
3378
  // SVG: use extend because IE9 is not allowed to change width and height in case
3362
3379
  // of rotation (below)
3363
- extend({}, element.getBBox()) :
3364
- // Legacy IE in export mode
3365
- {
3380
+ extend({}, element.getBBox()) : {
3381
+
3382
+ // Legacy IE in export mode
3366
3383
  width: element.offsetWidth,
3367
3384
  height: element.offsetHeight
3368
3385
  };
@@ -3396,8 +3413,19 @@
3396
3413
  width = bBox.width;
3397
3414
  height = bBox.height;
3398
3415
 
3399
- // Workaround for wrong bounding box in IE9 and IE10 (#1101, #1505, #1669, #2568)
3400
- if (isMS && styles && styles.fontSize === '11px' && height.toPrecision(3) === '16.9') {
3416
+ // Workaround for wrong bounding box in IE, Edge and Chrome on
3417
+ // Windows. With Highcharts' default font, IE and Edge report
3418
+ // a box height of 16.899 and Chrome rounds it to 17. If this
3419
+ // stands uncorrected, it results in more padding added below
3420
+ // the text than above when adding a label border or background.
3421
+ // Also vertical positioning is affected.
3422
+ // http://jsfiddle.net/highcharts/em37nvuj/
3423
+ // (#1101, #1505, #1669, #2568, #6213).
3424
+ if (
3425
+ styles &&
3426
+ styles.fontSize === '11px' &&
3427
+ Math.round(height) === 17
3428
+ ) {
3401
3429
  bBox.height = height = 14;
3402
3430
  }
3403
3431
 
@@ -3990,13 +4018,14 @@
3990
4018
  this.url = (isFirefox || isWebKit) && doc.getElementsByTagName('base').length ?
3991
4019
  win.location.href
3992
4020
  .replace(/#.*?$/, '') // remove the hash
4021
+ .replace(/<[^>]*>/g, '') // wing cut HTML
3993
4022
  .replace(/([\('\)])/g, '\\$1') // escape parantheses and quotes
3994
4023
  .replace(/ /g, '%20') : // replace spaces (needed for Safari only)
3995
4024
  '';
3996
4025
 
3997
4026
  // Add description
3998
4027
  desc = this.createElement('desc').add();
3999
- desc.element.appendChild(doc.createTextNode('Created with Highcharts 5.0.6'));
4028
+ desc.element.appendChild(doc.createTextNode('Created with Highcharts 5.0.7'));
4000
4029
 
4001
4030
 
4002
4031
  renderer.defs = this.createElement('defs').add();
@@ -4169,6 +4198,9 @@
4169
4198
  textLineHeight = textStyles && textStyles.lineHeight,
4170
4199
  textOutline = textStyles && textStyles.textOutline,
4171
4200
  ellipsis = textStyles && textStyles.textOverflow === 'ellipsis',
4201
+ noWrap = textStyles && textStyles.whiteSpace === 'nowrap',
4202
+ fontSize = textStyles && textStyles.fontSize,
4203
+ textCache,
4172
4204
  i = childNodes.length,
4173
4205
  tempParent = width && !wrapper.added && this.box,
4174
4206
  getLineHeight = function(tspan) {
@@ -4176,7 +4208,7 @@
4176
4208
 
4177
4209
  fontSizeStyle = /(px|em)$/.test(tspan && tspan.style.fontSize) ?
4178
4210
  tspan.style.fontSize :
4179
- ((textStyles && textStyles.fontSize) || renderer.style.fontSize || 12);
4211
+ (fontSize || renderer.style.fontSize || 12);
4180
4212
 
4181
4213
 
4182
4214
  return textLineHeight ?
@@ -4191,6 +4223,22 @@
4191
4223
  return inputStr.replace(/&lt;/g, '<').replace(/&gt;/g, '>');
4192
4224
  };
4193
4225
 
4226
+ // The buildText code is quite heavy, so if we're not changing something
4227
+ // that affects the text, skip it (#6113).
4228
+ textCache = [
4229
+ textStr,
4230
+ ellipsis,
4231
+ noWrap,
4232
+ textLineHeight,
4233
+ textOutline,
4234
+ fontSize,
4235
+ width
4236
+ ].join(',');
4237
+ if (textCache === wrapper.textCache) {
4238
+ return;
4239
+ }
4240
+ wrapper.textCache = textCache;
4241
+
4194
4242
  /// remove old text
4195
4243
  while (i--) {
4196
4244
  textNode.removeChild(childNodes[i]);
@@ -4310,7 +4358,6 @@
4310
4358
  // Check width and apply soft breaks or ellipsis
4311
4359
  if (width) {
4312
4360
  var words = span.replace(/([^\^])-/g, '$1- ').split(' '), // #1273
4313
- noWrap = textStyles.whiteSpace === 'nowrap',
4314
4361
  hasWhiteSpace = spans.length > 1 || lineNo || (words.length > 1 && !noWrap),
4315
4362
  tooLong,
4316
4363
  actualWidth,
@@ -4442,6 +4489,15 @@
4442
4489
  */
4443
4490
  getContrast: function(rgba) {
4444
4491
  rgba = color(rgba).rgba;
4492
+
4493
+ // The threshold may be discussed. Here's a proposal for adding
4494
+ // different weight to the color channels (#6216)
4495
+ /*
4496
+ rgba[0] *= 1; // red
4497
+ rgba[1] *= 1.2; // green
4498
+ rgba[2] *= 0.7; // blue
4499
+ */
4500
+
4445
4501
  return rgba[0] + rgba[1] + rgba[2] > 2 * 255 ? '#000000' : '#FFFFFF';
4446
4502
  },
4447
4503
 
@@ -4859,7 +4915,7 @@
4859
4915
  symbolFn = this.symbols[symbol],
4860
4916
 
4861
4917
  // check if there's a path defined for this symbol
4862
- path = defined(x) && symbolFn && symbolFn(
4918
+ path = defined(x) && symbolFn && this.symbols[symbol](
4863
4919
  Math.round(x),
4864
4920
  Math.round(y),
4865
4921
  width,
@@ -5023,13 +5079,12 @@
5023
5079
  */
5024
5080
  symbols: {
5025
5081
  'circle': function(x, y, w, h) {
5026
- var cpw = 0.166 * w;
5027
- return [
5028
- 'M', x + w / 2, y,
5029
- 'C', x + w + cpw, y, x + w + cpw, y + h, x + w / 2, y + h,
5030
- 'C', x - cpw, y + h, x - cpw, y, x + w / 2, y,
5031
- 'Z'
5032
- ];
5082
+ // Return a full arc
5083
+ return this.arc(x + w / 2, y + h / 2, w / 2, h / 2, {
5084
+ start: 0,
5085
+ end: Math.PI * 2,
5086
+ open: false
5087
+ });
5033
5088
  },
5034
5089
 
5035
5090
  'square': function(x, y, w, h) {
@@ -5070,7 +5125,8 @@
5070
5125
  },
5071
5126
  'arc': function(x, y, w, h, options) {
5072
5127
  var start = options.start,
5073
- radius = options.r || w || h,
5128
+ rx = options.r || w,
5129
+ ry = options.r || h || w,
5074
5130
  end = options.end - 0.001, // to prevent cos and sin of start and end from becoming equal on 360 arcs (related: #1561)
5075
5131
  innerRadius = options.innerR,
5076
5132
  open = options.open,
@@ -5078,34 +5134,41 @@
5078
5134
  sinStart = Math.sin(start),
5079
5135
  cosEnd = Math.cos(end),
5080
5136
  sinEnd = Math.sin(end),
5081
- longArc = options.end - start < Math.PI ? 0 : 1;
5137
+ longArc = options.end - start < Math.PI ? 0 : 1,
5138
+ arc;
5082
5139
 
5083
- return [
5140
+ arc = [
5084
5141
  'M',
5085
- x + radius * cosStart,
5086
- y + radius * sinStart,
5142
+ x + rx * cosStart,
5143
+ y + ry * sinStart,
5087
5144
  'A', // arcTo
5088
- radius, // x radius
5089
- radius, // y radius
5145
+ rx, // x radius
5146
+ ry, // y radius
5090
5147
  0, // slanting
5091
5148
  longArc, // long or short arc
5092
5149
  1, // clockwise
5093
- x + radius * cosEnd,
5094
- y + radius * sinEnd,
5095
- open ? 'M' : 'L',
5096
- x + innerRadius * cosEnd,
5097
- y + innerRadius * sinEnd,
5098
- 'A', // arcTo
5099
- innerRadius, // x radius
5100
- innerRadius, // y radius
5101
- 0, // slanting
5102
- longArc, // long or short arc
5103
- 0, // clockwise
5104
- x + innerRadius * cosStart,
5105
- y + innerRadius * sinStart,
5106
-
5107
- open ? '' : 'Z' // close
5150
+ x + rx * cosEnd,
5151
+ y + ry * sinEnd
5108
5152
  ];
5153
+
5154
+ if (defined(innerRadius)) {
5155
+ arc.push(
5156
+ open ? 'M' : 'L',
5157
+ x + innerRadius * cosEnd,
5158
+ y + innerRadius * sinEnd,
5159
+ 'A', // arcTo
5160
+ innerRadius, // x radius
5161
+ innerRadius, // y radius
5162
+ 0, // slanting
5163
+ longArc, // long or short arc
5164
+ 0, // clockwise
5165
+ x + innerRadius * cosStart,
5166
+ y + innerRadius * sinStart
5167
+ );
5168
+ }
5169
+
5170
+ arc.push(open ? '' : 'Z'); // close
5171
+ return arc;
5109
5172
  },
5110
5173
 
5111
5174
  /**
@@ -7241,11 +7304,18 @@
7241
7304
  symbols: ['circle', 'diamond', 'square', 'triangle', 'triangle-down'],
7242
7305
  lang: {
7243
7306
  loading: 'Loading...',
7244
- months: ['January', 'February', 'March', 'April', 'May', 'June', 'July',
7307
+ months: [
7308
+ 'January', 'February', 'March', 'April', 'May', 'June', 'July',
7245
7309
  'August', 'September', 'October', 'November', 'December'
7246
7310
  ],
7247
- shortMonths: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
7248
- weekdays: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
7311
+ shortMonths: [
7312
+ 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul',
7313
+ 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'
7314
+ ],
7315
+ weekdays: [
7316
+ 'Sunday', 'Monday', 'Tuesday', 'Wednesday',
7317
+ 'Thursday', 'Friday', 'Saturday'
7318
+ ],
7249
7319
  // invalidDate: '',
7250
7320
  decimalPoint: '.',
7251
7321
  numericSymbols: ['k', 'M', 'G', 'T', 'P', 'E'], // SI prefixes used in axis labels
@@ -7257,7 +7327,7 @@
7257
7327
  useUTC: true,
7258
7328
  //timezoneOffset: 0,
7259
7329
 
7260
- VMLRadialGradientURL: 'http://code.highcharts.com/5.0.6/gfx/vml-radial-gradient.png'
7330
+ VMLRadialGradientURL: 'http://code.highcharts.com/5.0.7/gfx/vml-radial-gradient.png'
7261
7331
 
7262
7332
  },
7263
7333
  chart: {
@@ -7493,6 +7563,37 @@
7493
7563
 
7494
7564
 
7495
7565
 
7566
+ /**
7567
+ * Sets the getTimezoneOffset function. If the timezone option is set, a default
7568
+ * getTimezoneOffset function with that timezone is returned. If not, the
7569
+ * specified getTimezoneOffset function is returned. If neither are specified,
7570
+ * undefined is returned.
7571
+ * @return {function} a getTimezoneOffset function or undefined
7572
+ */
7573
+ function getTimezoneOffsetOption() {
7574
+ var globalOptions = H.defaultOptions.global,
7575
+ moment = win.moment;
7576
+
7577
+ if (globalOptions.timezone) {
7578
+ if (!moment) {
7579
+ // getTimezoneOffset-function stays undefined because it depends on
7580
+ // Moment.js
7581
+ H.error(25);
7582
+
7583
+ } else {
7584
+ return function(timestamp) {
7585
+ return -moment.tz(
7586
+ timestamp,
7587
+ globalOptions.timezone
7588
+ ).utcOffset();
7589
+ };
7590
+ }
7591
+ }
7592
+
7593
+ // If not timezone is set, look for the getTimezoneOffset callback
7594
+ return globalOptions.useUTC && globalOptions.getTimezoneOffset;
7595
+ }
7596
+
7496
7597
  /**
7497
7598
  * Set the time methods globally based on the useUTC option. Time method can be
7498
7599
  * either local time or UTC (default). It is called internally on initiating
@@ -7509,7 +7610,7 @@
7509
7610
 
7510
7611
  H.Date = Date = globalOptions.Date || win.Date; // Allow using a different Date class
7511
7612
  Date.hcTimezoneOffset = useUTC && globalOptions.timezoneOffset;
7512
- Date.hcGetTimezoneOffset = useUTC && globalOptions.getTimezoneOffset;
7613
+ Date.hcGetTimezoneOffset = getTimezoneOffsetOption();
7513
7614
  Date.hcMakeTime = function(year, month, date, hours, minutes, seconds) {
7514
7615
  var d;
7515
7616
  if (useUTC) {
@@ -7686,6 +7787,7 @@
7686
7787
  return;
7687
7788
  }
7688
7789
 
7790
+
7689
7791
  // common for lines and bands
7690
7792
  if (isNew && path && path.length) {
7691
7793
  svgElem.attr({
@@ -9246,7 +9348,7 @@
9246
9348
 
9247
9349
  each(series.points, function(point, i) {
9248
9350
  var x;
9249
- if (point.options && point.options.x === undefined) {
9351
+ if (point.options) {
9250
9352
  x = axis.nameToX(point);
9251
9353
  if (x !== point.x) {
9252
9354
  point.x = x;
@@ -9606,11 +9708,9 @@
9606
9708
 
9607
9709
  }
9608
9710
 
9711
+ // reset min/max or remove extremes based on start/end on tick
9712
+ this.trimTicks(tickPositions, startOnTick, endOnTick);
9609
9713
  if (!this.isLinked) {
9610
-
9611
- // reset min/max or remove extremes based on start/end on tick
9612
- this.trimTicks(tickPositions, startOnTick, endOnTick);
9613
-
9614
9714
  // When there is only one point, or all points have the same value on this axis, then min
9615
9715
  // and max are equal and tickPositions.length is 0 or 1. In this case, add some padding
9616
9716
  // in order to center the point, but leave it with one tick. #1337.
@@ -9621,7 +9721,6 @@
9621
9721
  this.max += 0.5;
9622
9722
  }
9623
9723
  this.single = single;
9624
-
9625
9724
  if (!tickPositionsOption && !tickPositioner) {
9626
9725
  this.adjustTickAmount();
9627
9726
  }
@@ -9636,25 +9735,27 @@
9636
9735
  roundedMax = tickPositions[tickPositions.length - 1],
9637
9736
  minPointOffset = this.minPointOffset || 0;
9638
9737
 
9639
- if (startOnTick) {
9640
- this.min = roundedMin;
9641
- } else {
9642
- while (this.min - minPointOffset > tickPositions[0]) {
9643
- tickPositions.shift();
9738
+ if (!this.isLinked) {
9739
+ if (startOnTick) {
9740
+ this.min = roundedMin;
9741
+ } else {
9742
+ while (this.min - minPointOffset > tickPositions[0]) {
9743
+ tickPositions.shift();
9744
+ }
9644
9745
  }
9645
- }
9646
9746
 
9647
- if (endOnTick) {
9648
- this.max = roundedMax;
9649
- } else {
9650
- while (this.max + minPointOffset < tickPositions[tickPositions.length - 1]) {
9651
- tickPositions.pop();
9747
+ if (endOnTick) {
9748
+ this.max = roundedMax;
9749
+ } else {
9750
+ while (this.max + minPointOffset < tickPositions[tickPositions.length - 1]) {
9751
+ tickPositions.pop();
9752
+ }
9652
9753
  }
9653
- }
9654
9754
 
9655
- // If no tick are left, set one tick in the middle (#3195)
9656
- if (tickPositions.length === 0 && defined(roundedMin)) {
9657
- tickPositions.push((roundedMax + roundedMin) / 2);
9755
+ // If no tick are left, set one tick in the middle (#3195)
9756
+ if (tickPositions.length === 0 && defined(roundedMin)) {
9757
+ tickPositions.push((roundedMax + roundedMin) / 2);
9758
+ }
9658
9759
  }
9659
9760
  },
9660
9761
 
@@ -9921,13 +10022,12 @@
9921
10022
  setAxisSize: function() {
9922
10023
  var chart = this.chart,
9923
10024
  options = this.options,
9924
- offsetLeft = options.offsetLeft || 0,
9925
- offsetRight = options.offsetRight || 0,
10025
+ offsets = options.offsets || [0, 0, 0, 0], // top / right / bottom / left
9926
10026
  horiz = this.horiz,
9927
- width = pick(options.width, chart.plotWidth - offsetLeft + offsetRight),
9928
- height = pick(options.height, chart.plotHeight),
9929
- top = pick(options.top, chart.plotTop),
9930
- left = pick(options.left, chart.plotLeft + offsetLeft),
10027
+ width = pick(options.width, chart.plotWidth - offsets[3] + offsets[1]),
10028
+ height = pick(options.height, chart.plotHeight - offsets[0] + offsets[2]),
10029
+ top = pick(options.top, chart.plotTop + offsets[0]),
10030
+ left = pick(options.left, chart.plotLeft + offsets[3]),
9931
10031
  percentRegex = /%$/;
9932
10032
 
9933
10033
  // Check for percentage based input values. Rounding fixes problems with
@@ -10113,9 +10213,15 @@
10113
10213
  slotCount = Math.max(this.tickPositions.length - (this.categories ? 0 : 1), 1),
10114
10214
  marginLeft = chart.margin[3];
10115
10215
 
10116
- return (horiz && (labelOptions.step || 0) < 2 && !labelOptions.rotation && // #4415
10117
- ((this.staggerLines || 1) * chart.plotWidth) / slotCount) ||
10118
- (!horiz && ((marginLeft && (marginLeft - chart.spacing[3])) || chart.chartWidth * 0.33)); // #1580, #1931
10216
+ return (
10217
+ horiz &&
10218
+ (labelOptions.step || 0) < 2 &&
10219
+ !labelOptions.rotation && // #4415
10220
+ ((this.staggerLines || 1) * this.len) / slotCount
10221
+ ) || (!horiz && (
10222
+ (marginLeft && (marginLeft - chart.spacing[3])) ||
10223
+ chart.chartWidth * 0.33
10224
+ )); // #1580, #1931
10119
10225
 
10120
10226
  },
10121
10227
 
@@ -10296,6 +10402,21 @@
10296
10402
  axis.axisTitle[display ? 'show' : 'hide'](true);
10297
10403
  },
10298
10404
 
10405
+ /**
10406
+ * Generates a tick for initial positioning.
10407
+ * @param {number} pos - The tick position in axis values.
10408
+ * @param {number} i - The index of the tick in axis.tickPositions.
10409
+ */
10410
+ generateTick: function(pos) {
10411
+ var ticks = this.ticks;
10412
+
10413
+ if (!ticks[pos]) {
10414
+ ticks[pos] = new Tick(this, pos);
10415
+ } else {
10416
+ ticks[pos].addLabel(); // update labels depending on tick interval
10417
+ }
10418
+ },
10419
+
10299
10420
  /**
10300
10421
  * Render the tick labels to a preliminary position to get their sizes
10301
10422
  */
@@ -10360,12 +10481,9 @@
10360
10481
  if (hasData || axis.isLinked) {
10361
10482
 
10362
10483
  // Generate ticks
10363
- each(tickPositions, function(pos) {
10364
- if (!ticks[pos]) {
10365
- ticks[pos] = new Tick(axis, pos);
10366
- } else {
10367
- ticks[pos].addLabel(); // update labels depending on tick interval
10368
- }
10484
+ each(tickPositions, function(pos, i) {
10485
+ // i is not used here, but may be used in overrides
10486
+ axis.generateTick(pos, i);
10369
10487
  });
10370
10488
 
10371
10489
  axis.renderUnsquish();
@@ -10539,6 +10657,54 @@
10539
10657
  };
10540
10658
  },
10541
10659
 
10660
+ /**
10661
+ * Render a minor tick into the given position. If a minor tick already
10662
+ * exists in this position, move it.
10663
+ * @param {number} pos - The position in axis values.
10664
+ */
10665
+ renderMinorTick: function(pos) {
10666
+ var slideInTicks = this.chart.hasRendered && isNumber(this.oldMin),
10667
+ minorTicks = this.minorTicks;
10668
+
10669
+ if (!minorTicks[pos]) {
10670
+ minorTicks[pos] = new Tick(this, pos, 'minor');
10671
+ }
10672
+
10673
+ // Render new ticks in old position
10674
+ if (slideInTicks && minorTicks[pos].isNew) {
10675
+ minorTicks[pos].render(null, true);
10676
+ }
10677
+
10678
+ minorTicks[pos].render(null, false, 1);
10679
+ },
10680
+
10681
+ /**
10682
+ * Render a major tick into the given position. If a tick already exists
10683
+ * in this position, move it.
10684
+ * @param {number} pos - The position in axis values
10685
+ * @param {number} i - The tick index
10686
+ */
10687
+ renderTick: function(pos, i) {
10688
+ var isLinked = this.isLinked,
10689
+ ticks = this.ticks,
10690
+ slideInTicks = this.chart.hasRendered && isNumber(this.oldMin);
10691
+
10692
+ // Linked axes need an extra check to find out if
10693
+ if (!isLinked || (pos >= this.min && pos <= this.max)) {
10694
+
10695
+ if (!ticks[pos]) {
10696
+ ticks[pos] = new Tick(this, pos);
10697
+ }
10698
+
10699
+ // render new ticks in old position
10700
+ if (slideInTicks && ticks[pos].isNew) {
10701
+ ticks[pos].render(i, true, 0.1);
10702
+ }
10703
+
10704
+ ticks[pos].render(i);
10705
+ }
10706
+ },
10707
+
10542
10708
  /**
10543
10709
  * Render the axis
10544
10710
  */
@@ -10559,8 +10725,6 @@
10559
10725
  alternateGridColor = options.alternateGridColor,
10560
10726
  tickmarkOffset = axis.tickmarkOffset,
10561
10727
  axisLine = axis.axisLine,
10562
- hasRendered = chart.hasRendered,
10563
- slideInTicks = hasRendered && isNumber(axis.oldMin),
10564
10728
  showAxis = axis.showAxis,
10565
10729
  animation = animObject(renderer.globalAnimation),
10566
10730
  from,
@@ -10585,16 +10749,7 @@
10585
10749
  // minor ticks
10586
10750
  if (axis.minorTickInterval && !axis.categories) {
10587
10751
  each(axis.getMinorTickPositions(), function(pos) {
10588
- if (!minorTicks[pos]) {
10589
- minorTicks[pos] = new Tick(axis, pos, 'minor');
10590
- }
10591
-
10592
- // render new ticks in old position
10593
- if (slideInTicks && minorTicks[pos].isNew) {
10594
- minorTicks[pos].render(null, true);
10595
- }
10596
-
10597
- minorTicks[pos].render(null, false, 1);
10752
+ axis.renderMinorTick(pos);
10598
10753
  });
10599
10754
  }
10600
10755
 
@@ -10602,22 +10757,7 @@
10602
10757
  // we can get the position of the neighbour label. #808.
10603
10758
  if (tickPositions.length) { // #1300
10604
10759
  each(tickPositions, function(pos, i) {
10605
-
10606
- // linked axes need an extra check to find out if
10607
- if (!isLinked || (pos >= axis.min && pos <= axis.max)) {
10608
-
10609
- if (!ticks[pos]) {
10610
- ticks[pos] = new Tick(axis, pos);
10611
- }
10612
-
10613
- // render new ticks in old position
10614
- if (slideInTicks && ticks[pos].isNew) {
10615
- ticks[pos].render(i, true, 0.1);
10616
- }
10617
-
10618
- ticks[pos].render(i);
10619
- }
10620
-
10760
+ axis.renderTick(pos, i);
10621
10761
  });
10622
10762
  // In a categorized axis, the tick marks are displayed between labels. So
10623
10763
  // we need to add a tick mark and grid line at the left edge of the X axis.
@@ -11058,10 +11198,17 @@
11058
11198
 
11059
11199
 
11060
11200
  // Handle higher ranks. Mark new days if the time is on midnight
11061
- // (#950, #1649, #1760, #3349)
11062
- if (interval <= timeUnits.hour) {
11201
+ // (#950, #1649, #1760, #3349). Use a reasonable dropout threshold to
11202
+ // prevent looping over dense data grouping (#6156).
11203
+ if (interval <= timeUnits.hour && tickPositions.length < 10000) {
11063
11204
  each(tickPositions, function(time) {
11064
- if (dateFormat('%H%M%S%L', time) === '000000000') {
11205
+ if (
11206
+ // Speed optimization, no need to run dateFormat unless
11207
+ // we're on a full or half hour
11208
+ time % 1800000 === 0 &&
11209
+ // Check for local or global midnight
11210
+ dateFormat('%H%M%S%L', time) === '000000000'
11211
+ ) {
11065
11212
  higherRanks[time] = 'day';
11066
11213
  }
11067
11214
  });
@@ -11730,13 +11877,13 @@
11730
11877
  y: point[0].y
11731
11878
  };
11732
11879
  textConfig.points = pointConfig;
11733
- this.len = pointConfig.length;
11734
11880
  point = point[0];
11735
11881
 
11736
11882
  // single point tooltip
11737
11883
  } else {
11738
11884
  textConfig = point.getLabelConfig();
11739
11885
  }
11886
+ this.len = pointConfig.length; // #6128
11740
11887
  text = formatter.call(textConfig, tooltip);
11741
11888
 
11742
11889
  // register the current series
@@ -11803,8 +11950,8 @@
11803
11950
  headerHeight,
11804
11951
  tooltipLabel = this.getLabel();
11805
11952
 
11806
- // Create the individual labels
11807
- each(labels.slice(0, labels.length - 1), function(str, i) {
11953
+ // Create the individual labels for header and points, ignore footer
11954
+ each(labels.slice(0, points.length + 1), function(str, i) {
11808
11955
  var point = points[i - 1] ||
11809
11956
  // Item 0 is the header. Instead of this, we could also use the crosshair label
11810
11957
  {
@@ -11927,12 +12074,17 @@
11927
12074
  },
11928
12075
 
11929
12076
  /**
11930
- * Get the best X date format based on the closest point range on the axis.
12077
+ * Get the optimal date format for a point, based on a range.
12078
+ * @param {number} range - The time range
12079
+ * @param {number|Date} date - The date of the point in question
12080
+ * @param {number} startOfWeek - An integer representing the first day of
12081
+ * the week, where 0 is Sunday
12082
+ * @param {Object} dateTimeLabelFormats - A map of time units to formats
12083
+ * @return {string} - the optimal date format for a point
11931
12084
  */
11932
- getXDateFormat: function(point, options, xAxis) {
11933
- var xDateFormat,
11934
- dateTimeLabelFormats = options.dateTimeLabelFormats,
11935
- closestPointRange = xAxis && xAxis.closestPointRange,
12085
+ getDateFormat: function(range, date, startOfWeek, dateTimeLabelFormats) {
12086
+ var dateStr = dateFormat('%m-%d %H:%M:%S.%L', date),
12087
+ format,
11936
12088
  n,
11937
12089
  blank = '01-01 00:00:00.000',
11938
12090
  strpos = {
@@ -11942,41 +12094,56 @@
11942
12094
  hour: 6,
11943
12095
  day: 3
11944
12096
  },
11945
- date,
11946
12097
  lastN = 'millisecond'; // for sub-millisecond data, #4223
12098
+ for (n in timeUnits) {
11947
12099
 
11948
- if (closestPointRange) {
11949
- date = dateFormat('%m-%d %H:%M:%S.%L', point.x);
11950
- for (n in timeUnits) {
11951
-
11952
- // If the range is exactly one week and we're looking at a Sunday/Monday, go for the week format
11953
- if (closestPointRange === timeUnits.week && +dateFormat('%w', point.x) === xAxis.options.startOfWeek &&
11954
- date.substr(6) === blank.substr(6)) {
11955
- n = 'week';
11956
- break;
11957
- }
11958
-
11959
- // The first format that is too great for the range
11960
- if (timeUnits[n] > closestPointRange) {
11961
- n = lastN;
11962
- break;
11963
- }
12100
+ // If the range is exactly one week and we're looking at a Sunday/Monday, go for the week format
12101
+ if (range === timeUnits.week && +dateFormat('%w', date) === startOfWeek &&
12102
+ dateStr.substr(6) === blank.substr(6)) {
12103
+ n = 'week';
12104
+ break;
12105
+ }
11964
12106
 
11965
- // If the point is placed every day at 23:59, we need to show
11966
- // the minutes as well. #2637.
11967
- if (strpos[n] && date.substr(strpos[n]) !== blank.substr(strpos[n])) {
11968
- break;
11969
- }
12107
+ // The first format that is too great for the range
12108
+ if (timeUnits[n] > range) {
12109
+ n = lastN;
12110
+ break;
12111
+ }
11970
12112
 
11971
- // Weeks are outside the hierarchy, only apply them on Mondays/Sundays like in the first condition
11972
- if (n !== 'week') {
11973
- lastN = n;
11974
- }
12113
+ // If the point is placed every day at 23:59, we need to show
12114
+ // the minutes as well. #2637.
12115
+ if (strpos[n] && dateStr.substr(strpos[n]) !== blank.substr(strpos[n])) {
12116
+ break;
11975
12117
  }
11976
12118
 
11977
- if (n) {
11978
- xDateFormat = dateTimeLabelFormats[n];
12119
+ // Weeks are outside the hierarchy, only apply them on Mondays/Sundays like in the first condition
12120
+ if (n !== 'week') {
12121
+ lastN = n;
11979
12122
  }
12123
+ }
12124
+
12125
+ if (n) {
12126
+ format = dateTimeLabelFormats[n];
12127
+ }
12128
+
12129
+ return format;
12130
+ },
12131
+
12132
+ /**
12133
+ * Get the best X date format based on the closest point range on the axis.
12134
+ */
12135
+ getXDateFormat: function(point, options, xAxis) {
12136
+ var xDateFormat,
12137
+ dateTimeLabelFormats = options.dateTimeLabelFormats,
12138
+ closestPointRange = xAxis && xAxis.closestPointRange;
12139
+
12140
+ if (closestPointRange) {
12141
+ xDateFormat = this.getDateFormat(
12142
+ closestPointRange,
12143
+ point.x,
12144
+ xAxis.options.startOfWeek,
12145
+ dateTimeLabelFormats
12146
+ );
11980
12147
  } else {
11981
12148
  xDateFormat = dateTimeLabelFormats.day;
11982
12149
  }
@@ -12236,7 +12403,8 @@
12236
12403
  kdpoints.sort(function(p1, p2) {
12237
12404
  var isCloserX = p1.distX - p2.distX,
12238
12405
  isCloser = p1.dist - p2.dist,
12239
- isAbove = p2.series.group.zIndex - p1.series.group.zIndex;
12406
+ isAbove = (p2.series.group && p2.series.group.zIndex) -
12407
+ (p1.series.group && p1.series.group.zIndex);
12240
12408
 
12241
12409
  // We have two points which are not in the same place on xAxis and shared tooltip:
12242
12410
  if (isCloserX !== 0 && shared) { // #5721
@@ -13562,6 +13730,7 @@
13562
13730
  }
13563
13731
 
13564
13732
  // Draw the legend symbol inside the group box
13733
+ legend.symbolHeight = options.symbolHeight || legend.fontMetrics.f;
13565
13734
  series.drawLegendSymbol(legend, item);
13566
13735
 
13567
13736
  if (legend.setItemEvents) {
@@ -14039,7 +14208,7 @@
14039
14208
  */
14040
14209
  drawRectangle: function(legend, item) {
14041
14210
  var options = legend.options,
14042
- symbolHeight = options.symbolHeight || legend.fontMetrics.f,
14211
+ symbolHeight = legend.symbolHeight,
14043
14212
  square = options.squareSymbol,
14044
14213
  symbolWidth = square ? symbolHeight : legend.symbolWidth;
14045
14214
 
@@ -14070,6 +14239,8 @@
14070
14239
  radius,
14071
14240
  legendSymbol,
14072
14241
  symbolWidth = legend.symbolWidth,
14242
+ symbolHeight = legend.symbolHeight,
14243
+ generalRadius = symbolHeight / 2,
14073
14244
  renderer = this.chart.renderer,
14074
14245
  legendItemGroup = this.legendGroup,
14075
14246
  verticalCenter = legend.baseline - Math.round(legend.fontMetrics.b * 0.3),
@@ -14099,7 +14270,22 @@
14099
14270
 
14100
14271
  // Draw the marker
14101
14272
  if (markerOptions && markerOptions.enabled !== false) {
14102
- radius = this.symbol.indexOf('url') === 0 ? 0 : markerOptions.radius;
14273
+
14274
+ // Do not allow the marker to be larger than the symbolHeight
14275
+ radius = Math.min(
14276
+ pick(markerOptions.radius, generalRadius),
14277
+ generalRadius
14278
+ );
14279
+
14280
+ // Restrict symbol markers size
14281
+ if (this.symbol.indexOf('url') === 0) {
14282
+ markerOptions = merge(markerOptions, {
14283
+ width: symbolHeight,
14284
+ height: symbolHeight
14285
+ });
14286
+ radius = 0;
14287
+ }
14288
+
14103
14289
  this.legendSymbol = legendSymbol = renderer.symbol(
14104
14290
  this.symbol,
14105
14291
  (symbolWidth / 2) - radius,
@@ -14325,6 +14511,26 @@
14325
14511
  return series;
14326
14512
  },
14327
14513
 
14514
+ /**
14515
+ * Order all series above a given index. When series are added and ordered
14516
+ * by configuration, only the last series is handled (#248, #1123, #2456,
14517
+ * #6112). This function is called on series initialization and destroy.
14518
+ *
14519
+ * @param {number} fromIndex - If this is given, only the series above this
14520
+ * index are handled.
14521
+ */
14522
+ orderSeries: function(fromIndex) {
14523
+ var series = this.series,
14524
+ i = fromIndex || 0;
14525
+ for (; i < series.length; i++) {
14526
+ if (series[i]) {
14527
+ series[i].index = i;
14528
+ series[i].name = series[i].name ||
14529
+ 'Series ' + (series[i].index + 1);
14530
+ }
14531
+ }
14532
+ },
14533
+
14328
14534
  /**
14329
14535
  * Check whether a given point is within the plot area
14330
14536
  *
@@ -14366,6 +14572,11 @@
14366
14572
  isHiddenChart = renderer.isHidden(),
14367
14573
  afterRedraw = [];
14368
14574
 
14575
+ // Handle responsive rules, not only on resize (#6130)
14576
+ if (chart.setResponsive) {
14577
+ chart.setResponsive(false);
14578
+ }
14579
+
14369
14580
  H.setAnimation(animation, chart);
14370
14581
 
14371
14582
  if (isHiddenChart) {
@@ -14468,12 +14679,18 @@
14468
14679
  chart.drawChartBox();
14469
14680
  }
14470
14681
 
14682
+ // Fire an event before redrawing series, used by the boost module to
14683
+ // clear previous series renderings.
14684
+ fireEvent(chart, 'predraw');
14471
14685
 
14472
14686
  // redraw affected series
14473
14687
  each(series, function(serie) {
14474
14688
  if ((isDirtyBox || serie.isDirty) && serie.visible) {
14475
14689
  serie.redraw();
14476
14690
  }
14691
+ // Set it here, otherwise we will have unlimited 'updatedData' calls
14692
+ // for a hidden series after setData(). Fixes #6012
14693
+ serie.isDirtyData = false;
14477
14694
  });
14478
14695
 
14479
14696
  // move tooltip or reset
@@ -14484,8 +14701,9 @@
14484
14701
  // redraw if canvas
14485
14702
  renderer.draw();
14486
14703
 
14487
- // fire the event
14704
+ // Fire the events
14488
14705
  fireEvent(chart, 'redraw');
14706
+ fireEvent(chart, 'render');
14489
14707
 
14490
14708
  if (isHiddenChart) {
14491
14709
  chart.cloneRenderTo(true);
@@ -14508,7 +14726,7 @@
14508
14726
  i;
14509
14727
 
14510
14728
  function itemById(item) {
14511
- return item.id === id || item.options.id === id;
14729
+ return item.id === id || (item.options && item.options.id === id);
14512
14730
  }
14513
14731
 
14514
14732
  ret =
@@ -14724,10 +14942,14 @@
14724
14942
  chart.containerHeight = getStyle(renderTo, 'height');
14725
14943
  }
14726
14944
 
14727
- chart.chartWidth = Math.max(0, widthOption || chart.containerWidth || 600); // #1393, 1460
14728
- chart.chartHeight = Math.max(0, pick(heightOption,
14729
- // the offsetHeight of an empty container is 0 in standard browsers, but 19 in IE7:
14730
- chart.containerHeight > 19 ? chart.containerHeight : 400));
14945
+ chart.chartWidth = Math.max( // #1393
14946
+ 0,
14947
+ widthOption || chart.containerWidth || 600 // #1460
14948
+ );
14949
+ chart.chartHeight = Math.max(
14950
+ 0,
14951
+ heightOption || chart.containerHeight || 400
14952
+ );
14731
14953
  },
14732
14954
 
14733
14955
  /**
@@ -14907,8 +15129,8 @@
14907
15129
  }
14908
15130
 
14909
15131
  // adjust for scroller
14910
- if (chart.extraBottomMargin) {
14911
- chart.marginBottom += chart.extraBottomMargin;
15132
+ if (chart.extraMargin) {
15133
+ chart[chart.extraMargin.type] = (chart[chart.extraMargin.type] || 0) + chart.extraMargin.value;
14912
15134
  }
14913
15135
  if (chart.extraTopMargin) {
14914
15136
  chart.plotTop += chart.extraTopMargin;
@@ -15048,9 +15270,6 @@
15048
15270
  chart.layOutTitles(); // #2857
15049
15271
  chart.getMargins();
15050
15272
 
15051
- if (chart.setResponsive) {
15052
- chart.setResponsive(false);
15053
- }
15054
15273
  chart.redraw(animation);
15055
15274
 
15056
15275
 
@@ -15570,9 +15789,11 @@
15570
15789
  }
15571
15790
 
15572
15791
  // ==== Destroy chart properties:
15573
- each(['title', 'subtitle', 'chartBackground', 'plotBackground', 'plotBGImage',
15574
- 'plotBorder', 'seriesGroup', 'clipRect', 'credits', 'pointer',
15575
- 'rangeSelector', 'legend', 'resetZoomButton', 'tooltip', 'renderer'
15792
+ each([
15793
+ 'title', 'subtitle', 'chartBackground', 'plotBackground',
15794
+ 'plotBGImage', 'plotBorder', 'seriesGroup', 'clipRect', 'credits',
15795
+ 'pointer', 'rangeSelector', 'legend', 'resetZoomButton', 'tooltip',
15796
+ 'renderer'
15576
15797
  ], function(name) {
15577
15798
  var prop = chart[name];
15578
15799
 
@@ -15666,9 +15887,6 @@
15666
15887
 
15667
15888
  chart.render();
15668
15889
 
15669
- // add canvas
15670
- chart.renderer.draw();
15671
-
15672
15890
  // Fire the load event if there are no external images
15673
15891
  if (!chart.renderer.imgCount && chart.onload) {
15674
15892
  chart.onload();
@@ -15692,6 +15910,8 @@
15692
15910
  }, this);
15693
15911
 
15694
15912
  fireEvent(this, 'load');
15913
+ fireEvent(this, 'render');
15914
+
15695
15915
 
15696
15916
  // Set up auto resize, check for not destroyed (#6068)
15697
15917
  if (defined(this.index) && this.options.chart.reflow !== false) {
@@ -15892,9 +16112,11 @@
15892
16112
  (this.selected ? ' highcharts-point-select' : '') +
15893
16113
  (this.negative ? ' highcharts-negative' : '') +
15894
16114
  (this.isNull ? ' highcharts-null-point' : '') +
15895
- (this.colorIndex !== undefined ? ' highcharts-color-' + this.colorIndex : '') +
16115
+ (this.colorIndex !== undefined ? ' highcharts-color-' +
16116
+ this.colorIndex : '') +
15896
16117
  (this.options.className ? ' ' + this.options.className : '') +
15897
- (this.zone && this.zone.className ? ' ' + this.zone.className : '');
16118
+ (this.zone && this.zone.className ? ' ' +
16119
+ this.zone.className.replace('highcharts-negative', '') : '');
15898
16120
  },
15899
16121
 
15900
16122
  /**
@@ -15984,6 +16206,7 @@
15984
16206
  x: this.category,
15985
16207
  y: this.y,
15986
16208
  color: this.color,
16209
+ colorIndex: this.colorIndex,
15987
16210
  key: this.name || this.category,
15988
16211
  series: this.series,
15989
16212
  point: this,
@@ -16235,8 +16458,7 @@
16235
16458
  eventType,
16236
16459
  events,
16237
16460
  chartSeries = chart.series,
16238
- lastSeries,
16239
- i;
16461
+ lastSeries;
16240
16462
 
16241
16463
  series.chart = chart;
16242
16464
  series.options = options = series.setOptions(options); // merge with plotOptions
@@ -16287,14 +16509,8 @@
16287
16509
  }
16288
16510
  series._i = pick(lastSeries && lastSeries._i, -1) + 1;
16289
16511
 
16290
- // Insert the series and update the `index` property of all series
16291
- // above this. Unless the `index` option is set, the new series is
16292
- // inserted last. #248, #1123, #2456
16293
- for (i = this.insert(chartSeries); i < chartSeries.length; i++) {
16294
- chartSeries[i].index = i;
16295
- chartSeries[i].name = chartSeries[i].name ||
16296
- 'Series ' + (chartSeries[i].index + 1);
16297
- }
16512
+ // Insert the series and re-order all series above the insertion point.
16513
+ chart.orderSeries(this.insert(chartSeries));
16298
16514
  },
16299
16515
 
16300
16516
  /**
@@ -16502,10 +16718,14 @@
16502
16718
 
16503
16719
  getCyclic: function(prop, value, defaults) {
16504
16720
  var i,
16721
+ chart = this.chart,
16505
16722
  userOptions = this.userOptions,
16506
16723
  indexName = prop + 'Index',
16507
16724
  counterName = prop + 'Counter',
16508
- len = defaults ? defaults.length : pick(this.chart.options.chart[prop + 'Count'], this.chart[prop + 'Count']),
16725
+ len = defaults ? defaults.length : pick(
16726
+ chart.options.chart[prop + 'Count'],
16727
+ chart[prop + 'Count']
16728
+ ),
16509
16729
  setting;
16510
16730
 
16511
16731
  if (!value) {
@@ -16514,8 +16734,12 @@
16514
16734
  if (defined(setting)) { // after Series.update()
16515
16735
  i = setting;
16516
16736
  } else {
16517
- userOptions['_' + indexName] = i = this.chart[counterName] % len;
16518
- this.chart[counterName] += 1;
16737
+ // #6138
16738
+ if (!chart.series.length) {
16739
+ chart[counterName] = 0;
16740
+ }
16741
+ userOptions['_' + indexName] = i = chart[counterName] % len;
16742
+ chart[counterName] += 1;
16519
16743
  }
16520
16744
  if (defaults) {
16521
16745
  value = defaults[i];
@@ -17128,6 +17352,7 @@
17128
17352
  chart[sharedClipKey] = chart[sharedClipKey].destroy();
17129
17353
  }
17130
17354
  if (chart[sharedClipKey + 'm']) {
17355
+ this.markerGroup.clip();
17131
17356
  chart[sharedClipKey + 'm'] = chart[sharedClipKey + 'm'].destroy();
17132
17357
  }
17133
17358
  }
@@ -17211,8 +17436,7 @@
17211
17436
 
17212
17437
  if (seriesMarkerOptions.enabled !== false || series._hasPointMarkers) {
17213
17438
 
17214
- i = points.length;
17215
- while (i--) {
17439
+ for (i = 0; i < points.length; i++) {
17216
17440
  point = points[i];
17217
17441
  plotY = point.plotY;
17218
17442
  graphic = point.graphic;
@@ -17273,8 +17497,7 @@
17273
17497
  markerAttribs: function(point, state) {
17274
17498
  var seriesMarkerOptions = this.options.marker,
17275
17499
  seriesStateOptions,
17276
- pointOptions = point && point.options,
17277
- pointMarkerOptions = (pointOptions && pointOptions.marker) || {},
17500
+ pointMarkerOptions = point.marker || {},
17278
17501
  pointStateOptions,
17279
17502
  radius = pick(
17280
17503
  pointMarkerOptions.radius,
@@ -17426,6 +17649,7 @@
17426
17649
  chart.hoverSeries = null;
17427
17650
  }
17428
17651
  erase(chart.series, series);
17652
+ chart.orderSeries();
17429
17653
 
17430
17654
  // clear all members
17431
17655
  for (prop in series) {
@@ -17756,14 +17980,11 @@
17756
17980
  remover;
17757
17981
 
17758
17982
  function setInvert() {
17759
- var size = {
17760
- width: series.yAxis.len,
17761
- height: series.xAxis.len
17762
- };
17763
-
17764
17983
  each(['group', 'markerGroup'], function(groupName) {
17765
17984
  if (series[groupName]) {
17766
- series[groupName].attr(size).invert(inverted);
17985
+ series[groupName].width = series.yAxis.len;
17986
+ series[groupName].height = series.xAxis.len;
17987
+ series[groupName].invert(inverted);
17767
17988
  }
17768
17989
  });
17769
17990
  }
@@ -17923,7 +18144,7 @@
17923
18144
  }, animDuration);
17924
18145
  }
17925
18146
 
17926
- series.isDirty = series.isDirtyData = false; // means data is in accordance with what you see
18147
+ series.isDirty = false; // means data is in accordance with what you see
17927
18148
  // (See #322) series.isDirty = series.isDirtyData = false; // means data is in accordance with what you see
17928
18149
  series.hasRendered = true;
17929
18150
  },
@@ -17980,7 +18201,17 @@
17980
18201
  }, compareX);
17981
18202
  },
17982
18203
 
18204
+ /**
18205
+ * Build the k-d-tree that is used by mouse and touch interaction to get the
18206
+ * closest point. Line-like series typically have a one-dimensional tree
18207
+ * where points are searched along the X axis, while scatter-like series
18208
+ * typically search in two dimensions, X and Y.
18209
+ */
17983
18210
  buildKDTree: function() {
18211
+
18212
+ // Prevent multiple k-d-trees from being built simultaneously (#6235)
18213
+ this.buildingKdTree = true;
18214
+
17984
18215
  var series = this,
17985
18216
  dimensions = series.kdDimensions;
17986
18217
 
@@ -18021,6 +18252,7 @@
18021
18252
  dimensions,
18022
18253
  dimensions
18023
18254
  );
18255
+ series.buildingKdTree = false;
18024
18256
  }
18025
18257
  delete series.kdTree;
18026
18258
 
@@ -18078,7 +18310,7 @@
18078
18310
  return ret;
18079
18311
  }
18080
18312
 
18081
- if (!this.kdTree) {
18313
+ if (!this.kdTree && !this.buildingKdTree) {
18082
18314
  this.buildKDTree();
18083
18315
  }
18084
18316
 
@@ -18857,13 +19089,20 @@
18857
19089
  merge(true, this.options.plotOptions, options.plotOptions);
18858
19090
  }
18859
19091
 
18860
- // Setters for collections. For axes and series, each item is referred by an id. If the
18861
- // id is not found, it defaults to the first item in the collection, so setting series
18862
- // without an id, will update the first series in the chart.
19092
+ // Setters for collections. For axes and series, each item is referred
19093
+ // by an id. If the id is not found, it defaults to the corresponding
19094
+ // item in the collection, so setting one series without an id, will
19095
+ // update the first series in the chart. Setting two series without
19096
+ // an id will update the first and the second respectively (#6019)
19097
+ // // docs: New behaviour for unidentified items, add it to docs for
19098
+ // chart.update and responsive.
18863
19099
  each(['xAxis', 'yAxis', 'series'], function(coll) {
18864
19100
  if (options[coll]) {
18865
- each(splat(options[coll]), function(newOptions) {
18866
- var item = (defined(newOptions.id) && this.get(newOptions.id)) || this[coll][0];
19101
+ each(splat(options[coll]), function(newOptions, i) {
19102
+ var item = (
19103
+ defined(newOptions.id) &&
19104
+ this.get(newOptions.id)
19105
+ ) || this[coll][i];
18867
19106
  if (item && item.coll === coll) {
18868
19107
  item.update(newOptions, false);
18869
19108
  }
@@ -19010,7 +19249,8 @@
19010
19249
  seriesOptions = series.options,
19011
19250
  data = series.data,
19012
19251
  chart = series.chart,
19013
- names = series.xAxis && series.xAxis.names,
19252
+ xAxis = series.xAxis,
19253
+ names = xAxis && xAxis.hasNames && xAxis.names,
19014
19254
  dataOptions = seriesOptions.data,
19015
19255
  point,
19016
19256
  isInTheMiddle,
@@ -19924,7 +20164,7 @@
19924
20164
  ),
19925
20165
  groupPadding = categoryWidth * options.groupPadding,
19926
20166
  groupWidth = categoryWidth - 2 * groupPadding,
19927
- pointOffsetWidth = groupWidth / columnCount,
20167
+ pointOffsetWidth = groupWidth / (columnCount || 1),
19928
20168
  pointWidth = Math.min(
19929
20169
  options.maxPointWidth || xAxis.len,
19930
20170
  pick(options.pointWidth, pointOffsetWidth * (1 - 2 * options.pointPadding))
@@ -20276,7 +20516,10 @@
20276
20516
  enabled: true // Overrides auto-enabling in line series (#3647)
20277
20517
  },
20278
20518
  tooltip: {
20279
- headerFormat: '<span style="color:{point.color}">\u25CF</span> <span style="font-size: 0.85em"> {series.name}</span><br/>',
20519
+
20520
+ headerFormat: '<span style="color:{point.color}">\u25CF</span> ' +
20521
+ '<span style="font-size: 0.85em"> {series.name}</span><br/>',
20522
+
20280
20523
  pointFormat: 'x: <b>{point.x}</b><br/>y: <b>{point.y}</b><br/>'
20281
20524
  }
20282
20525
 
@@ -20969,7 +21212,6 @@
20969
21212
  * Draw the data labels
20970
21213
  */
20971
21214
  Series.prototype.drawDataLabels = function() {
20972
-
20973
21215
  var series = this,
20974
21216
  seriesOptions = series.options,
20975
21217
  options = seriesOptions.dataLabels,
@@ -21018,7 +21260,6 @@
21018
21260
  // Make the labels for each point
21019
21261
  generalOptions = options;
21020
21262
  each(points, function(point) {
21021
-
21022
21263
  var enabled,
21023
21264
  dataLabel = point.dataLabel,
21024
21265
  labelConfig,
@@ -21026,130 +21267,97 @@
21026
21267
  name,
21027
21268
  rotation,
21028
21269
  connector = point.connector,
21029
- isNew = true,
21030
- style,
21031
- moreStyle = {};
21032
-
21270
+ isNew = !dataLabel,
21271
+ style;
21033
21272
  // Determine if each data label is enabled
21273
+ // @note dataLabelAttribs (like pointAttribs) would eradicate
21274
+ // the need for dlOptions, and simplify the section below.
21034
21275
  pointOptions = point.dlOptions || (point.options && point.options.dataLabels); // dlOptions is used in treemaps
21035
21276
  enabled = pick(pointOptions && pointOptions.enabled, generalOptions.enabled) && point.y !== null; // #2282, #4641
21036
-
21037
-
21038
- // If the point is outside the plot area, destroy it. #678, #820
21039
- if (dataLabel && !enabled) {
21040
- point.dataLabel = dataLabel.destroy();
21041
-
21042
- // Individual labels are disabled if the are explicitly disabled
21043
- // in the point options, or if they fall outside the plot area.
21044
- } else if (enabled) {
21045
-
21277
+ if (enabled) {
21046
21278
  // Create individual options structure that can be extended without
21047
21279
  // affecting others
21048
21280
  options = merge(generalOptions, pointOptions);
21049
- style = options.style;
21050
-
21051
- rotation = options.rotation;
21052
-
21053
- // Get the string
21054
21281
  labelConfig = point.getLabelConfig();
21055
21282
  str = options.format ?
21056
21283
  format(options.format, labelConfig) :
21057
21284
  options.formatter.call(labelConfig, options);
21058
-
21285
+ style = options.style;
21286
+ rotation = options.rotation;
21059
21287
 
21060
21288
  // Determine the color
21061
21289
  style.color = pick(options.color, style.color, series.color, '#000000');
21290
+ // Get automated contrast color
21291
+ if (style.color === 'contrast') {
21292
+ style.color = options.inside || options.distance < 0 || !!seriesOptions.stacking ?
21293
+ renderer.getContrast(point.color || series.color) :
21294
+ '#000000';
21295
+ }
21296
+ if (seriesOptions.cursor) {
21297
+ style.cursor = seriesOptions.cursor;
21298
+ }
21062
21299
 
21063
21300
 
21064
- // update existing label
21065
- if (dataLabel) {
21066
-
21067
- if (defined(str)) {
21068
- dataLabel
21069
- .attr({
21070
- text: str
21071
- });
21072
- isNew = false;
21073
-
21074
- } else { // #1437 - the label is shown conditionally
21075
- point.dataLabel = dataLabel = dataLabel.destroy();
21076
- if (connector) {
21077
- point.connector = connector.destroy();
21078
- }
21079
- }
21080
-
21081
- // create new label
21082
- } else if (defined(str)) {
21083
- attr = {
21084
- //align: align,
21085
-
21086
- fill: options.backgroundColor,
21087
- stroke: options.borderColor,
21088
- 'stroke-width': options.borderWidth,
21089
-
21090
- r: options.borderRadius || 0,
21091
- rotation: rotation,
21092
- padding: options.padding,
21093
- zIndex: 1
21094
- };
21095
-
21096
-
21097
- // Get automated contrast color
21098
- if (style.color === 'contrast') {
21099
- moreStyle.color = options.inside || options.distance < 0 || !!seriesOptions.stacking ?
21100
- renderer.getContrast(point.color || series.color) :
21101
- '#000000';
21102
- }
21103
-
21104
- if (seriesOptions.cursor) {
21105
- moreStyle.cursor = seriesOptions.cursor;
21106
- }
21301
+ attr = {
21302
+ //align: align,
21107
21303
 
21304
+ fill: options.backgroundColor,
21305
+ stroke: options.borderColor,
21306
+ 'stroke-width': options.borderWidth,
21108
21307
 
21308
+ r: options.borderRadius || 0,
21309
+ rotation: rotation,
21310
+ padding: options.padding,
21311
+ zIndex: 1
21312
+ };
21109
21313
 
21110
- // Remove unused attributes (#947)
21111
- for (name in attr) {
21112
- if (attr[name] === undefined) {
21113
- delete attr[name];
21114
- }
21314
+ // Remove unused attributes (#947)
21315
+ for (name in attr) {
21316
+ if (attr[name] === undefined) {
21317
+ delete attr[name];
21115
21318
  }
21116
-
21319
+ }
21320
+ }
21321
+ // If the point is outside the plot area, destroy it. #678, #820
21322
+ if (dataLabel && (!enabled || !defined(str))) {
21323
+ point.dataLabel = dataLabel = dataLabel.destroy();
21324
+ if (connector) {
21325
+ point.connector = connector.destroy();
21326
+ }
21327
+ // Individual labels are disabled if the are explicitly disabled
21328
+ // in the point options, or if they fall outside the plot area.
21329
+ } else if (enabled && defined(str)) {
21330
+ // create new label
21331
+ if (!dataLabel) {
21117
21332
  dataLabel = point.dataLabel = renderer[rotation ? 'text' : 'label']( // labels don't support rotation
21118
- str,
21119
- 0, -9999,
21120
- options.shape,
21121
- null,
21122
- null,
21123
- options.useHTML,
21124
- null,
21125
- 'data-label'
21126
- )
21127
- .attr(attr);
21128
-
21333
+ str,
21334
+ 0, -9999,
21335
+ options.shape,
21336
+ null,
21337
+ null,
21338
+ options.useHTML,
21339
+ null,
21340
+ 'data-label'
21341
+ );
21129
21342
  dataLabel.addClass(
21130
21343
  'highcharts-data-label-color-' + point.colorIndex +
21131
21344
  ' ' + (options.className || '') +
21132
21345
  (options.useHTML ? 'highcharts-tracker' : '') // #3398
21133
21346
  );
21347
+ } else {
21348
+ attr.text = str;
21349
+ }
21350
+ dataLabel.attr(attr);
21134
21351
 
21135
-
21136
- // Styles must be applied before add in order to read text bounding box
21137
- dataLabel.css(extend(style, moreStyle));
21352
+ // Styles must be applied before add in order to read text bounding box
21353
+ dataLabel.css(style).shadow(options.shadow);
21138
21354
 
21139
21355
 
21356
+ if (!dataLabel.added) {
21140
21357
  dataLabel.add(dataLabelsGroup);
21141
-
21142
-
21143
- dataLabel.shadow(options.shadow);
21144
-
21145
-
21146
-
21147
- }
21148
-
21149
- if (dataLabel) {
21150
- // Now the data label is created and placed at 0,0, so we need to align it
21151
- series.alignDataLabel(point, dataLabel, options, null, isNew);
21152
21358
  }
21359
+ // Now the data label is created and placed at 0,0, so we need to align it
21360
+ series.alignDataLabel(point, dataLabel, options, null, isNew);
21153
21361
  }
21154
21362
  });
21155
21363
  }
@@ -22918,27 +23126,37 @@
22918
23126
  * Recurse over a set of options and its current values,
22919
23127
  * and store the current values in the ret object.
22920
23128
  */
22921
- function getCurrent(options, curr, ret) {
23129
+ function getCurrent(options, curr, ret, depth) {
22922
23130
  var key, i;
22923
23131
  for (key in options) {
22924
- if (inArray(key, ['series', 'xAxis', 'yAxis']) > -1) {
23132
+ if (!depth && inArray(key, ['series', 'xAxis', 'yAxis']) > -1) {
22925
23133
  options[key] = splat(options[key]);
22926
23134
 
22927
23135
  ret[key] = [];
22928
23136
  for (i = 0; i < options[key].length; i++) {
22929
23137
  ret[key][i] = {};
22930
- getCurrent(options[key][i], curr[key][i], ret[key][i]);
23138
+ getCurrent(
23139
+ options[key][i],
23140
+ curr[key][i],
23141
+ ret[key][i],
23142
+ depth + 1
23143
+ );
22931
23144
  }
22932
23145
  } else if (isObject(options[key])) {
22933
23146
  ret[key] = {};
22934
- getCurrent(options[key], curr[key] || {}, ret[key]);
23147
+ getCurrent(
23148
+ options[key],
23149
+ curr[key] || {},
23150
+ ret[key],
23151
+ depth + 1
23152
+ );
22935
23153
  } else {
22936
23154
  ret[key] = curr[key] || null;
22937
23155
  }
22938
23156
  }
22939
23157
  }
22940
23158
 
22941
- getCurrent(options, this.options, ret);
23159
+ getCurrent(options, this.options, ret, 0);
22942
23160
  return ret;
22943
23161
  };
22944
23162