highcharts-rails 5.0.6 → 5.0.7

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.
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