highcharts-rails 4.2.0 → 4.2.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 9734fc00bcd24efb69ab2624ca3102fe197ec239
4
- data.tar.gz: 166596c4c874aa229806fb2a710fef89d51448e0
3
+ metadata.gz: 2c72cc1681a1147d68993e02378628c13ea2da7f
4
+ data.tar.gz: ed6de8e4c3f8c211ea04f901ed0f4c8c1c89c8d7
5
5
  SHA512:
6
- metadata.gz: e14ab6d55b82b4661cd8fc9ecbbf8b4d8fa8bdcb7184a9ab287035abcc6432953176dcb13e390a7d17aff2dde9e4ba93a08c15298e8d42f7ee0cab38a3f6841c
7
- data.tar.gz: 4402b64121cfbb659bb291d516c0a0b936f3d85f4fc09ef2b59a3d00e04104238713bcb276e4987d506a8fdb8fffb5f6eecfa063b4285b232866f5af8eb4d721
6
+ metadata.gz: bd9e2637e11c8b4fa254e79431c264e14bcb82eec93dadcf855d333be699abbc23d797239751c2bbe1b5dcded9c383678c4e2dfdd2a277b4a46f6ed5e0f9a01d
7
+ data.tar.gz: f51303cfe27bf5c5f597bc41016c22ae4ea8c5e68892995750e22a62218c297b771a60aad923bf43e1379961f3f94670fcdc2903ab8159349a971bf47fc2ac9b
data/CHANGELOG.markdown CHANGED
@@ -1,3 +1,40 @@
1
+ # 4.2.2 / 2016-04-09
2
+
3
+ * Updated Highcharts to 4.2.2 (2016-02-04)
4
+ * Added new option, linecap, to allow round corners on solid gauge.
5
+ * Changed chart.load event to wait for external symbol images, so that their size is set correctly in the SVG. This comes in handy for export and server generated images.
6
+ * Fixed #1977, bubble series shadow was not moved when moving bubbles.
7
+ * Fixed #4077, panning was not supported on inverted charts.
8
+ * Fixed #4086, tick marks and labels were not trimmed to axis extremes when tickPositions option was used.
9
+ * Fixed #4573, wrong number rounding in some cases.
10
+ * Fixed #4622, updating point didn't change legend's item.
11
+ * Fixed #4759, a regression which did not update point markers in point.update().
12
+ * Fixed #4779, data labels were not cropped when rotated.
13
+ * Fixed #4859, no easy way to catch offline export failed exception.
14
+ * Fixed #4861, where offline export would fail in some cases with embedded images.
15
+ * Fixed #4870, column width calculation not correct on logarithmic X axis.
16
+ * Fixed #4886, pie slicing failed and tooltip disappeared too quickly on Windows phone.
17
+ * Fixed #4888, fillOpacity of 0 did not take effect on area charts.
18
+ * Fixed #4889, plotLine and plotBand should be rendered with higher zIndex than frames in 3D view.
19
+ * Fixed #4890, originalEvent was missing from selection event since 4.2.0.
20
+ * Fixed #4898, rounding issues with Y axis causing columns to exceed threshold value, and plot lines to be filtered out.
21
+ * Fixed #4904, async loading of points kept running after chart was destroyed in boost module.
22
+ * Fixed #4905, pie data labels were not hidden when scaling down.
23
+ * Fixed #4906, chart did not zoom when drag and drop was dropped on a different chart.
24
+ * Fixed #4911, false detection of overlapping data labels in multiple panes.
25
+ * Fixed #4912, specific export data label style didn't take effect for pies.
26
+ * Fixed #4913, regression in box size detection causing chart to overflow the container in some cases.
27
+ * Fixed #4914, boost module loadingDiv was destroyed before redraw was finished.
28
+ * Fixed #4918, spline curved wrongly when plotX or Y had a value of 0.
29
+ * Fixed #4920, polar X axis labels misaligned after chart redraw.
30
+ * Fixed #4927, crosshair only worked for one Y axis.
31
+ * Fixed #4928, HTML label lengths were not reset on resize.
32
+ * Fixed #4929, halo covered point and disallowed point click in IE8.
33
+ * Fixed #4938, HTML elements were missing visibility and opacity setters.
34
+ * Fixed #4951, X axis daily ticks did not render on midnight around a DST crossover when the getTimezoneOffset callback was used.
35
+ * Fixed #4955, export button was hidden by category crosshair.
36
+ * Fixed #2069, null points in stacked areas were rendered as zero instead of gaps.
37
+
1
38
  # 4.2.0 / 2016-04-09
2
39
 
3
40
  * Updated Highcharts to 4.2.0 (2015-12-15)
@@ -2,9 +2,9 @@
2
2
  // @compilation_level SIMPLE_OPTIMIZATIONS
3
3
 
4
4
  /**
5
- * @license Highcharts JS v4.2.0 (2105-12-15)
5
+ * @license Highcharts JS v4.2.2 (2016-02-04)
6
6
  *
7
- * (c) 2009-2014 Torstein Honsi
7
+ * (c) 2009-2016 Torstein Honsi
8
8
  *
9
9
  * License: www.highcharts.com/license
10
10
  */
@@ -59,7 +59,7 @@
59
59
  charts = [],
60
60
  chartCount = 0,
61
61
  PRODUCT = 'Highcharts',
62
- VERSION = '4.2.0',
62
+ VERSION = '4.2.2',
63
63
 
64
64
  // some constants for frequently used strings
65
65
  DIV = 'div',
@@ -312,12 +312,12 @@
312
312
  i,
313
313
  start = fromD.split(' '),
314
314
  end = [].concat(toD), // copy
315
- startBaseLine,
316
- endBaseLine,
315
+ isArea = elem.isArea,
316
+ positionFactor = isArea ? 2 : 1,
317
317
  sixify = function (arr) { // in splines make move points have six parameters like bezier curves
318
318
  i = arr.length;
319
319
  while (i--) {
320
- if (arr[i] === M) {
320
+ if (arr[i] === M || arr[i] === L) {
321
321
  arr.splice(i + 1, 0, arr[i + 1], arr[i + 2], arr[i + 1], arr[i + 2]);
322
322
  }
323
323
  }
@@ -328,39 +328,49 @@
328
328
  sixify(end);
329
329
  }
330
330
 
331
- // pull out the base lines before padding
332
- if (elem.isArea) {
333
- startBaseLine = start.splice(start.length - 6, 6);
334
- endBaseLine = end.splice(end.length - 6, 6);
335
- }
336
-
337
- // if shifting points, prepend a dummy point to the end path
331
+ // If shifting points, prepend a dummy point to the end path. For areas,
332
+ // prepend both at the beginning and end of the path.
338
333
  if (shift <= end.length / numParams && start.length === end.length) {
339
334
  while (shift--) {
340
- end = [].concat(end).splice(0, numParams).concat(end);
335
+ end = end.slice(0, numParams).concat(end);
336
+ if (isArea) {
337
+ end = end.concat(end.slice(end.length - numParams));
338
+ }
341
339
  }
342
340
  }
343
341
  elem.shift = 0; // reset for following animations
344
342
 
345
- // copy and append last point until the length matches the end length
343
+
344
+ // Copy and append last point until the length matches the end length
346
345
  if (start.length) {
347
346
  endLength = end.length;
348
347
  while (start.length < endLength) {
349
348
 
350
- //bezier && sixify(start);
351
- slice = [].concat(start).splice(start.length - numParams, numParams);
352
- if (bezier) { // disable first control point
349
+ // Pull out the slice that is going to be appended or inserted. In a line graph,
350
+ // the positionFactor is 1, and the last point is sliced out. In an area graph,
351
+ // the positionFactor is 2, causing the middle two points to be sliced out, since
352
+ // an area path starts at left, follows the upper path then turns and follows the
353
+ // bottom back.
354
+ slice = start.slice().splice(
355
+ (start.length / positionFactor) - numParams,
356
+ numParams * positionFactor
357
+ );
358
+
359
+ // Disable first control point
360
+ if (bezier) {
353
361
  slice[numParams - 6] = slice[numParams - 2];
354
362
  slice[numParams - 5] = slice[numParams - 1];
355
363
  }
356
- start = start.concat(slice);
364
+
365
+ // Now insert the slice, either in the middle (for areas) or at the end (for lines)
366
+ [].splice.apply(
367
+ start,
368
+ [(start.length / positionFactor), 0].concat(slice)
369
+ );
370
+
357
371
  }
358
372
  }
359
373
 
360
- if (startBaseLine) { // append the base lines for areas
361
- start = start.concat(startBaseLine);
362
- end = end.concat(endBaseLine);
363
- }
364
374
  return [start, end];
365
375
  }
366
376
  }; // End of Fx prototype
@@ -1001,24 +1011,55 @@
1001
1011
  * Format a number and return a string based on input settings
1002
1012
  * @param {Number} number The input number to format
1003
1013
  * @param {Number} decimals The amount of decimals
1004
- * @param {String} decPoint The decimal point, defaults to the one given in the lang options
1014
+ * @param {String} decimalPoint The decimal point, defaults to the one given in the lang options
1005
1015
  * @param {String} thousandsSep The thousands separator, defaults to the one given in the lang options
1006
1016
  */
1007
- Highcharts.numberFormat = function (number, decimals, decPoint, thousandsSep) {
1017
+ Highcharts.numberFormat = function (number, decimals, decimalPoint, thousandsSep) {
1018
+
1019
+ number = +number || 0;
1020
+
1008
1021
  var lang = defaultOptions.lang,
1009
- // http://kevin.vanzonneveld.net/techblog/article/javascript_equivalent_for_phps_number_format/
1010
- n = +number || 0,
1011
- c = decimals === -1 ?
1012
- Math.min((n.toString().split('.')[1] || '').length, 20) : // Preserve decimals. Not huge numbers (#3793).
1013
- (isNaN(decimals = Math.abs(decimals)) ? 2 : decimals),
1014
- d = decPoint === undefined ? lang.decimalPoint : decPoint,
1015
- t = thousandsSep === undefined ? lang.thousandsSep : thousandsSep,
1016
- s = n < 0 ? '-' : '',
1017
- i = String(pInt(n = mathAbs(n).toFixed(c))),
1018
- j = i.length > 3 ? i.length % 3 : 0;
1019
-
1020
- return (s + (j ? i.substr(0, j) + t : '') + i.substr(j).replace(/(\d{3})(?=\d)/g, '$1' + t) +
1021
- (c ? d + mathAbs(n - i).toFixed(c).slice(2) : ''));
1022
+ origDec = (number.toString().split('.')[1] || '').length,
1023
+ decimalComponent,
1024
+ strinteger,
1025
+ thousands,
1026
+ absNumber = Math.abs(number),
1027
+ ret;
1028
+
1029
+ if (decimals === -1) {
1030
+ decimals = Math.min(origDec, 20); // Preserve decimals. Not huge numbers (#3793).
1031
+ } else if (isNaN(decimals)) {
1032
+ decimals = 2;
1033
+ }
1034
+
1035
+ // A string containing the positive integer component of the number
1036
+ strinteger = String(pInt(absNumber.toFixed(decimals)));
1037
+
1038
+ // Leftover after grouping into thousands. Can be 0, 1 or 3.
1039
+ thousands = strinteger.length > 3 ? strinteger.length % 3 : 0;
1040
+
1041
+ // Language
1042
+ decimalPoint = pick(decimalPoint, lang.decimalPoint);
1043
+ thousandsSep = pick(thousandsSep, lang.thousandsSep);
1044
+
1045
+ // Start building the return
1046
+ ret = number < 0 ? '-' : '';
1047
+
1048
+ // Add the leftover after grouping into thousands. For example, in the number 42 000 000,
1049
+ // this line adds 42.
1050
+ ret += thousands ? strinteger.substr(0, thousands) + thousandsSep : '';
1051
+
1052
+ // Add the remaining thousands groups, joined by the thousands separator
1053
+ ret += strinteger.substr(thousands).replace(/(\d{3})(?=\d)/g, '$1' + thousandsSep);
1054
+
1055
+ // Add the decimal point and the decimal component
1056
+ if (+decimals) {
1057
+ // Get the decimal component, and add power to avoid rounding errors with float numbers (#4573)
1058
+ decimalComponent = Math.abs(absNumber - strinteger + Math.pow(10, -Math.max(decimals, origDec) - 1));
1059
+ ret += decimalPoint + decimalComponent.toFixed(decimals).slice(2);
1060
+ }
1061
+
1062
+ return ret;
1022
1063
  };
1023
1064
 
1024
1065
  /**
@@ -1033,7 +1074,18 @@
1033
1074
  * Internal method to return CSS value for given element and property
1034
1075
  */
1035
1076
  getStyle = function (el, prop) {
1036
- var style = win.getComputedStyle(el, undefined);
1077
+
1078
+ var style;
1079
+
1080
+ // For width and height, return the actual inner pixel size (#4913)
1081
+ if (prop === 'width') {
1082
+ return Math.min(el.offsetWidth, el.scrollWidth) - getStyle(el, 'padding-left') - getStyle(el, 'padding-right');
1083
+ } else if (prop === 'height') {
1084
+ return Math.min(el.offsetHeight, el.scrollHeight) - getStyle(el, 'padding-top') - getStyle(el, 'padding-bottom');
1085
+ }
1086
+
1087
+ // Otherwise, get the computed style
1088
+ style = win.getComputedStyle(el, undefined);
1037
1089
  return style && pInt(style.getPropertyValue(prop));
1038
1090
  };
1039
1091
 
@@ -1481,7 +1533,7 @@
1481
1533
  useUTC: true,
1482
1534
  //timezoneOffset: 0,
1483
1535
  canvasToolsURL: 'http://code.highcharts.com/modules/canvas-tools.js',
1484
- VMLRadialGradientURL: 'http://code.highcharts.com/4.2.0/gfx/vml-radial-gradient.png'
1536
+ VMLRadialGradientURL: 'http://code.highcharts.com/4.2.2/gfx/vml-radial-gradient.png'
1485
1537
  },
1486
1538
  chart: {
1487
1539
  //animation: true,
@@ -2181,8 +2233,6 @@
2181
2233
  * and apply strokes to the copy.
2182
2234
  *
2183
2235
  * Contrast checks at http://jsfiddle.net/highcharts/43soe9m1/2/
2184
- *
2185
- * docs: update default, document the polyfill and the limitations on hex colors and pixel values, document contrast pseudo-color
2186
2236
  */
2187
2237
  applyTextShadow: function (textShadow) {
2188
2238
  var elem = this.element,
@@ -2277,7 +2327,8 @@
2277
2327
  element = this.element,
2278
2328
  hasSetSymbolSize,
2279
2329
  ret = this,
2280
- skipAttr;
2330
+ skipAttr,
2331
+ setter;
2281
2332
 
2282
2333
  // single key-value pair
2283
2334
  if (typeof hash === 'string' && val !== UNDEFINED) {
@@ -2312,12 +2363,13 @@
2312
2363
  }
2313
2364
 
2314
2365
  if (!skipAttr) {
2315
- (this[key + 'Setter'] || this._defaultSetter).call(this, value, key, element);
2316
- }
2366
+ setter = this[key + 'Setter'] || this._defaultSetter;
2367
+ setter.call(this, value, key, element);
2317
2368
 
2318
- // Let the shadow follow the main element
2319
- if (this.shadows && /^(width|height|visibility|x|y|d|transform|cx|cy|r)$/.test(key)) {
2320
- this.updateShadows(key, value);
2369
+ // Let the shadow follow the main element
2370
+ if (this.shadows && /^(width|height|visibility|x|y|d|transform|cx|cy|r)$/.test(key)) {
2371
+ this.updateShadows(key, value, setter);
2372
+ }
2321
2373
  }
2322
2374
  }
2323
2375
 
@@ -2338,15 +2390,25 @@
2338
2390
  return ret;
2339
2391
  },
2340
2392
 
2341
- updateShadows: function (key, value) {
2393
+ /**
2394
+ * Update the shadow elements with new attributes
2395
+ * @param {String} key The attribute name
2396
+ * @param {String|Number} value The value of the attribute
2397
+ * @param {Function} setter The setter function, inherited from the parent wrapper
2398
+ * @returns {undefined}
2399
+ */
2400
+ updateShadows: function (key, value, setter) {
2342
2401
  var shadows = this.shadows,
2343
2402
  i = shadows.length;
2403
+
2344
2404
  while (i--) {
2345
- shadows[i].setAttribute(
2346
- key,
2405
+ setter.call(
2406
+ null,
2347
2407
  key === 'height' ?
2348
- Math.max(value - (shadows[i].cutHeight || 0), 0) :
2349
- key === 'd' ? this.d : value
2408
+ Math.max(value - (shadows[i].cutHeight || 0), 0) :
2409
+ key === 'd' ? this.d : value,
2410
+ key,
2411
+ shadows[i]
2350
2412
  );
2351
2413
  }
2352
2414
  },
@@ -2717,7 +2779,7 @@
2717
2779
  */
2718
2780
  getBBox: function (reload, rot) {
2719
2781
  var wrapper = this,
2720
- bBox,// = wrapper.bBox,
2782
+ bBox, // = wrapper.bBox,
2721
2783
  renderer = wrapper.renderer,
2722
2784
  width,
2723
2785
  height,
@@ -3286,6 +3348,7 @@
3286
3348
  renderer.gradients = {}; // Object where gradient SvgElements are stored
3287
3349
  renderer.cache = {}; // Cache for numerical bounding boxes
3288
3350
  renderer.cacheKeys = [];
3351
+ renderer.imgCount = 0;
3289
3352
 
3290
3353
  renderer.setSize(width, height, false);
3291
3354
 
@@ -3832,12 +3895,11 @@
3832
3895
  var attr = isObject(x) ? x : { x: x, y: y, r: r },
3833
3896
  wrapper = this.createElement('circle');
3834
3897
 
3835
- wrapper.xSetter = function (value) {
3836
- this.element.setAttribute('cx', value);
3837
- };
3838
- wrapper.ySetter = function (value) {
3839
- this.element.setAttribute('cy', value);
3898
+ // Setting x or y translates to cx and cy
3899
+ wrapper.xSetter = wrapper.ySetter = function (value, key, element) {
3900
+ element.setAttribute('c' + key, value);
3840
3901
  };
3902
+
3841
3903
  return wrapper.attr(attr);
3842
3904
  },
3843
3905
 
@@ -3997,7 +4059,8 @@
3997
4059
  */
3998
4060
  symbol: function (symbol, x, y, width, height, options) {
3999
4061
 
4000
- var obj,
4062
+ var ren = this,
4063
+ obj,
4001
4064
 
4002
4065
  // get the symbol definition function
4003
4066
  symbolFn = this.symbols[symbol],
@@ -4091,10 +4154,17 @@
4091
4154
  if (this.parentNode) {
4092
4155
  this.parentNode.removeChild(this);
4093
4156
  }
4157
+
4158
+ // Fire the load event when all external images are loaded
4159
+ ren.imgCount--;
4160
+ if (!ren.imgCount) {
4161
+ charts[ren.chartIndex].onload();
4162
+ }
4094
4163
  },
4095
4164
  src: imageSrc
4096
4165
  });
4097
4166
  }
4167
+ this.imgCount++;
4098
4168
  }
4099
4169
 
4100
4170
  return obj;
@@ -4772,10 +4842,10 @@
4772
4842
 
4773
4843
  if (elem.tagName === 'SPAN') {
4774
4844
 
4775
- var width,
4776
- rotation = wrapper.rotation,
4845
+ var rotation = wrapper.rotation,
4777
4846
  baseline,
4778
4847
  textWidth = pInt(wrapper.textWidth),
4848
+ whiteSpace = styles && styles.whiteSpace,
4779
4849
  currentTextTransform = [rotation, align, elem.innerHTML, wrapper.textWidth, wrapper.textAlign].join(',');
4780
4850
 
4781
4851
  if (currentTextTransform !== wrapper.cTT) { // do the calculations and DOM access only if properties changed
@@ -4788,19 +4858,24 @@
4788
4858
  wrapper.setSpanRotation(rotation, alignCorrection, baseline);
4789
4859
  }
4790
4860
 
4791
- width = pick(wrapper.elemWidth, elem.offsetWidth);
4792
-
4793
4861
  // Update textWidth
4794
- if (width > textWidth && /[ \-]/.test(elem.textContent || elem.innerText)) { // #983, #1254
4862
+ if (elem.offsetWidth > textWidth && /[ \-]/.test(elem.textContent || elem.innerText)) { // #983, #1254
4795
4863
  css(elem, {
4796
4864
  width: textWidth + PX,
4797
4865
  display: 'block',
4798
- whiteSpace: (styles && styles.whiteSpace) || 'normal' // #3331
4866
+ whiteSpace: whiteSpace || 'normal' // #3331
4867
+ });
4868
+ wrapper.hasTextWidth = true;
4869
+ } else if (wrapper.hasTextWidth) { // #4928
4870
+ css(elem, {
4871
+ width: '',
4872
+ display: '',
4873
+ whiteSpace: whiteSpace || 'nowrap'
4799
4874
  });
4800
- width = textWidth;
4875
+ wrapper.hasTextWidth = false;
4801
4876
  }
4802
4877
 
4803
- wrapper.getSpanCorrection(width, baseline, alignCorrection, rotation, align);
4878
+ wrapper.getSpanCorrection(wrapper.hasTextWidth ? textWidth : elem.offsetWidth, baseline, alignCorrection, rotation, align);
4804
4879
  }
4805
4880
 
4806
4881
  // apply position with correction
@@ -4853,7 +4928,17 @@
4853
4928
  html: function (str, x, y) {
4854
4929
  var wrapper = this.createElement('span'),
4855
4930
  element = wrapper.element,
4856
- renderer = wrapper.renderer;
4931
+ renderer = wrapper.renderer,
4932
+ addSetters = function (element, style) {
4933
+ // These properties are set as attributes on the SVG group, and as
4934
+ // identical CSS properties on the div. (#3542)
4935
+ each(['opacity', 'visibility'], function (prop) {
4936
+ wrap(element, prop + 'Setter', function (proceed, value, key, elem) {
4937
+ proceed.call(this, value, key, elem);
4938
+ style[key] = value;
4939
+ });
4940
+ });
4941
+ };
4857
4942
 
4858
4943
  // Text setter
4859
4944
  wrapper.textSetter = function (value) {
@@ -4863,6 +4948,7 @@
4863
4948
  element.innerHTML = this.textStr = value;
4864
4949
  wrapper.htmlUpdateTransform();
4865
4950
  };
4951
+ addSetters(wrapper, wrapper.element.style);
4866
4952
 
4867
4953
  // Various setters which rely on update transform
4868
4954
  wrapper.xSetter = wrapper.ySetter = wrapper.alignSetter = wrapper.rotationSetter = function (value, key) {
@@ -4952,15 +5038,7 @@
4952
5038
  parentGroup.doTransform = true;
4953
5039
  }
4954
5040
  });
4955
-
4956
- // These properties are set as attributes on the SVG group, and as
4957
- // identical CSS properties on the div. (#3542)
4958
- each(['opacity', 'visibility'], function (prop) {
4959
- wrap(parentGroup, prop + 'Setter', function (proceed, value, key, elem) {
4960
- proceed.call(this, value, key, elem);
4961
- htmlGroupStyle[key] = value;
4962
- });
4963
- });
5041
+ addSetters(parentGroup, htmlGroupStyle);
4964
5042
  });
4965
5043
 
4966
5044
  }
@@ -5523,6 +5601,7 @@
5523
5601
  renderer.gradients = {};
5524
5602
  renderer.cache = {}; // Cache for numerical bounding boxes
5525
5603
  renderer.cacheKeys = [];
5604
+ renderer.imgCount = 0;
5526
5605
 
5527
5606
 
5528
5607
  renderer.setSize(width, height, false);
@@ -5973,7 +6052,7 @@
5973
6052
  ret.push(
5974
6053
  'e',
5975
6054
  M,
5976
- x,// - innerRadius,
6055
+ x, // - innerRadius,
5977
6056
  y// - innerRadius
5978
6057
  );
5979
6058
  }
@@ -6371,13 +6450,13 @@
6371
6450
  */
6372
6451
  getMarkPath: function (x, y, tickLength, tickWidth, horiz, renderer) {
6373
6452
  return renderer.crispLine([
6374
- M,
6375
- x,
6376
- y,
6377
- L,
6378
- x + (horiz ? 0 : -tickLength),
6379
- y + (horiz ? tickLength : 0)
6380
- ], tickWidth);
6453
+ M,
6454
+ x,
6455
+ y,
6456
+ L,
6457
+ x + (horiz ? 0 : -tickLength),
6458
+ y + (horiz ? tickLength : 0)
6459
+ ], tickWidth);
6381
6460
  },
6382
6461
 
6383
6462
  /**
@@ -6561,12 +6640,8 @@
6561
6640
  path = [],
6562
6641
  addEvent,
6563
6642
  eventType,
6564
- xs,
6565
- ys,
6566
- x,
6567
- y,
6568
6643
  color = options.color,
6569
- zIndex = options.zIndex,
6644
+ zIndex = pick(options.zIndex, 0),
6570
6645
  events = options.events,
6571
6646
  attribs = {},
6572
6647
  renderer = axis.chart.renderer;
@@ -6602,9 +6677,7 @@
6602
6677
  return;
6603
6678
  }
6604
6679
  // zIndex
6605
- if (defined(zIndex)) {
6606
- attribs.zIndex = zIndex;
6607
- }
6680
+ attribs.zIndex = zIndex;
6608
6681
 
6609
6682
  // common for lines and bands
6610
6683
  if (svgElem) {
@@ -6646,40 +6719,7 @@
6646
6719
  rotation: horiz && !isBand && 90
6647
6720
  }, optionsLabel);
6648
6721
 
6649
- // add the SVG element
6650
- if (!label) {
6651
- attribs = {
6652
- align: optionsLabel.textAlign || optionsLabel.align,
6653
- rotation: optionsLabel.rotation
6654
- };
6655
- if (defined(zIndex)) {
6656
- attribs.zIndex = zIndex;
6657
- }
6658
- plotLine.label = label = renderer.text(
6659
- optionsLabel.text,
6660
- 0,
6661
- 0,
6662
- optionsLabel.useHTML
6663
- )
6664
- .attr(attribs)
6665
- .css(optionsLabel.style)
6666
- .add();
6667
- }
6668
-
6669
- // get the bounding box and align the label
6670
- // #3000 changed to better handle choice between plotband or plotline
6671
- xs = [path[1], path[4], (isBand ? path[6] : path[1])];
6672
- ys = [path[2], path[5], (isBand ? path[7] : path[2])];
6673
- x = arrayMin(xs);
6674
- y = arrayMin(ys);
6675
-
6676
- label.align(optionsLabel, false, {
6677
- x: x,
6678
- y: y,
6679
- width: arrayMax(xs) - x,
6680
- height: arrayMax(ys) - y
6681
- });
6682
- label.show();
6722
+ this.renderLabel(optionsLabel, path, isBand, zIndex);
6683
6723
 
6684
6724
  } else if (label) { // move out of sight
6685
6725
  label.hide();
@@ -6689,6 +6729,55 @@
6689
6729
  return plotLine;
6690
6730
  },
6691
6731
 
6732
+ /**
6733
+ * Render and align label for plot line or band.
6734
+ */
6735
+ renderLabel: function (optionsLabel, path, isBand, zIndex) {
6736
+ var plotLine = this,
6737
+ label = plotLine.label,
6738
+ renderer = plotLine.axis.chart.renderer,
6739
+ attribs,
6740
+ xs,
6741
+ ys,
6742
+ x,
6743
+ y;
6744
+
6745
+ // add the SVG element
6746
+ if (!label) {
6747
+ attribs = {
6748
+ align: optionsLabel.textAlign || optionsLabel.align,
6749
+ rotation: optionsLabel.rotation
6750
+ };
6751
+
6752
+ attribs.zIndex = zIndex;
6753
+
6754
+ plotLine.label = label = renderer.text(
6755
+ optionsLabel.text,
6756
+ 0,
6757
+ 0,
6758
+ optionsLabel.useHTML
6759
+ )
6760
+ .attr(attribs)
6761
+ .css(optionsLabel.style)
6762
+ .add();
6763
+ }
6764
+
6765
+ // get the bounding box and align the label
6766
+ // #3000 changed to better handle choice between plotband or plotline
6767
+ xs = [path[1], path[4], (isBand ? path[6] : path[1])];
6768
+ ys = [path[2], path[5], (isBand ? path[7] : path[2])];
6769
+ x = arrayMin(xs);
6770
+ y = arrayMin(ys);
6771
+
6772
+ label.align(optionsLabel, false, {
6773
+ x: x,
6774
+ y: y,
6775
+ width: arrayMax(xs) - x,
6776
+ height: arrayMax(ys) - y
6777
+ });
6778
+ label.show();
6779
+ },
6780
+
6692
6781
  /**
6693
6782
  * Remove the plot line or band
6694
6783
  */
@@ -7296,7 +7385,7 @@
7296
7385
  localMin = old ? axis.oldMin : axis.min,
7297
7386
  returnValue,
7298
7387
  minPixelPadding = axis.minPixelPadding,
7299
- doPostTranslate = (axis.doPostTranslate || (axis.isLog && handleLog)) && axis.lin2val;
7388
+ doPostTranslate = (axis.isOrdinal || axis.isBroken || (axis.isLog && handleLog)) && axis.lin2val;
7300
7389
 
7301
7390
  if (!localA) {
7302
7391
  localA = axis.transA;
@@ -7961,14 +8050,18 @@
7961
8050
 
7962
8051
  if (startOnTick) {
7963
8052
  this.min = roundedMin;
7964
- } else if (this.min - minPointOffset > roundedMin) {
7965
- tickPositions.shift();
8053
+ } else {
8054
+ while (this.min - minPointOffset > tickPositions[0]) {
8055
+ tickPositions.shift();
8056
+ }
7966
8057
  }
7967
8058
 
7968
8059
  if (endOnTick) {
7969
8060
  this.max = roundedMax;
7970
- } else if (this.max + minPointOffset < roundedMax) {
7971
- tickPositions.pop();
8061
+ } else {
8062
+ while (this.max + minPointOffset < tickPositions[tickPositions.length - 1]) {
8063
+ tickPositions.pop();
8064
+ }
7972
8065
  }
7973
8066
 
7974
8067
  // If no tick are left, set one tick in the middle (#3195)
@@ -8225,12 +8318,13 @@
8225
8318
  left = pick(options.left, chart.plotLeft + offsetLeft),
8226
8319
  percentRegex = /%$/;
8227
8320
 
8228
- // Check for percentage based input values
8321
+ // Check for percentage based input values. Rounding fixes problems with
8322
+ // column overflow and plot line filtering (#4898, #4899)
8229
8323
  if (percentRegex.test(height)) {
8230
- height = parseFloat(height) / 100 * chart.plotHeight;
8324
+ height = Math.round(parseFloat(height) / 100 * chart.plotHeight);
8231
8325
  }
8232
8326
  if (percentRegex.test(top)) {
8233
- top = parseFloat(top) / 100 * chart.plotHeight + chart.plotTop;
8327
+ top = Math.round(parseFloat(top) / 100 * chart.plotHeight + chart.plotTop);
8234
8328
  }
8235
8329
 
8236
8330
  // Expose basic values to use in Series object and navigator
@@ -8454,7 +8548,10 @@
8454
8548
  }
8455
8549
 
8456
8550
  // Set the explicit or automatic label alignment
8457
- this.labelAlign = attr.align = labelOptions.align || this.autoLabelAlign(this.labelRotation);
8551
+ this.labelAlign = labelOptions.align || this.autoLabelAlign(this.labelRotation);
8552
+ if (this.labelAlign) {
8553
+ attr.align = this.labelAlign;
8554
+ }
8458
8555
 
8459
8556
  // Apply general and specific CSS
8460
8557
  each(tickPositions, function (pos) {
@@ -8983,9 +9080,7 @@
8983
9080
  // Disabled in options
8984
9081
  !this.crosshair ||
8985
9082
  // Snap
8986
- ((defined(point) || !pick(options.snap, true)) === false) ||
8987
- // Not on this axis (#4095, #2888)
8988
- (point && point.series && point.series[this.coll] !== this)
9083
+ ((defined(point) || !pick(options.snap, true)) === false)
8989
9084
  ) {
8990
9085
  this.hideCrosshair();
8991
9086
 
@@ -9122,6 +9217,7 @@
9122
9217
  var time = minDate.getTime(),
9123
9218
  minMonth = minDate[getMonth](),
9124
9219
  minDateDate = minDate[getDate](),
9220
+ variableDayLength = !useUTC || !!getTimezoneOffset, // #4951
9125
9221
  localTimezoneOffset = (timeUnits.day +
9126
9222
  (useUTC ? getTZOffset(minDate) : minDate.getTimezoneOffset() * 60 * 1000)
9127
9223
  ) % timeUnits.day; // #950, #3359
@@ -9140,7 +9236,7 @@
9140
9236
 
9141
9237
  // if we're using global time, the interval is not fixed as it jumps
9142
9238
  // one hour at the DST crossover
9143
- } else if (!useUTC && (interval === timeUnits.day || interval === timeUnits.week)) {
9239
+ } else if (variableDayLength && (interval === timeUnits.day || interval === timeUnits.week)) {
9144
9240
  time = makeTime(minYear, minMonth, minDateDate +
9145
9241
  i * count * (interval === timeUnits.day ? 1 : 7));
9146
9242
 
@@ -9744,11 +9840,11 @@
9744
9840
  this.isHidden = false;
9745
9841
  }
9746
9842
  fireEvent(chart, 'tooltipRefresh', {
9747
- text: text,
9748
- x: x + chart.plotLeft,
9749
- y: y + chart.plotTop,
9750
- borderColor: borderColor
9751
- });
9843
+ text: text,
9844
+ x: x + chart.plotLeft,
9845
+ y: y + chart.plotTop,
9846
+ borderColor: borderColor
9847
+ });
9752
9848
  },
9753
9849
 
9754
9850
  /**
@@ -9972,9 +10068,9 @@
9972
10068
  */
9973
10069
  getCoordinates: function (e) {
9974
10070
  var coordinates = {
9975
- xAxis: [],
9976
- yAxis: []
9977
- };
10071
+ xAxis: [],
10072
+ yAxis: []
10073
+ };
9978
10074
 
9979
10075
  each(this.chart.axes, function (axis) {
9980
10076
  coordinates[axis.isXAxis ? 'xAxis' : 'yAxis'].push({
@@ -10000,14 +10096,13 @@
10000
10096
  hoverPoint = chart.hoverPoint,
10001
10097
  hoverSeries = chart.hoverSeries,
10002
10098
  i,
10003
- distance = Number.MAX_VALUE, // #4511
10099
+ distance = [Number.MAX_VALUE, Number.MAX_VALUE], // #4511
10004
10100
  anchor,
10005
10101
  noSharedTooltip,
10006
10102
  stickToHoverSeries,
10007
10103
  directTouch,
10008
- pointDistance,
10009
10104
  kdpoints = [],
10010
- kdpoint,
10105
+ kdpoint = [],
10011
10106
  kdpointT;
10012
10107
 
10013
10108
  // For hovering over the empty parts of the plot area (hoverSeries is undefined).
@@ -10025,7 +10120,7 @@
10025
10120
  // search the k-d tree.
10026
10121
  stickToHoverSeries = hoverSeries && (shared ? hoverSeries.noSharedTooltip : hoverSeries.directTouch);
10027
10122
  if (stickToHoverSeries && hoverPoint) {
10028
- kdpoint = hoverPoint;
10123
+ kdpoint = [hoverPoint];
10029
10124
 
10030
10125
  // Handle shared tooltip or cases where a series is not yet hovered
10031
10126
  } else {
@@ -10043,42 +10138,50 @@
10043
10138
  });
10044
10139
  // Find absolute nearest point
10045
10140
  each(kdpoints, function (p) {
10046
- pointDistance = !shared && p.series.kdDimensions === 1 ? p.dist : p.distX; // #4645
10047
-
10048
- if (p && typeof pointDistance === 'number' && pointDistance < distance) {
10049
- distance = pointDistance;
10050
- kdpoint = p;
10141
+ if (p) {
10142
+ // Store both closest points, using point.dist and point.distX comparisons (#4645):
10143
+ each(['dist', 'distX'], function (dist, k) {
10144
+ if (typeof p[dist] === 'number' && p[dist] < distance[k]) {
10145
+ distance[k] = p[dist];
10146
+ kdpoint[k] = p;
10147
+ }
10148
+ });
10051
10149
  }
10052
10150
  });
10053
10151
  }
10054
10152
 
10153
+ // Remove points with different x-positions, required for shared tooltip and crosshairs (#4645):
10154
+ if (shared) {
10155
+ i = kdpoints.length;
10156
+ while (i--) {
10157
+ if (kdpoints[i].clientX !== kdpoint[1].clientX || kdpoints[i].series.noSharedTooltip) {
10158
+ kdpoints.splice(i, 1);
10159
+ }
10160
+ }
10161
+ }
10162
+
10055
10163
  // Refresh tooltip for kdpoint if new hover point or tooltip was hidden // #3926, #4200
10056
- if (kdpoint && (kdpoint !== this.prevKDPoint || (tooltip && tooltip.isHidden))) {
10164
+ if (kdpoint[0] && (kdpoint[0] !== this.prevKDPoint || (tooltip && tooltip.isHidden))) {
10057
10165
  // Draw tooltip if necessary
10058
- if (shared && !kdpoint.series.noSharedTooltip) {
10059
- i = kdpoints.length;
10060
- while (i--) {
10061
- if (kdpoints[i].clientX !== kdpoint.clientX || kdpoints[i].series.noSharedTooltip) {
10062
- kdpoints.splice(i, 1);
10063
- }
10064
- }
10166
+ if (shared && !kdpoint[0].series.noSharedTooltip) {
10065
10167
  if (kdpoints.length && tooltip) {
10066
10168
  tooltip.refresh(kdpoints, e);
10067
10169
  }
10068
10170
 
10069
10171
  // Do mouseover on all points (#3919, #3985, #4410)
10070
10172
  each(kdpoints, function (point) {
10071
- point.onMouseOver(e, point !== ((hoverSeries && hoverSeries.directTouch && hoverPoint) || kdpoint));
10173
+ point.onMouseOver(e, point !== ((hoverSeries && hoverSeries.directTouch && hoverPoint) || kdpoint[0]));
10072
10174
  });
10175
+ this.prevKDPoint = kdpoint[1];
10073
10176
  } else {
10074
10177
  if (tooltip) {
10075
- tooltip.refresh(kdpoint, e);
10178
+ tooltip.refresh(kdpoint[0], e);
10076
10179
  }
10077
10180
  if (!hoverSeries || !hoverSeries.directTouch) { // #4448
10078
- kdpoint.onMouseOver(e);
10181
+ kdpoint[0].onMouseOver(e);
10079
10182
  }
10183
+ this.prevKDPoint = kdpoint[0];
10080
10184
  }
10081
- this.prevKDPoint = kdpoint;
10082
10185
 
10083
10186
  // Update positions (regardless of kdpoint or hoverPoint)
10084
10187
  } else {
@@ -10100,11 +10203,17 @@
10100
10203
  }
10101
10204
 
10102
10205
  // Crosshair
10103
- each(chart.axes, function (axis) {
10104
- axis.drawCrosshair(e, pick(kdpoint, hoverPoint));
10206
+ each(shared ? kdpoints : [pick(kdpoint[1], hoverPoint)], function (point) {
10207
+ var series = point && point.series;
10208
+ if (series) {
10209
+ each(['xAxis', 'yAxis', 'colorAxis'], function (coll) {
10210
+ if (series[coll]) {
10211
+ series[coll].drawCrosshair(e, point);
10212
+ }
10213
+ });
10214
+ }
10105
10215
  });
10106
10216
 
10107
-
10108
10217
  },
10109
10218
 
10110
10219
 
@@ -10329,6 +10438,7 @@
10329
10438
 
10330
10439
  if (this.selectionMarker) {
10331
10440
  var selectionData = {
10441
+ originalEvent: e, // #4890
10332
10442
  xAxis: [],
10333
10443
  yAxis: []
10334
10444
  },
@@ -10422,9 +10532,9 @@
10422
10532
  /**
10423
10533
  * When mouse leaves the container, hide the tooltip.
10424
10534
  */
10425
- onContainerMouseLeave: function () {
10535
+ onContainerMouseLeave: function (e) {
10426
10536
  var chart = charts[hoverChartIndex];
10427
- if (chart) {
10537
+ if (chart && (e.relatedTarget || e.toElement)) { // #4886, MS Touch end fires mouseleave but with no related target
10428
10538
  chart.pointer.reset();
10429
10539
  chart.pointer.chartPosition = null; // also reset the chart position, used in #149 fix
10430
10540
  }
@@ -10435,7 +10545,9 @@
10435
10545
 
10436
10546
  var chart = this.chart;
10437
10547
 
10438
- hoverChartIndex = chart.index;
10548
+ if (!defined(hoverChartIndex) || !charts[hoverChartIndex].mouseIsDown) {
10549
+ hoverChartIndex = chart.index;
10550
+ }
10439
10551
 
10440
10552
  e = this.normalize(e);
10441
10553
  e.returnValue = false; // #2251, #3224
@@ -10476,7 +10588,7 @@
10476
10588
  var series = this.chart.hoverSeries,
10477
10589
  relatedTarget = e.relatedTarget || e.toElement;
10478
10590
 
10479
- if (series && !series.options.stickyTracking &&
10591
+ if (series && relatedTarget && !series.options.stickyTracking && // #4886
10480
10592
  !this.inClass(relatedTarget, PREFIX + 'tooltip') &&
10481
10593
  !this.inClass(relatedTarget, PREFIX + 'series-' + series.index)) { // #2499, #4465
10482
10594
  series.onMouseOut();
@@ -11182,15 +11294,15 @@
11182
11294
  legend.setItemEvents(item, li, useHTML, itemStyle, itemHiddenStyle);
11183
11295
  }
11184
11296
 
11185
- // Colorize the items
11186
- legend.colorizeItem(item, item.visible);
11187
-
11188
11297
  // add the HTML checkbox on top
11189
11298
  if (showCheckbox) {
11190
11299
  legend.createCheckboxForItem(item);
11191
11300
  }
11192
11301
  }
11193
11302
 
11303
+ // Colorize the items
11304
+ legend.colorizeItem(item, item.visible);
11305
+
11194
11306
  // Always update the text
11195
11307
  legend.setText(item);
11196
11308
 
@@ -13068,8 +13180,7 @@
13068
13180
  */
13069
13181
  firstRender: function () {
13070
13182
  var chart = this,
13071
- options = chart.options,
13072
- callback = chart.callback;
13183
+ options = chart.options;
13073
13184
 
13074
13185
  // Check whether the chart is ready to render
13075
13186
  if (!chart.isReadyToRender()) {
@@ -13113,24 +13224,36 @@
13113
13224
 
13114
13225
  // add canvas
13115
13226
  chart.renderer.draw();
13116
- // run callbacks
13117
- if (callback) {
13118
- callback.apply(chart, [chart]);
13227
+
13228
+ // Fire the load event if there are no external images
13229
+ if (!chart.renderer.imgCount) {
13230
+ chart.onload();
13119
13231
  }
13120
- each(chart.callbacks, function (fn) {
13121
- if (chart.index !== UNDEFINED) { // Chart destroyed in its own callback (#3600)
13122
- fn.apply(chart, [chart]);
13123
- }
13124
- });
13125
-
13126
- // Fire the load event
13127
- fireEvent(chart, 'load');
13128
13232
 
13129
13233
  // If the chart was rendered outside the top container, put it back in (#3679)
13130
13234
  chart.cloneRenderTo(true);
13131
13235
 
13132
13236
  },
13133
13237
 
13238
+ /**
13239
+ * On chart load
13240
+ */
13241
+ onload: function () {
13242
+ var chart = this;
13243
+
13244
+ // Run callbacks
13245
+ each([this.callback].concat(this.callbacks), function (fn) {
13246
+ if (fn && chart.index !== undefined) { // Chart destroyed in its own callback (#3600)
13247
+ fn.apply(chart, [chart]);
13248
+ }
13249
+ });
13250
+
13251
+ // Fire the load event if there are no external images
13252
+ if (!chart.renderer.imgCount) {
13253
+ fireEvent(chart, 'load');
13254
+ }
13255
+ },
13256
+
13134
13257
  /**
13135
13258
  * Creates arrays for spacing and margin from given options.
13136
13259
  */
@@ -13237,6 +13360,7 @@
13237
13360
  if (pointValKey) {
13238
13361
  point.y = point[pointValKey];
13239
13362
  }
13363
+ point.isNull = point.y === null;
13240
13364
 
13241
13365
  // If no x is set by now, get auto incremented value. All points must have an
13242
13366
  // x value, however the y value can be null to create a gap in the series
@@ -13632,51 +13756,7 @@
13632
13756
  this.xIncrement = xIncrement + pointInterval;
13633
13757
  return xIncrement;
13634
13758
  },
13635
-
13636
- /**
13637
- * Divide the series data into segments divided by null values.
13638
- */
13639
- getSegments: function () {
13640
- var series = this,
13641
- lastNull = -1,
13642
- segments = [],
13643
- i,
13644
- points = series.points,
13645
- pointsLength = points.length;
13646
-
13647
- if (pointsLength) { // no action required for []
13648
-
13649
- // if connect nulls, just remove null points
13650
- if (series.options.connectNulls) {
13651
- i = pointsLength;
13652
- while (i--) {
13653
- if (points[i].y === null) {
13654
- points.splice(i, 1);
13655
- }
13656
- }
13657
- if (points.length) {
13658
- segments = [points];
13659
- }
13660
-
13661
- // else, split on null points
13662
- } else {
13663
- each(points, function (point, i) {
13664
- if (point.y === null) {
13665
- if (i > lastNull + 1) {
13666
- segments.push(points.slice(lastNull + 1, i));
13667
- }
13668
- lastNull = i;
13669
- } else if (i === pointsLength - 1) { // last value
13670
- segments.push(points.slice(lastNull + 1, i + 1));
13671
- }
13672
- });
13673
- }
13674
- }
13675
-
13676
- // register it
13677
- series.segments = segments;
13678
- },
13679
-
13759
+
13680
13760
  /**
13681
13761
  * Set the series options by merging from the options tree
13682
13762
  * @param {Object} itemOptions
@@ -13892,8 +13972,7 @@
13892
13972
  }
13893
13973
 
13894
13974
  series.data = [];
13895
- series.options.data = data;
13896
- //series.zData = zData;
13975
+ series.options.data = series.userOptions.data = data;
13897
13976
 
13898
13977
  // destroy old points
13899
13978
  i = oldDataLength;
@@ -13915,7 +13994,7 @@
13915
13994
 
13916
13995
  // Typically for pie series, points need to be processed and generated
13917
13996
  // prior to rendering the legend
13918
- if (options.legendType === 'point') { // docs: legendType now supported on more series types (at least column and pie)
13997
+ if (options.legendType === 'point') {
13919
13998
  this.processData();
13920
13999
  this.generatePoints();
13921
14000
  }
@@ -13946,6 +14025,8 @@
13946
14025
  getExtremesFromAll = series.getExtremesFromAll || options.getExtremesFromAll, // #4599
13947
14026
  isCartesian = series.isCartesian,
13948
14027
  xExtremes,
14028
+ val2lin = xAxis && xAxis.val2lin,
14029
+ isLog = xAxis && xAxis.isLog,
13949
14030
  min,
13950
14031
  max;
13951
14032
 
@@ -13981,8 +14062,11 @@
13981
14062
 
13982
14063
 
13983
14064
  // Find the closest distance between processed points
13984
- for (i = processedXData.length - 1; i >= 0; i--) {
13985
- distance = processedXData[i] - processedXData[i - 1];
14065
+ i = processedXData.length || 1;
14066
+ while (--i) {
14067
+ distance = isLog ?
14068
+ val2lin(processedXData[i]) - val2lin(processedXData[i - 1]) :
14069
+ processedXData[i] - processedXData[i - 1];
13986
14070
 
13987
14071
  if (distance > 0 && (closestPointRange === UNDEFINED || distance < closestPointRange)) {
13988
14072
  closestPointRange = distance;
@@ -14204,7 +14288,7 @@
14204
14288
 
14205
14289
 
14206
14290
  // Calculate the bottom y value for stacked series
14207
- if (stacking && series.visible && stack && stack[xValue]) {
14291
+ if (stacking && series.visible && !point.isNull && stack && stack[xValue]) {
14208
14292
  stackIndicator = series.getStackIndicator(stackIndicator, xValue, series.index);
14209
14293
  pointStack = stack[xValue];
14210
14294
  stackValues = pointStack.points[stackIndicator.key];
@@ -14261,11 +14345,16 @@
14261
14345
  lastPlotX = plotX;
14262
14346
 
14263
14347
  }
14264
-
14265
14348
  series.closestPointRangePx = closestPointRangePx;
14349
+ },
14266
14350
 
14267
- // now that we have the cropped data, build the segments
14268
- series.getSegments();
14351
+ /**
14352
+ * Return the series points with null points filtered out
14353
+ */
14354
+ getValidPoints: function () {
14355
+ return grep(this.points, function (point) {
14356
+ return !point.isNull;
14357
+ });
14269
14358
  },
14270
14359
 
14271
14360
  /**
@@ -14428,6 +14517,7 @@
14428
14517
 
14429
14518
  if (graphic) { // update
14430
14519
  graphic[isInside ? 'show' : 'hide'](true) // Since the marker group isn't clipped, each individual marker must be toggled
14520
+ .attr(pointAttr) // #4759
14431
14521
  .animate(extend({
14432
14522
  x: plotX - radius,
14433
14523
  y: plotY - radius
@@ -14715,92 +14805,89 @@
14715
14805
  },
14716
14806
 
14717
14807
  /**
14718
- * Return the graph path of a segment
14808
+ * Get the graph path
14719
14809
  */
14720
- getSegmentPath: function (segment) {
14810
+ getGraphPath: function (points, nullsAsZeroes, connectCliffs) {
14721
14811
  var series = this,
14722
- segmentPath = [],
14723
- step = series.options.step;
14812
+ options = series.options,
14813
+ step = options.step,
14814
+ graphPath = [],
14815
+ gap;
14724
14816
 
14725
- // build the segment line
14726
- each(segment, function (point, i) {
14817
+ points = points || series.points;
14818
+
14819
+ // Build the line
14820
+ each(points, function (point, i) {
14727
14821
 
14728
14822
  var plotX = point.plotX,
14729
14823
  plotY = point.plotY,
14730
- lastPoint;
14824
+ lastPoint = points[i - 1],
14825
+ pathToPoint; // the path to this point from the previous
14826
+
14827
+ if ((point.leftCliff || (lastPoint && lastPoint.rightCliff)) && !connectCliffs) {
14828
+ gap = true; // ... and continue
14829
+ }
14731
14830
 
14732
- if (series.getPointSpline) { // generate the spline as defined in the SplineSeries object
14733
- segmentPath.push.apply(segmentPath, series.getPointSpline(segment, point, i));
14831
+ // Line series, nullsAsZeroes is not handled
14832
+ if (point.isNull && !defined(nullsAsZeroes) && i > 0) {
14833
+ gap = !options.connectNulls;
14834
+
14835
+ // Area series, nullsAsZeroes is set
14836
+ } else if (point.isNull && !nullsAsZeroes) {
14837
+ gap = true;
14734
14838
 
14735
14839
  } else {
14736
14840
 
14737
- // moveTo or lineTo
14738
- segmentPath.push(i ? L : M);
14841
+ if (i === 0 || gap) {
14842
+ pathToPoint = [M, point.plotX, point.plotY];
14843
+
14844
+ } else if (series.getPointSpline) { // generate the spline as defined in the SplineSeries object
14845
+
14846
+ pathToPoint = series.getPointSpline(points, point, i);
14847
+
14848
+ } else if (step) {
14739
14849
 
14740
- // step line?
14741
- if (step && i) {
14742
- lastPoint = segment[i - 1];
14743
14850
  if (step === 'right') {
14744
- segmentPath.push(
14851
+ pathToPoint = [
14852
+ L,
14745
14853
  lastPoint.plotX,
14746
- plotY,
14747
- L
14748
- );
14749
-
14854
+ plotY
14855
+ ];
14856
+
14750
14857
  } else if (step === 'center') {
14751
- segmentPath.push(
14858
+ pathToPoint = [
14859
+ L,
14752
14860
  (lastPoint.plotX + plotX) / 2,
14753
14861
  lastPoint.plotY,
14754
14862
  L,
14755
14863
  (lastPoint.plotX + plotX) / 2,
14756
- plotY,
14757
- L
14758
- );
14759
-
14864
+ plotY
14865
+ ];
14866
+
14760
14867
  } else {
14761
- segmentPath.push(
14868
+ pathToPoint = [
14869
+ L,
14762
14870
  plotX,
14763
- lastPoint.plotY,
14764
- L
14765
- );
14871
+ lastPoint.plotY
14872
+ ];
14766
14873
  }
14767
- }
14874
+ pathToPoint.push(L, plotX, plotY);
14768
14875
 
14769
- // normal line to next point
14770
- segmentPath.push(
14771
- point.plotX,
14772
- point.plotY
14773
- );
14774
- }
14775
- });
14776
-
14777
- return segmentPath;
14778
- },
14779
-
14780
- /**
14781
- * Get the graph path
14782
- */
14783
- getGraphPath: function () {
14784
- var series = this,
14785
- graphPath = [],
14786
- segmentPath,
14787
- singlePoints = []; // used in drawTracker
14788
-
14789
- // Divide into segments and build graph and area paths
14790
- each(series.segments, function (segment) {
14876
+ } else {
14877
+ // normal line to next point
14878
+ pathToPoint = [
14879
+ L,
14880
+ plotX,
14881
+ plotY
14882
+ ];
14883
+ }
14791
14884
 
14792
- segmentPath = series.getSegmentPath(segment);
14793
14885
 
14794
- // add the segment to the graph, or a single point for tracking
14795
- if (segment.length > 1) {
14796
- graphPath = graphPath.concat(segmentPath);
14797
- } else {
14798
- singlePoints.push(segment[0]);
14886
+ graphPath.push.apply(graphPath, pathToPoint);
14887
+ gap = false;
14799
14888
  }
14800
14889
  });
14801
14890
 
14802
- // Record it for use in drawGraph and drawTracker, and return graphPath
14803
- series.singlePoints = singlePoints;
14804
14891
  series.graphPath = graphPath;
14805
14892
 
14806
14893
  return graphPath;
@@ -14816,7 +14903,7 @@
14816
14903
  props = [['graph', options.lineColor || this.color, options.dashStyle]],
14817
14904
  lineWidth = options.lineWidth,
14818
14905
  roundCap = options.linecap !== 'square',
14819
- graphPath = this.getGraphPath(),
14906
+ graphPath = (this.gappedPath || this.getGraphPath).call(this),
14820
14907
  fillColor = (this.fillGraph && this.color) || NONE, // polygon series use filled graph
14821
14908
  zones = this.zones;
14822
14909
 
@@ -15339,6 +15426,8 @@
15339
15426
 
15340
15427
  // Save the stack option on the series configuration object, and whether to treat it as percent
15341
15428
  this.stack = stackOption;
15429
+ this.leftCliff = 0;
15430
+ this.rightCliff = 0;
15342
15431
 
15343
15432
  // The align options and text align varies on whether the stack is negative and
15344
15433
  // if the chart is inverted or not.
@@ -15446,18 +15535,29 @@
15446
15535
  * Build the stacks from top down
15447
15536
  */
15448
15537
  Axis.prototype.buildStacks = function () {
15449
- var series = this.series,
15538
+ var axisSeries = this.series,
15539
+ series,
15450
15540
  reversedStacks = pick(this.options.reversedStacks, true),
15451
- i = series.length;
15541
+ len = axisSeries.length,
15542
+ i;
15452
15543
  if (!this.isXAxis) {
15453
15544
  this.usePercentage = false;
15545
+ i = len;
15454
15546
  while (i--) {
15455
- series[reversedStacks ? i : series.length - i - 1].setStackedPoints();
15547
+ axisSeries[reversedStacks ? i : len - i - 1].setStackedPoints();
15548
+ }
15549
+
15550
+ i = len;
15551
+ while (i--) {
15552
+ series = axisSeries[reversedStacks ? i : len - i - 1];
15553
+ if (series.setStackCliffs) {
15554
+ series.setStackCliffs();
15555
+ }
15456
15556
  }
15457
15557
  // Loop up again to compute percent stack
15458
15558
  if (this.usePercentage) {
15459
- for (i = 0; i < series.length; i++) {
15460
- series[i].setPercentStacks();
15559
+ for (i = 0; i < len; i++) {
15560
+ axisSeries[i].setPercentStacks();
15461
15561
  }
15462
15562
  }
15463
15563
  }
@@ -15608,13 +15708,16 @@
15608
15708
 
15609
15709
  // If the StackItem doesn't exist, create it first
15610
15710
  stack = stacks[key][x];
15611
- stack.points[pointKey] = [pick(stack.cum, stackThreshold)];
15612
- stack.touched = yAxis.stacksTouched;
15711
+ if (y !== null) {
15712
+ stack.points[pointKey] = stack.points[series.index] = [pick(stack.cum, stackThreshold)];
15713
+ stack.touched = yAxis.stacksTouched;
15714
+
15613
15715
 
15614
- // In area charts, if there are multiple points on the same X value, let the
15615
- // area fill the full span of those points
15616
- if (stackIndicator.index > 0 && series.singleStacks === false) {
15617
- stack.points[pointKey][0] = stack.points[series.index + ',' + x + ',0'][0];
15716
+ // In area charts, if there are multiple points on the same X value, let the
15717
+ // area fill the full span of those points
15718
+ if (stackIndicator.index > 0 && series.singleStacks === false) {
15719
+ stack.points[pointKey][0] = stack.points[series.index + ',' + x + ',0'][0];
15720
+ }
15618
15721
  }
15619
15722
 
15620
15723
  // Add value to the stack total
@@ -15636,7 +15739,9 @@
15636
15739
 
15637
15740
  stack.cum = pick(stack.cum, stackThreshold) + (y || 0);
15638
15741
 
15639
- stack.points[pointKey].push(stack.cum);
15742
+ if (y !== null) {
15743
+ stack.points[pointKey].push(stack.cum);
15744
+ }
15640
15745
  stackedYData[i] = stack.cum;
15641
15746
 
15642
15747
  }
@@ -16040,7 +16145,7 @@
16040
16145
  chart = series.chart,
16041
16146
  remove = function () {
16042
16147
 
16043
- if (data.length === points.length) {
16148
+ if (points && points.length === data.length) { // #4935
16044
16149
  points.splice(i, 1);
16045
16150
  }
16046
16151
  data.splice(i, 1);
@@ -16246,29 +16351,28 @@
16246
16351
  var AreaSeries = extendClass(Series, {
16247
16352
  type: 'area',
16248
16353
  singleStacks: false,
16249
- /**
16250
- * For stacks, don't split segments on null values. Instead, draw null values with
16251
- * no marker. Also insert dummy points for any X position that exists in other series
16252
- * in the stack.
16354
+ /**
16355
+ * Return an array of stacked points, where null and missing points are replaced by
16356
+ * dummy points in order for gaps to be drawn correctly in stacks.
16253
16357
  */
16254
- getSegments: function () {
16358
+ getStackPoints: function () {
16255
16359
  var series = this,
16256
- segments = [],
16257
16360
  segment = [],
16258
16361
  keys = [],
16259
16362
  xAxis = this.xAxis,
16260
16363
  yAxis = this.yAxis,
16261
16364
  stack = yAxis.stacks[this.stackKey],
16262
16365
  pointMap = {},
16263
- plotX,
16264
- plotY,
16265
16366
  points = this.points,
16266
- connectNulls = this.options.connectNulls,
16267
- stackIndicator,
16367
+ seriesIndex = series.index,
16368
+ yAxisSeries = yAxis.series,
16369
+ seriesLength = yAxisSeries.length,
16370
+ visibleSeries,
16371
+ upOrDown = pick(yAxis.options.reversedStacks, true) ? 1 : -1,
16268
16372
  i,
16269
16373
  x;
16270
16374
 
16271
- if (this.options.stacking && !this.cropped) { // cropped causes artefacts in Stock, and perf issue
16375
+ if (this.options.stacking) {
16272
16376
  // Create a map where we can quickly look up the points by their X value.
16273
16377
  for (i = 0; i < points.length; i++) {
16274
16378
  pointMap[points[i].x] = points[i];
@@ -16284,113 +16388,192 @@
16284
16388
  return a - b;
16285
16389
  });
16286
16390
 
16287
- each(keys, function (x) {
16288
- var threshold = null,
16289
- stackPoint,
16290
- skip = connectNulls && (!pointMap[x] || pointMap[x].y === null); // #1836
16391
+ visibleSeries = map(yAxisSeries, function () {
16392
+ return this.visible;
16393
+ });
16291
16394
 
16292
- if (!skip) {
16395
+ each(keys, function (x, idx) {
16396
+ var y = 0,
16397
+ stackPoint,
16398
+ stackedValues;
16399
+
16400
+ if (pointMap[x] && !pointMap[x].isNull) {
16401
+ segment.push(pointMap[x]);
16402
+
16403
+ // Find left and right cliff. -1 goes left, 1 goes right.
16404
+ each([-1, 1], function (direction) {
16405
+ var nullName = direction === 1 ? 'rightNull' : 'leftNull',
16406
+ cliffName = direction === 1 ? 'rightCliff' : 'leftCliff',
16407
+ cliff = 0,
16408
+ otherStack = stack[keys[idx + direction]];
16409
+
16410
+ // If there is a stack next to this one, to the left or to the right...
16411
+ if (otherStack) {
16412
+ i = seriesIndex;
16413
+ while (i >= 0 && i < seriesLength) { // Can go either up or down, depending on reversedStacks
16414
+ stackPoint = otherStack.points[i];
16415
+ if (!stackPoint) {
16416
+ // If the next point in this series is missing, mark the point
16417
+ // with point.leftNull or point.rightNull = true.
16418
+ if (i === seriesIndex) {
16419
+ pointMap[x][nullName] = true;
16420
+
16421
+ // If there are missing points in the next stack in any of the
16422
+ // series below this one, we need to substract the missing values
16423
+ // and add a hiatus to the left or right.
16424
+ } else if (visibleSeries[i]) {
16425
+ stackedValues = stack[x].points[i];
16426
+ if (stackedValues) {
16427
+ cliff -= stackedValues[1] - stackedValues[0];
16428
+ }
16429
+ }
16430
+ }
16431
+ // When reversedStacks is true, loop up, else loop down
16432
+ i += upOrDown;
16433
+ }
16434
+ }
16435
+ pointMap[x][cliffName] = cliff;
16436
+ });
16293
16437
 
16294
- // The point exists, push it to the segment
16295
- if (pointMap[x]) {
16296
- segment.push(pointMap[x]);
16297
16438
 
16298
- // There is no point for this X value in this series, so we
16299
- // insert a dummy point in order for the areas to be drawn
16300
- // correctly.
16301
- } else {
16439
+ // There is no point for this X value in this series, so we
16440
+ // insert a dummy point in order for the areas to be drawn
16441
+ // correctly.
16442
+ } else {
16302
16443
 
16303
- // Loop down the stack to find the series below this one that has
16304
- // a value (#1991)
16305
- for (i = series.index; i <= yAxis.series.length; i++) {
16306
- stackIndicator = series.getStackIndicator(null, x, i);
16307
- stackPoint = stack[x].points[stackIndicator.key];
16308
- if (stackPoint) {
16309
- threshold = stackPoint[1];
16310
- break;
16311
- }
16444
+ // Loop down the stack to find the series below this one that has
16445
+ // a value (#1991)
16446
+ i = seriesIndex;
16447
+ while (i >= 0 && i < seriesLength) {
16448
+ stackPoint = stack[x].points[i];
16449
+ if (stackPoint) {
16450
+ y = stackPoint[1];
16451
+ break;
16312
16452
  }
16313
-
16314
- plotX = xAxis.translate(x);
16315
- plotY = yAxis.getThreshold(threshold);
16316
- segment.push({
16317
- y: null,
16318
- plotX: plotX,
16319
- clientX: plotX,
16320
- plotY: plotY,
16321
- yBottom: plotY,
16322
- onMouseOver: noop
16323
- });
16453
+ // When reversedStacks is true, loop up, else loop down
16454
+ i += upOrDown;
16324
16455
  }
16456
+
16457
+ y = yAxis.toPixels(y, true);
16458
+ segment.push({
16459
+ isNull: true,
16460
+ plotX: xAxis.toPixels(x, true),
16461
+ plotY: y,
16462
+ yBottom: y
16463
+ });
16325
16464
  }
16326
16465
  });
16327
16466
 
16328
- if (segment.length) {
16329
- segments.push(segment);
16330
- }
16331
-
16332
- } else {
16333
- Series.prototype.getSegments.call(this);
16334
- segments = this.segments;
16335
- }
16467
+ }
16336
16468
 
16337
- this.segments = segments;
16469
+ return segment;
16338
16470
  },
16339
16471
 
16340
- /**
16341
- * Extend the base Series getSegmentPath method by adding the path for the area.
16342
- * This path is pushed to the series.areaPath property.
16343
- */
16344
- getSegmentPath: function (segment) {
16345
-
16346
- var segmentPath = Series.prototype.getSegmentPath.call(this, segment), // call base method
16347
- areaSegmentPath = [].concat(segmentPath), // work on a copy for the area path
16348
- i,
16472
+ getGraphPath: function (points) {
16473
+ var getGraphPath = Series.prototype.getGraphPath,
16474
+ graphPath,
16349
16475
  options = this.options,
16350
- segLength = segmentPath.length,
16351
- translatedThreshold = this.yAxis.getThreshold(options.threshold), // #2181
16352
- yBottom;
16476
+ stacking = options.stacking,
16477
+ yAxis = this.yAxis,
16478
+ topPath,
16479
+ //topPoints = [],
16480
+ bottomPath,
16481
+ bottomPoints = [],
16482
+ graphPoints = [],
16483
+ seriesIndex = this.index,
16484
+ i,
16485
+ areaPath,
16486
+ plotX,
16487
+ stacks = yAxis.stacks[this.stackKey],
16488
+ threshold = options.threshold,
16489
+ translatedThreshold = yAxis.getThreshold(options.threshold),
16490
+ isNull,
16491
+ yBottom,
16492
+ connectNulls = options.connectNulls || stacking === 'percent',
16493
+ /**
16494
+ * To display null points in underlying stacked series, this series graph must be
16495
+ * broken, and the area also fall down to fill the gap left by the null point. #2069
16496
+ */
16497
+ addDummyPoints = function (i, otherI, side) {
16498
+ var point = points[i],
16499
+ stackedValues = stacking && stacks[point.x].points[seriesIndex],
16500
+ nullVal = point[side + 'Null'] || 0,
16501
+ cliffVal = point[side + 'Cliff'] || 0,
16502
+ top,
16503
+ bottom,
16504
+ isNull = true;
16505
+
16506
+ if (cliffVal || nullVal) {
16507
+
16508
+ top = (nullVal ? stackedValues[0] : stackedValues[1]) + cliffVal;
16509
+ bottom = stackedValues[0] + cliffVal;
16510
+ isNull = !!nullVal;
16511
+
16512
+ } else if (!stacking && points[otherI] && points[otherI].isNull) {
16513
+ top = bottom = threshold;
16514
+ }
16515
+
16516
+ // Add to the top and bottom line of the area
16517
+ if (top !== undefined) {
16518
+ graphPoints.push({
16519
+ plotX: plotX,
16520
+ plotY: top === null ? translatedThreshold : yAxis.toPixels(top, true),
16521
+ isNull: isNull
16522
+ });
16523
+ bottomPoints.push({
16524
+ plotX: plotX,
16525
+ plotY: bottom === null ? translatedThreshold : yAxis.toPixels(bottom, true)
16526
+ });
16527
+ }
16528
+ };
16353
16529
 
16354
- if (segLength === 3) { // for animation from 1 to two points
16355
- areaSegmentPath.push(L, segmentPath[1], segmentPath[2]);
16530
+ // Find what points to use
16531
+ points = points || this.points;
16532
+
16533
+
16534
+ // Fill in missing points
16535
+ if (stacking) {
16536
+ points = this.getStackPoints();
16356
16537
  }
16357
- if (options.stacking && !this.closedStacks) {
16358
16538
 
16359
- // Follow stack back. Later, implement areaspline. A general solution could be to
16360
- // reverse the entire graphPath of the previous series, though may be hard with
16361
- // splines and with series with different extremes
16362
- for (i = segment.length - 1; i >= 0; i--) {
16539
+ for (i = 0; i < points.length; i++) {
16540
+ isNull = points[i].isNull;
16541
+ plotX = pick(points[i].rectPlotX, points[i].plotX);
16542
+ yBottom = pick(points[i].yBottom, translatedThreshold);
16363
16543
 
16364
- yBottom = pick(segment[i].yBottom, translatedThreshold);
16544
+ if (!isNull || connectNulls) {
16365
16545
 
16366
- // step line?
16367
- if (i < segment.length - 1 && options.step) {
16368
- areaSegmentPath.push(segment[i + 1].plotX, yBottom);
16546
+ if (!connectNulls) {
16547
+ addDummyPoints(i, i - 1, 'left');
16369
16548
  }
16370
16549
 
16371
- areaSegmentPath.push(segment[i].plotX, yBottom);
16550
+ if (!(isNull && !stacking && connectNulls)) { // Skip null point when stacking is false and connectNulls true
16551
+ graphPoints.push(points[i]);
16552
+ bottomPoints.push({
16553
+ x: i,
16554
+ plotX: plotX,
16555
+ plotY: yBottom
16556
+ });
16557
+ }
16558
+
16559
+ if (!connectNulls) {
16560
+ addDummyPoints(i, i + 1, 'right');
16561
+ }
16372
16562
  }
16563
+ }
16373
16564
 
16374
- } else { // follow zero line back
16375
- this.closeSegment(areaSegmentPath, segment, translatedThreshold);
16565
+ topPath = getGraphPath.call(this, graphPoints, true, true);
16566
+
16567
+ bottomPath = getGraphPath.call(this, bottomPoints.reverse(), true, true);
16568
+ if (bottomPath.length) {
16569
+ bottomPath[0] = L;
16376
16570
  }
16377
- this.areaPath = this.areaPath.concat(areaSegmentPath);
16378
- return segmentPath;
16379
- },
16380
16571
 
16381
- /**
16382
- * Extendable method to close the segment path of an area. This is overridden in polar
16383
- * charts.
16384
- */
16385
- closeSegment: function (path, segment, translatedThreshold) {
16386
- path.push(
16387
- L,
16388
- segment[segment.length - 1].plotX,
16389
- translatedThreshold,
16390
- L,
16391
- segment[0].plotX,
16392
- translatedThreshold
16393
- );
16572
+ areaPath = topPath.concat(bottomPath);
16573
+ graphPath = getGraphPath.call(this, graphPoints, false, connectNulls); // TODO: don't set leftCliff and rightCliff when connectNulls?
16574
+
16575
+ this.areaPath = areaPath;
16576
+ return graphPath;
16394
16577
  },
16395
16578
 
16396
16579
  /**
@@ -16431,7 +16614,7 @@
16431
16614
  zIndex: 0 // #1069
16432
16615
  };
16433
16616
  if (!prop[2]) {
16434
- attr['fill-opacity'] = options.fillOpacity || 0.75;
16617
+ attr['fill-opacity'] = pick(options.fillOpacity, 0.75);
16435
16618
  }
16436
16619
  series[areaKey] = series.chart.renderer.path(areaPath)
16437
16620
  .attr(attr)
@@ -16458,22 +16641,21 @@
16458
16641
  /**
16459
16642
  * Get the spline segment from a given point's previous neighbour to the given point
16460
16643
  */
16461
- getPointSpline: function (segment, point, i) {
16644
+ getPointSpline: function (points, point, i) {
16462
16645
  var smoothing = 1.5, // 1 means control points midway between points, 2 means 1/3 from the point, 3 is 1/4 etc
16463
16646
  denom = smoothing + 1,
16464
16647
  plotX = point.plotX,
16465
16648
  plotY = point.plotY,
16466
- lastPoint = segment[i - 1],
16467
- nextPoint = segment[i + 1],
16649
+ lastPoint = points[i - 1],
16650
+ nextPoint = points[i + 1],
16468
16651
  leftContX,
16469
16652
  leftContY,
16470
16653
  rightContX,
16471
16654
  rightContY,
16472
16655
  ret;
16473
16656
 
16474
- // find control points
16475
- if (lastPoint && nextPoint) {
16476
-
16657
+ // Find control points
16658
+ if (lastPoint && !lastPoint.isNull && nextPoint && !nextPoint.isNull) {
16477
16659
  var lastX = lastPoint.plotX,
16478
16660
  lastY = lastPoint.plotY,
16479
16661
  nextX = nextPoint.plotX,
@@ -16513,6 +16695,7 @@
16513
16695
  point.rightContX = rightContX;
16514
16696
  point.rightContY = rightContY;
16515
16697
 
16698
+
16516
16699
  }
16517
16700
 
16518
16701
  // Visualize control points for debugging
@@ -16547,23 +16730,17 @@
16547
16730
  })
16548
16731
  .add();
16549
16732
  }
16550
- */
16551
-
16552
- // moveTo or lineTo
16553
- if (!i) {
16554
- ret = [M, plotX, plotY];
16555
- } else { // curve from last point to this
16556
- ret = [
16557
- 'C',
16558
- lastPoint.rightContX || lastPoint.plotX,
16559
- lastPoint.rightContY || lastPoint.plotY,
16560
- leftContX || plotX,
16561
- leftContY || plotY,
16562
- plotX,
16563
- plotY
16564
- ];
16565
- lastPoint.rightContX = lastPoint.rightContY = null; // reset for updating series later
16566
- }
16733
+ // */
16734
+ ret = [
16735
+ 'C',
16736
+ pick(lastPoint.rightContX, lastPoint.plotX),
16737
+ pick(lastPoint.rightContY, lastPoint.plotY),
16738
+ pick(leftContX, plotX),
16739
+ pick(leftContY, plotY),
16740
+ plotX,
16741
+ plotY
16742
+ ];
16743
+ lastPoint.rightContX = lastPoint.rightContY = null; // reset for updating series later
16567
16744
  return ret;
16568
16745
  }
16569
16746
  });
@@ -16580,11 +16757,9 @@
16580
16757
  var areaProto = AreaSeries.prototype,
16581
16758
  AreaSplineSeries = extendClass(SplineSeries, {
16582
16759
  type: 'areaspline',
16583
- closedStacks: true, // instead of following the previous graph back, follow the threshold back
16584
-
16585
- // Mix in methods from the area series
16586
- getSegmentPath: areaProto.getSegmentPath,
16587
- closeSegment: areaProto.closeSegment,
16760
+ getStackPoints: areaProto.getStackPoints,
16761
+ getGraphPath: areaProto.getGraphPath,
16762
+ setStackCliffs: areaProto.setStackCliffs,
16588
16763
  drawGraph: areaProto.drawGraph,
16589
16764
  drawLegendSymbol: LegendSymbolMixin.drawRectangle
16590
16765
  });
@@ -17650,11 +17825,16 @@
17650
17825
  plotY = pick(point.plotY, -9999),
17651
17826
  bBox = dataLabel.getBBox(),
17652
17827
  baseline = chart.renderer.fontMetrics(options.style.fontSize).b,
17828
+ rotation = options.rotation,
17829
+ normRotation,
17830
+ negRotation,
17831
+ align = options.align,
17653
17832
  rotCorr, // rotation correction
17654
17833
  // Math.round for rounding errors (#2683), alignTo to allow column labels (#2700)
17655
17834
  visible = this.visible && (point.series.forceDL || chart.isInsidePlot(plotX, mathRound(plotY), inverted) ||
17656
17835
  (alignTo && chart.isInsidePlot(plotX, inverted ? alignTo.x + 1 : alignTo.y + alignTo.height - 1, inverted))),
17657
- alignAttr; // the final position;
17836
+ alignAttr, // the final position;
17837
+ justify = pick(options.overflow, 'justify') === 'justify';
17658
17838
 
17659
17839
  if (visible) {
17660
17840
 
@@ -17673,37 +17853,54 @@
17673
17853
  });
17674
17854
 
17675
17855
  // Allow a hook for changing alignment in the last moment, then do the alignment
17676
- if (options.rotation) { // Fancy box alignment isn't supported for rotated text
17677
- rotCorr = chart.renderer.rotCorr(baseline, options.rotation); // #3723
17678
- dataLabel[isNew ? 'attr' : 'animate']({
17679
- x: alignTo.x + options.x + alignTo.width / 2 + rotCorr.x,
17680
- y: alignTo.y + options.y + alignTo.height / 2
17681
- })
17856
+ if (rotation) {
17857
+ justify = false; // Not supported for rotated text
17858
+ rotCorr = chart.renderer.rotCorr(baseline, rotation); // #3723
17859
+ alignAttr = {
17860
+ x: alignTo.x + options.x + alignTo.width / 2 + rotCorr.x,
17861
+ y: alignTo.y + options.y + alignTo.height / 2
17862
+ };
17863
+ dataLabel
17864
+ [isNew ? 'attr' : 'animate'](alignAttr)
17682
17865
  .attr({ // #3003
17683
17866
  align: options.align
17684
17867
  });
17685
- } else {
17686
- dataLabel.align(options, null, alignTo);
17687
- alignAttr = dataLabel.alignAttr;
17688
-
17689
- // Handle justify or crop
17690
- if (pick(options.overflow, 'justify') === 'justify') {
17691
- this.justifyDataLabel(dataLabel, options, alignAttr, bBox, alignTo, isNew);
17692
17868
 
17693
- } else if (pick(options.crop, true)) {
17694
- // Now check that the data label is within the plot area
17695
- visible = chart.isInsidePlot(alignAttr.x, alignAttr.y) && chart.isInsidePlot(alignAttr.x + bBox.width, alignAttr.y + bBox.height);
17869
+ // Compensate for the rotated label sticking out on the sides
17870
+ normRotation = (rotation + 720) % 360;
17871
+ negRotation = normRotation > 180 && normRotation < 360;
17696
17872
 
17873
+ if (align === 'left') {
17874
+ alignAttr.y -= negRotation ? bBox.height : 0;
17875
+ } else if (align === 'center') {
17876
+ alignAttr.x -= bBox.width / 2;
17877
+ alignAttr.y -= bBox.height / 2;
17878
+ } else if (align === 'right') {
17879
+ alignAttr.x -= bBox.width;
17880
+ alignAttr.y -= negRotation ? 0 : bBox.height;
17697
17881
  }
17882
+
17698
17883
 
17699
- // When we're using a shape, make it possible with a connector or an arrow pointing to thie point
17700
- if (options.shape) {
17701
- dataLabel.attr({
17702
- anchorX: point.plotX,
17703
- anchorY: point.plotY
17704
- });
17705
- }
17884
+ } else {
17885
+ dataLabel.align(options, null, alignTo);
17886
+ alignAttr = dataLabel.alignAttr;
17887
+ }
17706
17888
 
17889
+ // Handle justify or crop
17890
+ if (justify) {
17891
+ this.justifyDataLabel(dataLabel, options, alignAttr, bBox, alignTo, isNew);
17892
+
17893
+ // Now check that the data label is within the plot area
17894
+ } else if (pick(options.crop, true)) {
17895
+ visible = chart.isInsidePlot(alignAttr.x, alignAttr.y) && chart.isInsidePlot(alignAttr.x + bBox.width, alignAttr.y + bBox.height);
17896
+ }
17897
+
17898
+ // When we're using a shape, make it possible with a connector or an arrow pointing to thie point
17899
+ if (options.shape && !rotation) {
17900
+ dataLabel.attr({
17901
+ anchorX: point.plotX,
17902
+ anchorY: point.plotY
17903
+ });
17707
17904
  }
17708
17905
  }
17709
17906
 
@@ -17827,10 +18024,14 @@
17827
18024
  // run parent method
17828
18025
  Series.prototype.drawDataLabels.apply(series);
17829
18026
 
17830
- // arrange points for detection collision
17831
18027
  each(data, function (point) {
17832
18028
  if (point.dataLabel && point.visible) { // #407, #2510
18029
+
18030
+ // Arrange points for detection collision
17833
18031
  halves[point.half].push(point);
18032
+
18033
+ // Reset positions (#4905)
18034
+ point.dataLabel._pos = null;
17834
18035
  }
17835
18036
  });
17836
18037
 
@@ -18165,12 +18366,7 @@
18165
18366
  center[2] = newSize;
18166
18367
  center[3] = Math.min(relativeLength(options.innerSize || 0, newSize), newSize); // #3632
18167
18368
  this.translate(center);
18168
- each(this.points, function (point) {
18169
- if (point.dataLabel) {
18170
- point.dataLabel._pos = null; // reset
18171
- }
18172
- });
18173
-
18369
+
18174
18370
  if (this.drawDataLabels) {
18175
18371
  this.drawDataLabels();
18176
18372
  }
@@ -18303,6 +18499,8 @@
18303
18499
  isIntersecting,
18304
18500
  pos1,
18305
18501
  pos2,
18502
+ parent1,
18503
+ parent2,
18306
18504
  padding,
18307
18505
  intersectRect = function (x1, y1, w1, h1, x2, y2, w2, h2) {
18308
18506
  return !(
@@ -18338,14 +18536,16 @@
18338
18536
  if (label1 && label2 && label1.placed && label2.placed && label1.newOpacity !== 0 && label2.newOpacity !== 0) {
18339
18537
  pos1 = label1.alignAttr;
18340
18538
  pos2 = label2.alignAttr;
18539
+ parent1 = label1.parentGroup; // Different panes have different positions
18540
+ parent2 = label2.parentGroup;
18341
18541
  padding = 2 * (label1.box ? 0 : label1.padding); // Substract the padding if no background or border (#4333)
18342
18542
  isIntersecting = intersectRect(
18343
- pos1.x,
18344
- pos1.y,
18543
+ pos1.x + parent1.translateX,
18544
+ pos1.y + parent1.translateY,
18345
18545
  label1.width - padding,
18346
18546
  label1.height - padding,
18347
- pos2.x,
18348
- pos2.y,
18547
+ pos2.x + parent2.translateX,
18548
+ pos2.y + parent2.translateY,
18349
18549
  label2.width - padding,
18350
18550
  label2.height - padding
18351
18551
  );
@@ -18461,8 +18661,6 @@
18461
18661
  tracker = series.tracker,
18462
18662
  cursor = options.cursor,
18463
18663
  css = cursor && { cursor: cursor },
18464
- singlePoints = series.singlePoints,
18465
- singlePoint,
18466
18664
  i,
18467
18665
  onMouseOver = function () {
18468
18666
  if (chart.hoverSeries !== series) {
@@ -18498,11 +18696,11 @@
18498
18696
  }
18499
18697
 
18500
18698
  // handle single points
18501
- for (i = 0; i < singlePoints.length; i++) {
18699
+ /*for (i = 0; i < singlePoints.length; i++) {
18502
18700
  singlePoint = singlePoints[i];
18503
18701
  trackerPath.push(M, singlePoint.plotX - snap, singlePoint.plotY,
18504
18702
  L, singlePoint.plotX + snap, singlePoint.plotY);
18505
- }
18703
+ }*/
18506
18704
 
18507
18705
  // draw the tracker
18508
18706
  if (tracker) {
@@ -18734,13 +18932,15 @@
18734
18932
  }
18735
18933
 
18736
18934
  each(panning === 'xy' ? [1, 0] : [1], function (isX) { // xy is used in maps
18737
- var mousePos = e[isX ? 'chartX' : 'chartY'],
18738
- axis = chart[isX ? 'xAxis' : 'yAxis'][0],
18739
- startPos = chart[isX ? 'mouseDownX' : 'mouseDownY'],
18935
+ var axis = chart[isX ? 'xAxis' : 'yAxis'][0],
18936
+ horiz = axis.horiz,
18937
+ mousePos = e[horiz ? 'chartX' : 'chartY'],
18938
+ mouseDown = horiz ? 'mouseDownX' : 'mouseDownY',
18939
+ startPos = chart[mouseDown],
18740
18940
  halfPointRange = (axis.pointRange || 0) / 2,
18741
18941
  extremes = axis.getExtremes(),
18742
18942
  newMin = axis.toValue(startPos - mousePos, true) + halfPointRange,
18743
- newMax = axis.toValue(startPos + chart[isX ? 'plotWidth' : 'plotHeight'] - mousePos, true) - halfPointRange,
18943
+ newMax = axis.toValue(startPos + axis.len - mousePos, true) - halfPointRange,
18744
18944
  goingLeft = startPos > mousePos; // #3613
18745
18945
 
18746
18946
  if (axis.series.length &&
@@ -18750,7 +18950,7 @@
18750
18950
  doRedraw = true;
18751
18951
  }
18752
18952
 
18753
- chart[isX ? 'mouseDownX' : 'mouseDownY'] = mousePos; // set new reference for next run
18953
+ chart[mouseDown] = mousePos; // set new reference for next run
18754
18954
  });
18755
18955
 
18756
18956
  if (doRedraw) {
@@ -18985,8 +19185,9 @@
18985
19185
  .add(chart.seriesGroup);
18986
19186
  }
18987
19187
  halo.attr(extend({
18988
- fill: point.color || series.color,
18989
- 'fill-opacity': haloOptions.opacity
19188
+ 'fill': point.color || series.color,
19189
+ 'fill-opacity': haloOptions.opacity,
19190
+ 'zIndex': -1 // #4929, IE8 added halo above everything
18990
19191
  },
18991
19192
  haloOptions.attributes))[move ? 'animate' : 'attr']({
18992
19193
  d: point.haloPath(haloOptions.size)