highcharts-rails 4.2.0 → 4.2.2

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