contour 2.1.0.beta16 → 2.1.0.beta17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +2 -1
  3. data/app/assets/javascripts/contour.js +1 -1
  4. data/app/assets/javascripts/external/{highcharts-3.0.2.src.js → highcharts-3.0.5.src.js} +1135 -641
  5. data/contour.gemspec +1 -1
  6. data/lib/contour/version.rb +1 -1
  7. data/lib/generators/contour/scaffold/templates/_paginate.html.erb +1 -1
  8. data/lib/generators/contour/scaffold/templates/controller.rb +1 -1
  9. data/lib/generators/contour/scaffold/templates/show.html.erb +2 -2
  10. data/test/dummy/db/test.sqlite3 +0 -0
  11. data/test/dummy/log/test.log +392 -0
  12. data/test/dummy/tmp/cache/assets/test/sprockets/0a38cde2d72ff579969b2cad3683287d +0 -0
  13. data/test/dummy/tmp/cache/assets/test/sprockets/0a9add3e4b179c763a81d057c1237324 +0 -0
  14. data/test/dummy/tmp/cache/assets/test/sprockets/0b793dc498fe65856d1b31a805f114dc +0 -0
  15. data/test/dummy/tmp/cache/assets/test/sprockets/13fe41fee1fe35b49d145bcc06610705 +0 -0
  16. data/test/dummy/tmp/cache/assets/test/sprockets/1a4771f9c62410bd2e9883cbf32c7ab2 +0 -0
  17. data/test/dummy/tmp/cache/assets/test/sprockets/202ee3e470074d921a4ebc990afe1182 +0 -0
  18. data/test/dummy/tmp/cache/assets/test/sprockets/2104a43b6b7d1d60519dc2deb62ae47b +0 -0
  19. data/test/dummy/tmp/cache/assets/test/sprockets/2479163ee9d4e495176d9585da79e9dd +0 -0
  20. data/test/dummy/tmp/cache/assets/test/sprockets/2f5173deea6c795b8fdde723bb4b63af +0 -0
  21. data/test/dummy/tmp/cache/assets/test/sprockets/357970feca3ac29060c1e3861e2c0953 +0 -0
  22. data/test/dummy/tmp/cache/assets/test/sprockets/408c90980fca5a250881bafa3ef0ca63 +0 -0
  23. data/test/dummy/tmp/cache/assets/test/sprockets/4326df7424da5275c5236fd4615801e8 +0 -0
  24. data/test/dummy/tmp/cache/assets/test/sprockets/4e3cde93d4ff732fa65037906a330288 +0 -0
  25. data/test/dummy/tmp/cache/assets/test/sprockets/6666059cb20313a69e518d351e00eb1f +0 -0
  26. data/test/dummy/tmp/cache/assets/test/sprockets/722eaf6263f7819374cc8215ee7c854b +0 -0
  27. data/test/dummy/tmp/cache/assets/test/sprockets/795f142066419901b557f2d610607b6b +0 -0
  28. data/test/dummy/tmp/cache/assets/test/sprockets/848658619cb4b7da3735feeb70b87d5c +0 -0
  29. data/test/dummy/tmp/cache/assets/test/sprockets/92654af24f38bd4ce2b3e67eac84f17f +0 -0
  30. data/test/dummy/tmp/cache/assets/test/sprockets/a7b222735fc6d94f5713fa8e4c00feec +0 -0
  31. data/test/dummy/tmp/cache/assets/test/sprockets/aebdc1b314bd6ea5185ab5d39bf75d87 +0 -0
  32. data/test/dummy/tmp/cache/assets/test/sprockets/b0d8276a1c6457dcb0798d0f676b1298 +0 -0
  33. data/test/dummy/tmp/cache/assets/test/sprockets/bffe970187601e005684626d70560050 +0 -0
  34. data/test/dummy/tmp/cache/assets/test/sprockets/c62ff8b966094d815ee8799210a70413 +0 -0
  35. data/test/dummy/tmp/cache/assets/test/sprockets/cffd775d018f68ce5dba1ee0d951a994 +0 -0
  36. data/test/dummy/tmp/cache/assets/test/sprockets/cffe43e7509735b14b54b6ac19fd08d5 +0 -0
  37. data/test/dummy/tmp/cache/assets/test/sprockets/d771ace226fc8215a3572e0aa35bb0d6 +0 -0
  38. data/test/dummy/tmp/cache/assets/test/sprockets/e2a43f58b4eed6258f424ad79d34e5c1 +0 -0
  39. data/test/dummy/tmp/cache/assets/test/sprockets/f7cbd26ba1d28d48de824f0e94586655 +0 -0
  40. metadata +33 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 01903d7f529481bbc3a19c40a784ba83f789a216
4
- data.tar.gz: 0b2c9921b085b594d803f99cd5f18f76b4e10b56
3
+ metadata.gz: 06c1f851aa201f8b055336424f8f786f4428c090
4
+ data.tar.gz: 6dcb4f35ccf617568aaa820907a2583efec5124d
5
5
  SHA512:
6
- metadata.gz: bd4d7d442b3642d68c417aa6f4608bf4ad315732409a9a5fa5f45ccf68abde3669228dc92f80b19cb54c96d2512ea40b0df985982249b7f32e41fdef61ff696e
7
- data.tar.gz: 1f4f848faeaf2402c1ebb5d4d1f1c57782c73381e18f65a1d976eae7f5a018afc8f9f1aaa04e55798a23ecc0732db82ccda7d3dd93b512bb9d18006a2c27b3a9
6
+ metadata.gz: 4f7d6c9c0640d083d851c3ab42d9291e68cd1fce9582904d35b4a1d39745062b3ee6ed38693c8f3464b6fea6f9c2455ad4359a5afe12a4199d2e59f4a0c91e4f
7
+ data.tar.gz: 76bd1bf6ebcc9f160c919073b5f819834d9ae7ae831c95b62f7bc5b33b7e6a8451ed02b51680e4d2d4125a4b08846cc8891c60a877c74e26c50fee938074e83e
data/CHANGELOG.md CHANGED
@@ -6,8 +6,9 @@
6
6
  - Menu dropdowns can now have headers
7
7
  - `links: [ ..., { header: 'Dropdown header' }, ... ]`
8
8
  - Removed bootstrap-scroll-modal since it collided with BS3's implementation
9
+ - Updated HighCharts to 3.0.5
9
10
  - **Gem Changes**
10
- - Updated to Devise 3.0.2
11
+ - Updated to Devise 3.0.3
11
12
 
12
13
  ## 2.0.0 (July 25, 2013)
13
14
 
@@ -10,6 +10,6 @@
10
10
  //
11
11
  //= require twitter-bootstrap/bootstrap
12
12
  //
13
- //= require external/highcharts-3.0.2.src.js
13
+ //= require external/highcharts-3.0.5.src.js
14
14
  //
15
15
  //= require_tree .
@@ -2,7 +2,7 @@
2
2
  // @compilation_level SIMPLE_OPTIMIZATIONS
3
3
 
4
4
  /**
5
- * @license Highcharts JS v3.0.2 (2013-06-05)
5
+ * @license Highcharts JS v3.0.5 (2013-08-23)
6
6
  *
7
7
  * (c) 2009-2013 Torstein Hønsi
8
8
  *
@@ -55,7 +55,7 @@ var UNDEFINED,
55
55
  noop = function () {},
56
56
  charts = [],
57
57
  PRODUCT = 'Highcharts',
58
- VERSION = '3.0.2',
58
+ VERSION = '3.0.5',
59
59
 
60
60
  // some constants for frequently used strings
61
61
  DIV = 'div',
@@ -149,15 +149,15 @@ function merge() {
149
149
  doCopy = function (copy, original) {
150
150
  var value, key;
151
151
 
152
+ // An object is replacing a primitive
153
+ if (typeof copy !== 'object') {
154
+ copy = {};
155
+ }
156
+
152
157
  for (key in original) {
153
158
  if (original.hasOwnProperty(key)) {
154
159
  value = original[key];
155
160
 
156
- // An object is replacing a primitive
157
- if (typeof copy !== 'object') {
158
- copy = {};
159
- }
160
-
161
161
  // Copy the contents of objects, but not arrays or DOM nodes
162
162
  if (value && typeof value === 'object' && Object.prototype.toString.call(value) !== '[object Array]'
163
163
  && typeof value.nodeType !== 'number') {
@@ -387,14 +387,14 @@ function extendClass(parent, members) {
387
387
  function numberFormat(number, decimals, decPoint, thousandsSep) {
388
388
  var lang = defaultOptions.lang,
389
389
  // http://kevin.vanzonneveld.net/techblog/article/javascript_equivalent_for_phps_number_format/
390
- n = number,
390
+ n = +number || 0,
391
391
  c = decimals === -1 ?
392
- ((n || 0).toString().split('.')[1] || '').length : // preserve decimals
392
+ (n.toString().split('.')[1] || '').length : // preserve decimals
393
393
  (isNaN(decimals = mathAbs(decimals)) ? 2 : decimals),
394
394
  d = decPoint === undefined ? lang.decimalPoint : decPoint,
395
395
  t = thousandsSep === undefined ? lang.thousandsSep : thousandsSep,
396
396
  s = n < 0 ? "-" : "",
397
- i = String(pInt(n = mathAbs(+n || 0).toFixed(c))),
397
+ i = String(pInt(n = mathAbs(n).toFixed(c))),
398
398
  j = i.length > 3 ? i.length % 3 : 0;
399
399
 
400
400
  return s + (j ? i.substr(0, j) + t : "") + i.substr(j).replace(/(\d{3})(?=\d)/g, "$1" + t) +
@@ -569,6 +569,13 @@ function format(str, ctx) {
569
569
  return ret.join('');
570
570
  }
571
571
 
572
+ /**
573
+ * Get the magnitude of a number
574
+ */
575
+ function getMagnitude(num) {
576
+ return math.pow(10, mathFloor(math.log(num) / math.LN10));
577
+ }
578
+
572
579
  /**
573
580
  * Take an interval and normalize it to multiples of 1, 2, 2.5 and 5
574
581
  * @param {Number} interval
@@ -681,7 +688,11 @@ function normalizeTimeTickInterval(tickInterval, unitsOption) {
681
688
  }
682
689
 
683
690
  // get the count
684
- count = normalizeTickInterval(tickInterval / interval, multiples);
691
+ count = normalizeTickInterval(
692
+ tickInterval / interval,
693
+ multiples,
694
+ unit[0] === YEAR ? getMagnitude(tickInterval / interval) : 1 // #1913
695
+ );
685
696
 
686
697
  return {
687
698
  unitRange: interval,
@@ -1146,7 +1157,7 @@ pathAnim = {
1146
1157
 
1147
1158
  // Extend the opacity getter, needed for fading opacity with IE9 and jQuery 1.10+
1148
1159
  wrap(opacityHook, 'get', function (proceed, elem, computed) {
1149
- return elem.attr ? (elem.opacity || 0) : proceed.call(this, elem, computed);
1160
+ return elem.attr ? (elem.opacity || 0) : proceed.call(this, elem, computed);
1150
1161
  });
1151
1162
 
1152
1163
 
@@ -1412,6 +1423,9 @@ pathAnim = {
1412
1423
  }
1413
1424
 
1414
1425
  $el.stop();
1426
+ if (params.opacity !== UNDEFINED && el.attr) {
1427
+ params.opacity += 'px'; // force jQuery to use same logic as width and height (#2161)
1428
+ }
1415
1429
  $el.animate(params, options);
1416
1430
 
1417
1431
  },
@@ -1462,7 +1476,7 @@ var
1462
1476
  defaultLabelOptions = {
1463
1477
  enabled: true,
1464
1478
  // rotation: 0,
1465
- align: 'center',
1479
+ // align: 'center',
1466
1480
  x: 0,
1467
1481
  y: 15,
1468
1482
  /*formatter: function () {
@@ -1494,8 +1508,8 @@ defaultOptions = {
1494
1508
  },
1495
1509
  global: {
1496
1510
  useUTC: true,
1497
- canvasToolsURL: 'http://code.highcharts.com/3.0.2/modules/canvas-tools.js',
1498
- VMLRadialGradientURL: 'http://code.highcharts.com/3.0.2/gfx/vml-radial-gradient.png'
1511
+ canvasToolsURL: 'http://code.highcharts.com/3.0.5/modules/canvas-tools.js',
1512
+ VMLRadialGradientURL: 'http://code.highcharts.com/3.0.5/gfx/vml-radial-gradient.png'
1499
1513
  },
1500
1514
  chart: {
1501
1515
  //animation: true,
@@ -1546,10 +1560,10 @@ defaultOptions = {
1546
1560
  text: 'Chart title',
1547
1561
  align: 'center',
1548
1562
  // floating: false,
1549
- // margin: 15,
1563
+ margin: 15,
1550
1564
  // x: 0,
1551
1565
  // verticalAlign: 'top',
1552
- y: 15,
1566
+ // y: null,
1553
1567
  style: {
1554
1568
  color: '#274b6d',//#3E576F',
1555
1569
  fontSize: '16px'
@@ -1562,7 +1576,7 @@ defaultOptions = {
1562
1576
  // floating: false
1563
1577
  // x: 0,
1564
1578
  // verticalAlign: 'top',
1565
- y: 30,
1579
+ // y: null,
1566
1580
  style: {
1567
1581
  color: '#4d759e'
1568
1582
  }
@@ -1608,9 +1622,10 @@ defaultOptions = {
1608
1622
  events: {}
1609
1623
  },
1610
1624
  dataLabels: merge(defaultLabelOptions, {
1625
+ align: 'center',
1611
1626
  enabled: false,
1612
1627
  formatter: function () {
1613
- return numberFormat(this.y, -1);
1628
+ return this.y === null ? '' : numberFormat(this.y, -1);
1614
1629
  },
1615
1630
  verticalAlign: 'bottom', // above singular point
1616
1631
  y: 0
@@ -2158,7 +2173,7 @@ SVGElement.prototype = {
2158
2173
 
2159
2174
  i = value.length;
2160
2175
  while (i--) {
2161
- value[i] = pInt(value[i]) * hash['stroke-width'];
2176
+ value[i] = pInt(value[i]) * pick(hash['stroke-width'], wrapper['stroke-width']);
2162
2177
  }
2163
2178
  value = value.join(',');
2164
2179
  }
@@ -2216,7 +2231,7 @@ SVGElement.prototype = {
2216
2231
  }
2217
2232
 
2218
2233
  // let the shadow follow the main element
2219
- if (shadows && /^(width|height|visibility|x|y|d|transform)$/.test(key)) {
2234
+ if (shadows && /^(width|height|visibility|x|y|d|transform|cx|cy|r)$/.test(key)) {
2220
2235
  i = shadows.length;
2221
2236
  while (i--) {
2222
2237
  attr(
@@ -2271,7 +2286,12 @@ SVGElement.prototype = {
2271
2286
  * Add a class name to an element
2272
2287
  */
2273
2288
  addClass: function (className) {
2274
- attr(this.element, 'class', attr(this.element, 'class') + ' ' + className);
2289
+ var element = this.element,
2290
+ currentClassName = attr(element, 'class') || '';
2291
+
2292
+ if (currentClassName.indexOf(className) === -1) {
2293
+ attr(element, 'class', currentClassName + ' ' + className);
2294
+ }
2275
2295
  return this;
2276
2296
  },
2277
2297
  /* hasClass and removeClass are not (yet) needed
@@ -2414,15 +2434,16 @@ SVGElement.prototype = {
2414
2434
  * @param {Function} handler
2415
2435
  */
2416
2436
  on: function (eventType, handler) {
2437
+ var element = this.element;
2417
2438
  // touch
2418
2439
  if (hasTouch && eventType === 'click') {
2419
- this.element.ontouchstart = function (e) {
2440
+ element.ontouchstart = function (e) {
2420
2441
  e.preventDefault();
2421
- handler();
2442
+ handler.call(element, e);
2422
2443
  };
2423
2444
  }
2424
2445
  // simplest possible event model for internal use
2425
- this.element['on' + eventType] = handler;
2446
+ element['on' + eventType] = handler;
2426
2447
  return this;
2427
2448
  },
2428
2449
 
@@ -2568,33 +2589,18 @@ SVGElement.prototype = {
2568
2589
  textWidth = pInt(wrapper.textWidth),
2569
2590
  xCorr = wrapper.xCorr || 0,
2570
2591
  yCorr = wrapper.yCorr || 0,
2571
- currentTextTransform = [rotation, align, elem.innerHTML, wrapper.textWidth].join(','),
2572
- rotationStyle = {},
2573
- cssTransformKey;
2592
+ currentTextTransform = [rotation, align, elem.innerHTML, wrapper.textWidth].join(',');
2574
2593
 
2575
2594
  if (currentTextTransform !== wrapper.cTT) { // do the calculations and DOM access only if properties changed
2576
2595
 
2577
2596
  if (defined(rotation)) {
2578
2597
 
2579
- if (renderer.isSVG) { // #916
2580
- cssTransformKey = isIE ? '-ms-transform' : isWebKit ? '-webkit-transform' : isFirefox ? 'MozTransform' : isOpera ? '-o-transform' : '';
2581
- rotationStyle[cssTransformKey] = rotationStyle.transform = 'rotate(' + rotation + 'deg)';
2582
-
2583
- } else {
2584
- radians = rotation * deg2rad; // deg to rad
2585
- costheta = mathCos(radians);
2586
- sintheta = mathSin(radians);
2587
-
2588
- // Adjust for alignment and rotation. Rotation of useHTML content is not yet implemented
2589
- // but it can probably be implemented for Firefox 3.5+ on user request. FF3.5+
2590
- // has support for CSS3 transform. The getBBox method also needs to be updated
2591
- // to compensate for the rotation, like it currently does for SVG.
2592
- // Test case: http://highcharts.com/tests/?file=text-rotation
2593
- rotationStyle.filter = rotation ? ['progid:DXImageTransform.Microsoft.Matrix(M11=', costheta,
2594
- ', M12=', -sintheta, ', M21=', sintheta, ', M22=', costheta,
2595
- ', sizingMethod=\'auto expand\')'].join('') : NONE;
2596
- }
2597
- css(elem, rotationStyle);
2598
+ radians = rotation * deg2rad; // deg to rad
2599
+ costheta = mathCos(radians);
2600
+ sintheta = mathSin(radians);
2601
+
2602
+ wrapper.setSpanRotation(rotation, sintheta, costheta);
2603
+
2598
2604
  }
2599
2605
 
2600
2606
  width = pick(wrapper.elemWidth, elem.offsetWidth);
@@ -2652,6 +2658,17 @@ SVGElement.prototype = {
2652
2658
  }
2653
2659
  },
2654
2660
 
2661
+ /**
2662
+ * Set the rotation of an individual HTML span
2663
+ */
2664
+ setSpanRotation: function (rotation) {
2665
+ var rotationStyle = {},
2666
+ cssTransformKey = isIE ? '-ms-transform' : isWebKit ? '-webkit-transform' : isFirefox ? 'MozTransform' : isOpera ? '-o-transform' : '';
2667
+
2668
+ rotationStyle[cssTransformKey] = rotationStyle.transform = 'rotate(' + rotation + 'deg)';
2669
+ css(this.element, rotationStyle);
2670
+ },
2671
+
2655
2672
  /**
2656
2673
  * Private method to update the transform attribute based on internal
2657
2674
  * properties
@@ -2954,6 +2971,8 @@ SVGElement.prototype = {
2954
2971
  var wrapper = this,
2955
2972
  element = wrapper.element || {},
2956
2973
  shadows = wrapper.shadows,
2974
+ parentToClean = wrapper.renderer.isSVG && element.nodeName === 'SPAN' && element.parentNode,
2975
+ grandParent,
2957
2976
  key,
2958
2977
  i;
2959
2978
 
@@ -2983,6 +3002,13 @@ SVGElement.prototype = {
2983
3002
  });
2984
3003
  }
2985
3004
 
3005
+ // In case of useHTML, clean up empty containers emulating SVG groups (#1960).
3006
+ while (parentToClean && parentToClean.childNodes.length === 0) {
3007
+ grandParent = parentToClean.parentNode;
3008
+ wrapper.safeRemoveChild(parentToClean);
3009
+ parentToClean = grandParent;
3010
+ }
3011
+
2986
3012
  // remove from alignObjects
2987
3013
  if (wrapper.alignTo) {
2988
3014
  erase(wrapper.renderer.alignedObjects, wrapper);
@@ -3071,18 +3097,24 @@ SVGRenderer.prototype = {
3071
3097
  var renderer = this,
3072
3098
  loc = location,
3073
3099
  boxWrapper,
3100
+ element,
3074
3101
  desc;
3075
3102
 
3076
3103
  boxWrapper = renderer.createElement('svg')
3077
3104
  .attr({
3078
- xmlns: SVG_NS,
3079
3105
  version: '1.1'
3080
3106
  });
3081
- container.appendChild(boxWrapper.element);
3107
+ element = boxWrapper.element;
3108
+ container.appendChild(element);
3109
+
3110
+ // For browsers other than IE, add the namespace attribute (#1978)
3111
+ if (container.innerHTML.indexOf('xmlns') === -1) {
3112
+ attr(element, 'xmlns', SVG_NS);
3113
+ }
3082
3114
 
3083
3115
  // object properties
3084
3116
  renderer.isSVG = true;
3085
- renderer.box = boxWrapper.element;
3117
+ renderer.box = element;
3086
3118
  renderer.boxWrapper = boxWrapper;
3087
3119
  renderer.alignedObjects = [];
3088
3120
 
@@ -3203,7 +3235,7 @@ SVGRenderer.prototype = {
3203
3235
  .split(/<br.*?>/g),
3204
3236
  childNodes = textNode.childNodes,
3205
3237
  styleRegex = /style="([^"]+)"/,
3206
- hrefRegex = /href="([^"]+)"/,
3238
+ hrefRegex = /href="(http[^"]+)"/,
3207
3239
  parentX = attr(textNode, 'x'),
3208
3240
  textStyles = wrapper.styles,
3209
3241
  width = textStyles && textStyles.width && pInt(textStyles.width),
@@ -3249,82 +3281,86 @@ SVGRenderer.prototype = {
3249
3281
  .replace(/&lt;/g, '<')
3250
3282
  .replace(/&gt;/g, '>');
3251
3283
 
3252
- // add the text node
3253
- tspan.appendChild(doc.createTextNode(span));
3254
-
3255
- if (!spanNo) { // first span in a line, align it to the left
3256
- attributes.x = parentX;
3257
- } else {
3258
- attributes.dx = 0; // #16
3259
- }
3284
+ // Nested tags aren't supported, and cause crash in Safari (#1596)
3285
+ if (span !== ' ') {
3286
+
3287
+ // add the text node
3288
+ tspan.appendChild(doc.createTextNode(span));
3260
3289
 
3261
- // add attributes
3262
- attr(tspan, attributes);
3290
+ if (!spanNo) { // first span in a line, align it to the left
3291
+ attributes.x = parentX;
3292
+ } else {
3293
+ attributes.dx = 0; // #16
3294
+ }
3263
3295
 
3264
- // first span on subsequent line, add the line height
3265
- if (!spanNo && lineNo) {
3296
+ // add attributes
3297
+ attr(tspan, attributes);
3266
3298
 
3267
- // allow getting the right offset height in exporting in IE
3268
- if (!hasSVG && forExport) {
3269
- css(tspan, { display: 'block' });
3270
- }
3299
+ // first span on subsequent line, add the line height
3300
+ if (!spanNo && lineNo) {
3271
3301
 
3272
- // Set the line height based on the font size of either
3273
- // the text element or the tspan element
3274
- attr(
3275
- tspan,
3276
- 'dy',
3277
- textLineHeight || renderer.fontMetrics(
3278
- /px$/.test(tspan.style.fontSize) ?
3279
- tspan.style.fontSize :
3280
- textStyles.fontSize
3281
- ).h,
3282
- // Safari 6.0.2 - too optimized for its own good (#1539)
3283
- // TODO: revisit this with future versions of Safari
3284
- isWebKit && tspan.offsetHeight
3285
- );
3286
- }
3302
+ // allow getting the right offset height in exporting in IE
3303
+ if (!hasSVG && forExport) {
3304
+ css(tspan, { display: 'block' });
3305
+ }
3287
3306
 
3288
- // Append it
3289
- textNode.appendChild(tspan);
3307
+ // Set the line height based on the font size of either
3308
+ // the text element or the tspan element
3309
+ attr(
3310
+ tspan,
3311
+ 'dy',
3312
+ textLineHeight || renderer.fontMetrics(
3313
+ /px$/.test(tspan.style.fontSize) ?
3314
+ tspan.style.fontSize :
3315
+ textStyles.fontSize
3316
+ ).h,
3317
+ // Safari 6.0.2 - too optimized for its own good (#1539)
3318
+ // TODO: revisit this with future versions of Safari
3319
+ isWebKit && tspan.offsetHeight
3320
+ );
3321
+ }
3290
3322
 
3291
- spanNo++;
3323
+ // Append it
3324
+ textNode.appendChild(tspan);
3292
3325
 
3293
- // check width and apply soft breaks
3294
- if (width) {
3295
- var words = span.replace(/([^\^])-/g, '$1- ').split(' '), // #1273
3296
- tooLong,
3297
- actualWidth,
3298
- rest = [];
3326
+ spanNo++;
3299
3327
 
3300
- while (words.length || rest.length) {
3301
- delete wrapper.bBox; // delete cache
3302
- actualWidth = wrapper.getBBox().width;
3303
- tooLong = actualWidth > width;
3304
- if (!tooLong || words.length === 1) { // new line needed
3305
- words = rest;
3328
+ // check width and apply soft breaks
3329
+ if (width) {
3330
+ var words = span.replace(/([^\^])-/g, '$1- ').split(' '), // #1273
3331
+ tooLong,
3332
+ actualWidth,
3306
3333
  rest = [];
3307
- if (words.length) {
3308
- tspan = doc.createElementNS(SVG_NS, 'tspan');
3309
- attr(tspan, {
3310
- dy: textLineHeight || 16,
3311
- x: parentX
3312
- });
3313
- if (spanStyle) { // #390
3314
- attr(tspan, 'style', spanStyle);
3315
- }
3316
- textNode.appendChild(tspan);
3317
3334
 
3318
- if (actualWidth > width) { // a single word is pressing it out
3319
- width = actualWidth;
3335
+ while (words.length || rest.length) {
3336
+ delete wrapper.bBox; // delete cache
3337
+ actualWidth = wrapper.getBBox().width;
3338
+ tooLong = actualWidth > width;
3339
+ if (!tooLong || words.length === 1) { // new line needed
3340
+ words = rest;
3341
+ rest = [];
3342
+ if (words.length) {
3343
+ tspan = doc.createElementNS(SVG_NS, 'tspan');
3344
+ attr(tspan, {
3345
+ dy: textLineHeight || 16,
3346
+ x: parentX
3347
+ });
3348
+ if (spanStyle) { // #390
3349
+ attr(tspan, 'style', spanStyle);
3350
+ }
3351
+ textNode.appendChild(tspan);
3352
+
3353
+ if (actualWidth > width) { // a single word is pressing it out
3354
+ width = actualWidth;
3355
+ }
3320
3356
  }
3357
+ } else { // append to existing line tspan
3358
+ tspan.removeChild(tspan.firstChild);
3359
+ rest.unshift(words.pop());
3360
+ }
3361
+ if (words.length) {
3362
+ tspan.appendChild(doc.createTextNode(words.join(' ').replace(/- /g, '-')));
3321
3363
  }
3322
- } else { // append to existing line tspan
3323
- tspan.removeChild(tspan.firstChild);
3324
- rest.unshift(words.pop());
3325
- }
3326
- if (words.length) {
3327
- tspan.appendChild(doc.createTextNode(words.join(' ').replace(/- /g, '-')));
3328
3364
  }
3329
3365
  }
3330
3366
  }
@@ -3343,7 +3379,7 @@ SVGRenderer.prototype = {
3343
3379
  * @param {Object} hoverState
3344
3380
  * @param {Object} pressedState
3345
3381
  */
3346
- button: function (text, x, y, callback, normalState, hoverState, pressedState) {
3382
+ button: function (text, x, y, callback, normalState, hoverState, pressedState, disabledState) {
3347
3383
  var label = this.label(text, x, y, null, null, null, null, null, 'button'),
3348
3384
  curState = 0,
3349
3385
  stateOptions,
@@ -3351,6 +3387,7 @@ SVGRenderer.prototype = {
3351
3387
  normalStyle,
3352
3388
  hoverStyle,
3353
3389
  pressedStyle,
3390
+ disabledStyle,
3354
3391
  STYLE = 'style',
3355
3392
  verticalGradient = { x1: 0, y1: 0, x2: 0, y2: 1 };
3356
3393
 
@@ -3402,32 +3439,50 @@ SVGRenderer.prototype = {
3402
3439
  pressedStyle = pressedState[STYLE];
3403
3440
  delete pressedState[STYLE];
3404
3441
 
3405
- // add the events
3406
- addEvent(label.element, 'mouseenter', function () {
3407
- label.attr(hoverState)
3408
- .css(hoverStyle);
3442
+ // Disabled state
3443
+ disabledState = merge(normalState, {
3444
+ style: {
3445
+ color: '#CCC'
3446
+ }
3447
+ }, disabledState);
3448
+ disabledStyle = disabledState[STYLE];
3449
+ delete disabledState[STYLE];
3450
+
3451
+ // Add the events. IE9 and IE10 need mouseover and mouseout to funciton (#667).
3452
+ addEvent(label.element, isIE ? 'mouseover' : 'mouseenter', function () {
3453
+ if (curState !== 3) {
3454
+ label.attr(hoverState)
3455
+ .css(hoverStyle);
3456
+ }
3409
3457
  });
3410
- addEvent(label.element, 'mouseleave', function () {
3411
- stateOptions = [normalState, hoverState, pressedState][curState];
3412
- stateStyle = [normalStyle, hoverStyle, pressedStyle][curState];
3413
- label.attr(stateOptions)
3414
- .css(stateStyle);
3458
+ addEvent(label.element, isIE ? 'mouseout' : 'mouseleave', function () {
3459
+ if (curState !== 3) {
3460
+ stateOptions = [normalState, hoverState, pressedState][curState];
3461
+ stateStyle = [normalStyle, hoverStyle, pressedStyle][curState];
3462
+ label.attr(stateOptions)
3463
+ .css(stateStyle);
3464
+ }
3415
3465
  });
3416
3466
 
3417
3467
  label.setState = function (state) {
3418
- curState = state;
3468
+ label.state = curState = state;
3419
3469
  if (!state) {
3420
3470
  label.attr(normalState)
3421
3471
  .css(normalStyle);
3422
3472
  } else if (state === 2) {
3423
3473
  label.attr(pressedState)
3424
3474
  .css(pressedStyle);
3475
+ } else if (state === 3) {
3476
+ label.attr(disabledState)
3477
+ .css(disabledStyle);
3425
3478
  }
3426
3479
  };
3427
3480
 
3428
3481
  return label
3429
3482
  .on('click', function () {
3430
- callback.call(label);
3483
+ if (curState !== 3) {
3484
+ callback.call(label);
3485
+ }
3431
3486
  })
3432
3487
  .attr(normalState)
3433
3488
  .css(extend({ cursor: 'default' }, normalStyle));
@@ -3496,8 +3551,7 @@ SVGRenderer.prototype = {
3496
3551
  * @param {Number} end Ending angle
3497
3552
  */
3498
3553
  arc: function (x, y, r, innerR, start, end) {
3499
- // arcs are defined as symbols for the ability to set
3500
- // attributes in attr and animate
3554
+ var arc;
3501
3555
 
3502
3556
  if (isObject(x)) {
3503
3557
  y = x.y;
@@ -3507,11 +3561,16 @@ SVGRenderer.prototype = {
3507
3561
  end = x.end;
3508
3562
  x = x.x;
3509
3563
  }
3510
- return this.symbol('arc', x || 0, y || 0, r || 0, r || 0, {
3564
+
3565
+ // Arcs are defined as symbols for the ability to set
3566
+ // attributes in attr and animate
3567
+ arc = this.symbol('arc', x || 0, y || 0, r || 0, r || 0, {
3511
3568
  innerR: innerR || 0,
3512
3569
  start: start || 0,
3513
3570
  end: end || 0
3514
3571
  });
3572
+ arc.r = r; // #959
3573
+ return arc;
3515
3574
  },
3516
3575
 
3517
3576
  /**
@@ -4079,7 +4138,7 @@ SVGRenderer.prototype = {
4079
4138
  // Ensure dynamically updating position when any parent is translated
4080
4139
  each(parents.reverse(), function (parentGroup) {
4081
4140
  var htmlGroupStyle;
4082
-
4141
+
4083
4142
  // Create a HTML div and append it to the parent div to emulate
4084
4143
  // the SVG group structure
4085
4144
  htmlGroup = parentGroup.div = parentGroup.div || createElement(DIV, {
@@ -4382,7 +4441,7 @@ SVGRenderer.prototype = {
4382
4441
  if (styles) {
4383
4442
  var textStyles = {};
4384
4443
  styles = merge(styles); // create a copy to avoid altering the original object (#537)
4385
- each(['fontSize', 'fontWeight', 'fontFamily', 'color', 'lineHeight', 'width', 'textDecoration'], function (prop) {
4444
+ each(['fontSize', 'fontWeight', 'fontFamily', 'color', 'lineHeight', 'width', 'textDecoration', 'textShadow'], function (prop) {
4386
4445
  if (styles[prop] !== UNDEFINED) {
4387
4446
  textStyles[prop] = styles[prop];
4388
4447
  delete styles[prop];
@@ -4537,6 +4596,72 @@ Highcharts.VMLElement = VMLElement = {
4537
4596
  */
4538
4597
  updateTransform: SVGElement.prototype.htmlUpdateTransform,
4539
4598
 
4599
+ /**
4600
+ * Set the rotation of a span with oldIE's filter
4601
+ */
4602
+ setSpanRotation: function (rotation, sintheta, costheta) {
4603
+ // Adjust for alignment and rotation. Rotation of useHTML content is not yet implemented
4604
+ // but it can probably be implemented for Firefox 3.5+ on user request. FF3.5+
4605
+ // has support for CSS3 transform. The getBBox method also needs to be updated
4606
+ // to compensate for the rotation, like it currently does for SVG.
4607
+ // Test case: http://highcharts.com/tests/?file=text-rotation
4608
+ css(this.element, {
4609
+ filter: rotation ? ['progid:DXImageTransform.Microsoft.Matrix(M11=', costheta,
4610
+ ', M12=', -sintheta, ', M21=', sintheta, ', M22=', costheta,
4611
+ ', sizingMethod=\'auto expand\')'].join('') : NONE
4612
+ });
4613
+ },
4614
+
4615
+ /**
4616
+ * Converts a subset of an SVG path definition to its VML counterpart. Takes an array
4617
+ * as the parameter and returns a string.
4618
+ */
4619
+ pathToVML: function (value) {
4620
+ // convert paths
4621
+ var i = value.length,
4622
+ path = [],
4623
+ clockwise;
4624
+
4625
+ while (i--) {
4626
+
4627
+ // Multiply by 10 to allow subpixel precision.
4628
+ // Substracting half a pixel seems to make the coordinates
4629
+ // align with SVG, but this hasn't been tested thoroughly
4630
+ if (isNumber(value[i])) {
4631
+ path[i] = mathRound(value[i] * 10) - 5;
4632
+ } else if (value[i] === 'Z') { // close the path
4633
+ path[i] = 'x';
4634
+ } else {
4635
+ path[i] = value[i];
4636
+
4637
+ // When the start X and end X coordinates of an arc are too close,
4638
+ // they are rounded to the same value above. In this case, substract 1 from the end X
4639
+ // position. #760, #1371.
4640
+ if (value.isArc && (value[i] === 'wa' || value[i] === 'at')) {
4641
+ clockwise = value[i] === 'wa' ? 1 : -1; // #1642
4642
+ if (path[i + 5] === path[i + 7]) {
4643
+ path[i + 7] -= clockwise;
4644
+ }
4645
+ // Start and end Y (#1410)
4646
+ if (path[i + 6] === path[i + 8]) {
4647
+ path[i + 8] -= clockwise;
4648
+ }
4649
+ }
4650
+ }
4651
+ }
4652
+ // Loop up again to handle path shortcuts (#2132)
4653
+ /*while (i++ < path.length) {
4654
+ if (path[i] === 'H') { // horizontal line to
4655
+ path[i] = 'L';
4656
+ path.splice(i + 2, 0, path[i - 1]);
4657
+ } else if (path[i] === 'V') { // vertical line to
4658
+ path[i] = 'L';
4659
+ path.splice(i + 1, 0, path[i - 2]);
4660
+ }
4661
+ }*/
4662
+ return path.join(' ') || 'x';
4663
+ },
4664
+
4540
4665
  /**
4541
4666
  * Get or set attributes
4542
4667
  */
@@ -4606,40 +4731,7 @@ Highcharts.VMLElement = VMLElement = {
4606
4731
  value = value || [];
4607
4732
  wrapper.d = value.join(' '); // used in getter for animation
4608
4733
 
4609
- // convert paths
4610
- i = value.length;
4611
- var convertedPath = [],
4612
- clockwise;
4613
- while (i--) {
4614
-
4615
- // Multiply by 10 to allow subpixel precision.
4616
- // Substracting half a pixel seems to make the coordinates
4617
- // align with SVG, but this hasn't been tested thoroughly
4618
- if (isNumber(value[i])) {
4619
- convertedPath[i] = mathRound(value[i] * 10) - 5;
4620
- } else if (value[i] === 'Z') { // close the path
4621
- convertedPath[i] = 'x';
4622
- } else {
4623
- convertedPath[i] = value[i];
4624
-
4625
- // When the start X and end X coordinates of an arc are too close,
4626
- // they are rounded to the same value above. In this case, substract 1 from the end X
4627
- // position. #760, #1371.
4628
- if (value.isArc && (value[i] === 'wa' || value[i] === 'at')) {
4629
- clockwise = value[i] === 'wa' ? 1 : -1; // #1642
4630
- if (convertedPath[i + 5] === convertedPath[i + 7]) {
4631
- convertedPath[i + 7] -= clockwise;
4632
- }
4633
- // Start and end Y (#1410)
4634
- if (convertedPath[i + 6] === convertedPath[i + 8]) {
4635
- convertedPath[i + 8] -= clockwise;
4636
- }
4637
- }
4638
- }
4639
-
4640
- }
4641
- value = convertedPath.join(' ') || 'x';
4642
- element.path = value;
4734
+ element.path = value = wrapper.pathToVML(value);
4643
4735
 
4644
4736
  // update shadows
4645
4737
  if (shadows) {
@@ -4759,7 +4851,9 @@ Highcharts.VMLElement = VMLElement = {
4759
4851
 
4760
4852
  // rotation on VML elements
4761
4853
  } else if (nodeName === 'shape' && key === 'rotation') {
4762
- wrapper[key] = value;
4854
+
4855
+ wrapper[key] = element.style[key] = value; // style is for #1873
4856
+
4763
4857
  // Correction for the 1x1 size of the shape container. Used in gauge needles.
4764
4858
  element.style.left = -mathRound(mathSin(value * deg2rad) + 1) + PX;
4765
4859
  element.style.top = mathRound(mathCos(value * deg2rad)) + PX;
@@ -5036,10 +5130,10 @@ var VMLRendererExtension = { // inherit SVGRenderer
5036
5130
  // mimic a rectangle with its style object for automatic updating in attr
5037
5131
  return extend(clipRect, {
5038
5132
  members: [],
5039
- left: isObj ? x.x : x,
5040
- top: isObj ? x.y : y,
5041
- width: isObj ? x.width : width,
5042
- height: isObj ? x.height : height,
5133
+ left: (isObj ? x.x : x) + 1,
5134
+ top: (isObj ? x.y : y) + 1,
5135
+ width: (isObj ? x.width : width) - 1,
5136
+ height: (isObj ? x.height : height) - 1,
5043
5137
  getCSS: function (wrapper) {
5044
5138
  var element = wrapper.element,
5045
5139
  nodeName = element.nodeName,
@@ -5371,17 +5465,16 @@ var VMLRendererExtension = { // inherit SVGRenderer
5371
5465
  */
5372
5466
  rect: function (x, y, width, height, r, strokeWidth) {
5373
5467
 
5374
- if (isObject(x)) {
5375
- y = x.y;
5376
- width = x.width;
5377
- height = x.height;
5378
- strokeWidth = x.strokeWidth;
5379
- x = x.x;
5380
- }
5381
5468
  var wrapper = this.symbol('rect');
5382
- wrapper.r = r;
5469
+ wrapper.r = isObject(x) ? x.r : r;
5383
5470
 
5384
- return wrapper.attr(wrapper.crisp(strokeWidth, x, y, mathMax(width, 0), mathMax(height, 0)));
5471
+ //return wrapper.attr(wrapper.crisp(strokeWidth, x, y, mathMax(width, 0), mathMax(height, 0)));
5472
+ return wrapper.attr(
5473
+ isObject(x) ?
5474
+ x :
5475
+ // do not crispify when an object is passed in (as in column charts)
5476
+ wrapper.crisp(strokeWidth, x, y, mathMax(width, 0), mathMax(height, 0))
5477
+ );
5385
5478
  },
5386
5479
 
5387
5480
  /**
@@ -5669,7 +5762,7 @@ Tick.prototype = {
5669
5762
  !labelOptions.step && !labelOptions.staggerLines &&
5670
5763
  !labelOptions.rotation &&
5671
5764
  chart.plotWidth / tickPositions.length) ||
5672
- (!horiz && (chart.optionsMarginLeft || chart.plotWidth / 2)), // #1580
5765
+ (!horiz && (chart.optionsMarginLeft || chart.chartWidth * 0.33)), // #1580, #1931
5673
5766
  isFirst = pos === tickPositions[0],
5674
5767
  isLast = pos === tickPositions[tickPositions.length - 1],
5675
5768
  css,
@@ -5708,7 +5801,7 @@ Tick.prototype = {
5708
5801
  // first call
5709
5802
  if (!defined(label)) {
5710
5803
  attr = {
5711
- align: labelOptions.align
5804
+ align: axis.labelAlign
5712
5805
  };
5713
5806
  if (isNumber(labelOptions.rotation)) {
5714
5807
  attr.rotation = labelOptions.rotation;
@@ -5757,7 +5850,7 @@ Tick.prototype = {
5757
5850
  options = axis.options,
5758
5851
  labelOptions = options.labels,
5759
5852
  width = bBox.width,
5760
- leftSide = width * { left: 0, center: 0.5, right: 1 }[labelOptions.align] - labelOptions.x;
5853
+ leftSide = width * { left: 0, center: 0.5, right: 1 }[axis.labelAlign] - labelOptions.x;
5761
5854
 
5762
5855
  return [-leftSide, width - leftSide];
5763
5856
  },
@@ -5847,21 +5940,28 @@ Tick.prototype = {
5847
5940
  var axis = this.axis,
5848
5941
  transA = axis.transA,
5849
5942
  reversed = axis.reversed,
5850
- staggerLines = axis.staggerLines;
5943
+ staggerLines = axis.staggerLines,
5944
+ baseline = axis.chart.renderer.fontMetrics(labelOptions.style.fontSize).b,
5945
+ rotation = labelOptions.rotation;
5851
5946
 
5852
5947
  x = x + labelOptions.x - (tickmarkOffset && horiz ?
5853
5948
  tickmarkOffset * transA * (reversed ? -1 : 1) : 0);
5854
5949
  y = y + labelOptions.y - (tickmarkOffset && !horiz ?
5855
5950
  tickmarkOffset * transA * (reversed ? 1 : -1) : 0);
5951
+
5952
+ // Correct for rotation (#1764)
5953
+ if (rotation && axis.side === 2) {
5954
+ y -= baseline - baseline * mathCos(rotation * deg2rad);
5955
+ }
5856
5956
 
5857
5957
  // Vertically centered
5858
- if (!defined(labelOptions.y)) {
5859
- y += pInt(label.styles.lineHeight) * 0.9 - label.getBBox().height / 2;
5958
+ if (!defined(labelOptions.y) && !rotation) { // #1951
5959
+ y += baseline - label.getBBox().height / 2;
5860
5960
  }
5861
5961
 
5862
5962
  // Correct for staggered labels
5863
5963
  if (staggerLines) {
5864
- y += (index / (step || 1) % staggerLines) * 16;
5964
+ y += (index / (step || 1) % staggerLines) * (axis.labelOffset / staggerLines);
5865
5965
  }
5866
5966
 
5867
5967
  return {
@@ -5921,7 +6021,7 @@ Tick.prototype = {
5921
6021
  xy = tick.getPosition(horiz, pos, tickmarkOffset, old),
5922
6022
  x = xy.x,
5923
6023
  y = xy.y,
5924
- reverseCrisp = ((horiz && x === axis.pos) || (!horiz && y === axis.pos + axis.len)) ? -1 : 1, // #1480
6024
+ reverseCrisp = ((horiz && x === axis.pos + axis.len) || (!horiz && y === axis.pos)) ? -1 : 1, // #1480, #1687
5925
6025
  staggerLines = axis.staggerLines;
5926
6026
 
5927
6027
  this.isActive = true;
@@ -5994,9 +6094,10 @@ Tick.prototype = {
5994
6094
  if (label && !isNaN(x)) {
5995
6095
  label.xy = xy = tick.getLabelPosition(x, y, label, horiz, labelOptions, tickmarkOffset, index, step);
5996
6096
 
5997
- // apply show first and show last
5998
- if ((tick.isFirst && !pick(options.showFirstLabel, 1)) ||
5999
- (tick.isLast && !pick(options.showLastLabel, 1))) {
6097
+ // Apply show first and show last. If the tick is both first and last, it is
6098
+ // a single centered tick, in which case we show the label anyway (#2100).
6099
+ if ((tick.isFirst && !tick.isLast && !pick(options.showFirstLabel, 1)) ||
6100
+ (tick.isLast && !tick.isFirst && !pick(options.showLastLabel, 1))) {
6000
6101
  show = false;
6001
6102
 
6002
6103
  // Handle label overflow and show or hide accordingly
@@ -6160,7 +6261,8 @@ PlotLineOrBand.prototype = {
6160
6261
  plotLine.label = label = renderer.text(
6161
6262
  optionsLabel.text,
6162
6263
  0,
6163
- 0
6264
+ 0,
6265
+ optionsLabel.useHTML
6164
6266
  )
6165
6267
  .attr({
6166
6268
  align: optionsLabel.textAlign || optionsLabel.align,
@@ -6197,13 +6299,11 @@ PlotLineOrBand.prototype = {
6197
6299
  * Remove the plot line or band
6198
6300
  */
6199
6301
  destroy: function () {
6200
- var plotLine = this,
6201
- axis = plotLine.axis;
6202
-
6203
6302
  // remove it from the lookup
6204
- erase(axis.plotLinesAndBands, plotLine);
6205
-
6206
- destroyObjectProperties(plotLine, this.axis);
6303
+ erase(this.axis.plotLinesAndBands, this);
6304
+
6305
+ delete this.axis;
6306
+ destroyObjectProperties(this);
6207
6307
  }
6208
6308
  };
6209
6309
  /**
@@ -6224,6 +6324,12 @@ function StackItem(axis, options, isNegative, x, stackOption, stacking) {
6224
6324
  // Save the x value to be able to position the label later
6225
6325
  this.x = x;
6226
6326
 
6327
+ // Initialize total value
6328
+ this.total = null;
6329
+
6330
+ // This will keep each points' extremes stored by series.index
6331
+ this.points = {};
6332
+
6227
6333
  // Save the stack option on the series configuration object, and whether to treat it as percent
6228
6334
  this.stack = stackOption;
6229
6335
  this.percent = stacking === 'percent';
@@ -6255,12 +6361,19 @@ StackItem.prototype = {
6255
6361
  this.cum = total;
6256
6362
  },
6257
6363
 
6364
+ /**
6365
+ * Adds value to stack total, this method takes care of correcting floats
6366
+ */
6367
+ addValue: function (y) {
6368
+ this.setTotal(correctFloat(this.total + y));
6369
+ },
6370
+
6258
6371
  /**
6259
6372
  * Renders the stack total label and adds it to the stack label group.
6260
6373
  */
6261
6374
  render: function (group) {
6262
6375
  var options = this.options,
6263
- formatOption = options.format, // docs: added stackLabel.format option
6376
+ formatOption = options.format,
6264
6377
  str = formatOption ?
6265
6378
  format(formatOption, this) :
6266
6379
  options.formatter.call(this); // format the text in the label
@@ -6282,6 +6395,10 @@ StackItem.prototype = {
6282
6395
  }
6283
6396
  },
6284
6397
 
6398
+ cacheExtremes: function (series, extremes) {
6399
+ this.points[series.index] = extremes;
6400
+ },
6401
+
6285
6402
  /**
6286
6403
  * Sets the offset that the stack has from the x value and repositions the label.
6287
6404
  */
@@ -6421,7 +6538,6 @@ Axis.prototype = {
6421
6538
  tickPixelInterval: 72,
6422
6539
  showLastLabel: true,
6423
6540
  labels: {
6424
- align: 'right',
6425
6541
  x: -8,
6426
6542
  y: 3
6427
6543
  },
@@ -6454,7 +6570,6 @@ Axis.prototype = {
6454
6570
  */
6455
6571
  defaultLeftAxisOptions: {
6456
6572
  labels: {
6457
- align: 'right',
6458
6573
  x: -8,
6459
6574
  y: null
6460
6575
  },
@@ -6468,7 +6583,6 @@ Axis.prototype = {
6468
6583
  */
6469
6584
  defaultRightAxisOptions: {
6470
6585
  labels: {
6471
- align: 'left',
6472
6586
  x: 8,
6473
6587
  y: null
6474
6588
  },
@@ -6482,7 +6596,6 @@ Axis.prototype = {
6482
6596
  */
6483
6597
  defaultBottomAxisOptions: {
6484
6598
  labels: {
6485
- align: 'center',
6486
6599
  x: 0,
6487
6600
  y: 14
6488
6601
  // overflow: undefined,
@@ -6497,7 +6610,6 @@ Axis.prototype = {
6497
6610
  */
6498
6611
  defaultTopAxisOptions: {
6499
6612
  labels: {
6500
- align: 'center',
6501
6613
  x: 0,
6502
6614
  y: -5
6503
6615
  // overflow: undefined
@@ -6541,7 +6653,6 @@ Axis.prototype = {
6541
6653
 
6542
6654
 
6543
6655
  // Flag, stagger lines or not
6544
- axis.staggerLines = axis.horiz && options.labels.staggerLines;
6545
6656
  axis.userOptions = userOptions;
6546
6657
 
6547
6658
  //axis.axisTitleMargin = UNDEFINED,// = options.title.margin,
@@ -6619,8 +6730,11 @@ Axis.prototype = {
6619
6730
 
6620
6731
  // Dictionary for stacks
6621
6732
  axis.stacks = {};
6622
- axis._stacksTouched = 0;
6623
-
6733
+ axis.oldStacks = {};
6734
+
6735
+ // Dictionary for stacks max values
6736
+ axis.stackExtremes = {};
6737
+
6624
6738
  // Min and max in the data
6625
6739
  //axis.dataMin = UNDEFINED,
6626
6740
  //axis.dataMax = UNDEFINED,
@@ -6691,10 +6805,10 @@ Axis.prototype = {
6691
6805
 
6692
6806
  newOptions = chart.options[this.xOrY + 'Axis'][this.options.index] = merge(this.userOptions, newOptions);
6693
6807
 
6694
- this.destroy();
6808
+ this.destroy(true);
6695
6809
  this._addedPlotLB = false; // #1611
6696
6810
 
6697
- this.init(chart, newOptions);
6811
+ this.init(chart, extend(newOptions, { events: UNDEFINED }));
6698
6812
 
6699
6813
  chart.isDirtyBox = true;
6700
6814
  if (pick(redraw, true)) {
@@ -6778,25 +6892,24 @@ Axis.prototype = {
6778
6892
 
6779
6893
  return ret;
6780
6894
  },
6781
-
6895
+
6782
6896
  /**
6783
6897
  * Get the minimum and maximum for the series of each axis
6784
6898
  */
6785
6899
  getSeriesExtremes: function () {
6786
6900
  var axis = this,
6787
- chart = axis.chart,
6788
- stacks = axis.stacks,
6789
- posStack = [],
6790
- negStack = [],
6791
- stacksTouched = axis._stacksTouched = axis._stacksTouched + 1,
6792
- type,
6793
- i;
6794
-
6901
+ chart = axis.chart;
6902
+
6795
6903
  axis.hasVisibleSeries = false;
6796
6904
 
6797
6905
  // reset dataMin and dataMax in case we're redrawing
6798
6906
  axis.dataMin = axis.dataMax = null;
6799
6907
 
6908
+ // reset cached stacking extremes
6909
+ axis.stackExtremes = {};
6910
+
6911
+ axis.buildStacks();
6912
+
6800
6913
  // loop through this axis' series
6801
6914
  each(axis.series, function (series) {
6802
6915
 
@@ -6804,27 +6917,16 @@ Axis.prototype = {
6804
6917
 
6805
6918
  var seriesOptions = series.options,
6806
6919
  stacking,
6807
- posPointStack,
6808
- negPointStack,
6809
- stackKey,
6810
- stackOption,
6811
- negKey,
6812
6920
  xData,
6813
- yData,
6814
- x,
6815
- y,
6816
6921
  threshold = seriesOptions.threshold,
6817
- yDataLength,
6818
- activeYData = [],
6819
6922
  seriesDataMin,
6820
- seriesDataMax,
6821
- activeCounter = 0;
6822
-
6823
- axis.hasVisibleSeries = true;
6824
-
6923
+ seriesDataMax;
6924
+
6925
+ axis.hasVisibleSeries = true;
6926
+
6825
6927
  // Validate threshold in logarithmic axes
6826
6928
  if (axis.isLog && threshold <= 0) {
6827
- threshold = seriesOptions.threshold = null;
6929
+ threshold = null;
6828
6930
  }
6829
6931
 
6830
6932
  // Get dataMin and dataMax for X axes
@@ -6837,112 +6939,27 @@ Axis.prototype = {
6837
6939
 
6838
6940
  // Get dataMin and dataMax for Y axes, as well as handle stacking and processed data
6839
6941
  } else {
6840
- var isNegative,
6841
- pointStack,
6842
- key,
6843
- cropped = series.cropped,
6844
- xExtremes = series.xAxis.getExtremes(),
6845
- //findPointRange,
6846
- //pointRange,
6847
- j,
6848
- hasModifyValue = !!series.modifyValue;
6849
6942
 
6850
6943
  // Handle stacking
6851
6944
  stacking = seriesOptions.stacking;
6852
6945
  axis.usePercentage = stacking === 'percent';
6853
6946
 
6854
6947
  // create a stack for this particular series type
6855
- if (stacking) {
6856
- stackOption = seriesOptions.stack;
6857
- stackKey = series.type + pick(stackOption, '');
6858
- negKey = '-' + stackKey;
6859
- series.stackKey = stackKey; // used in translate
6860
-
6861
- posPointStack = posStack[stackKey] || []; // contains the total values for each x
6862
- posStack[stackKey] = posPointStack;
6863
-
6864
- negPointStack = negStack[negKey] || [];
6865
- negStack[negKey] = negPointStack;
6866
- }
6867
6948
  if (axis.usePercentage) {
6868
6949
  axis.dataMin = 0;
6869
6950
  axis.dataMax = 99;
6870
6951
  }
6871
6952
 
6872
- // processData can alter series.pointRange, so this goes after
6873
- //findPointRange = series.pointRange === null;
6874
-
6875
- xData = series.processedXData;
6876
- yData = series.processedYData;
6877
- yDataLength = yData.length;
6878
-
6879
- // loop over the non-null y values and read them into a local array
6880
- for (i = 0; i < yDataLength; i++) {
6881
- x = xData[i];
6882
- y = yData[i];
6883
-
6884
- // Read stacked values into a stack based on the x value,
6885
- // the sign of y and the stack key. Stacking is also handled for null values (#739)
6886
- if (stacking) {
6887
- isNegative = y < threshold;
6888
- pointStack = isNegative ? negPointStack : posPointStack;
6889
- key = isNegative ? negKey : stackKey;
6890
-
6891
- // Set the stack value and y for extremes
6892
- if (defined(pointStack[x])) { // we're adding to the stack
6893
- pointStack[x] = correctFloat(pointStack[x] + y);
6894
- y = [y, pointStack[x]]; // consider both the actual value and the stack (#1376)
6895
-
6896
- } else { // it's the first point in the stack
6897
- pointStack[x] = y;
6898
- }
6899
-
6900
- // add the series
6901
- if (!stacks[key]) {
6902
- stacks[key] = {};
6903
- }
6904
-
6905
- // If the StackItem is there, just update the values,
6906
- // if not, create one first
6907
- if (!stacks[key][x]) {
6908
- stacks[key][x] = new StackItem(axis, axis.options.stackLabels, isNegative, x, stackOption, stacking);
6909
- }
6910
- stacks[key][x].setTotal(pointStack[x]);
6911
- stacks[key][x].touched = stacksTouched;
6912
- }
6913
-
6914
- // Handle non null values
6915
- if (y !== null && y !== UNDEFINED && (!axis.isLog || (y.length || y > 0))) {
6916
-
6917
- // general hook, used for Highstock compare values feature
6918
- if (hasModifyValue) {
6919
- y = series.modifyValue(y);
6920
- }
6921
-
6922
- // For points within the visible range, including the first point outside the
6923
- // visible range, consider y extremes
6924
- if (series.getExtremesFromAll || cropped || ((xData[i + 1] || x) >= xExtremes.min &&
6925
- (xData[i - 1] || x) <= xExtremes.max)) {
6926
-
6927
- j = y.length;
6928
- if (j) { // array, like ohlc or range data
6929
- while (j--) {
6930
- if (y[j] !== null) {
6931
- activeYData[activeCounter++] = y[j];
6932
- }
6933
- }
6934
- } else {
6935
- activeYData[activeCounter++] = y;
6936
- }
6937
- }
6938
- }
6939
- }
6953
+
6954
+ // get this particular series extremes
6955
+ series.getExtremes();
6956
+ seriesDataMax = series.dataMax;
6957
+ seriesDataMin = series.dataMin;
6940
6958
 
6941
6959
  // Get the dataMin and dataMax so far. If percentage is used, the min and max are
6942
- // always 0 and 100. If the length of activeYData is 0, continue with null values.
6943
- if (!axis.usePercentage && activeYData.length) {
6944
- series.dataMin = seriesDataMin = arrayMin(activeYData);
6945
- series.dataMax = seriesDataMax = arrayMax(activeYData);
6960
+ // always 0 and 100. If seriesDataMin and seriesDataMax is null, then series
6961
+ // doesn't have active y data, we continue with nulls
6962
+ if (!axis.usePercentage && defined(seriesDataMin) && defined(seriesDataMax)) {
6946
6963
  axis.dataMin = mathMin(pick(axis.dataMin, seriesDataMin), seriesDataMin);
6947
6964
  axis.dataMax = mathMax(pick(axis.dataMax, seriesDataMax), seriesDataMax);
6948
6965
  }
@@ -6960,24 +6977,13 @@ Axis.prototype = {
6960
6977
  }
6961
6978
  }
6962
6979
  });
6963
-
6964
- // Destroy unused stacks (#1044)
6965
- for (type in stacks) {
6966
- for (i in stacks[type]) {
6967
- if (stacks[type][i].touched < stacksTouched) {
6968
- stacks[type][i].destroy();
6969
- delete stacks[type][i];
6970
- }
6971
- }
6972
- }
6973
-
6974
6980
  },
6975
6981
 
6976
6982
  /**
6977
6983
  * Translate from axis value to pixel position on the chart, or back
6978
6984
  *
6979
6985
  */
6980
- translate: function (val, backwards, cvsCoord, old, handleLog, pointPlacementBetween) {
6986
+ translate: function (val, backwards, cvsCoord, old, handleLog, pointPlacement) {
6981
6987
  var axis = this,
6982
6988
  axisLength = axis.len,
6983
6989
  sign = 1,
@@ -7020,9 +7026,11 @@ Axis.prototype = {
7020
7026
  if (postTranslate) { // log and ordinal axes
7021
7027
  val = axis.val2lin(val);
7022
7028
  }
7023
-
7029
+ if (pointPlacement === 'between') {
7030
+ pointPlacement = 0.5;
7031
+ }
7024
7032
  returnValue = sign * (val - localMin) * localA + cvsOffset + (sign * minPixelPadding) +
7025
- (pointPlacementBetween ? localA * axis.pointRange / 2 : 0);
7033
+ (isNumber(pointPlacement) ? localA * pointPlacement * axis.pointRange : 0);
7026
7034
  }
7027
7035
 
7028
7036
  return returnValue;
@@ -7226,7 +7234,7 @@ Axis.prototype = {
7226
7234
  interval = normalizeTickInterval(
7227
7235
  interval,
7228
7236
  null,
7229
- math.pow(10, mathFloor(math.log(interval) / math.LN10))
7237
+ getMagnitude(interval)
7230
7238
  );
7231
7239
 
7232
7240
  positions = map(axis.getLinearTickPositions(
@@ -7390,7 +7398,7 @@ Axis.prototype = {
7390
7398
  var seriesPointRange = series.pointRange,
7391
7399
  pointPlacement = series.options.pointPlacement,
7392
7400
  seriesClosestPointRange = series.closestPointRange;
7393
-
7401
+
7394
7402
  if (seriesPointRange > range) { // #1446
7395
7403
  seriesPointRange = 0;
7396
7404
  }
@@ -7401,7 +7409,7 @@ Axis.prototype = {
7401
7409
  // is 'between' or 'on', this padding does not apply.
7402
7410
  minPointOffset = mathMax(
7403
7411
  minPointOffset,
7404
- pointPlacement ? 0 : seriesPointRange / 2
7412
+ isString(pointPlacement) ? 0 : seriesPointRange / 2
7405
7413
  );
7406
7414
 
7407
7415
  // Determine the total padding needed to the length of the axis to make room for the
@@ -7456,7 +7464,6 @@ Axis.prototype = {
7456
7464
  isXAxis = axis.isXAxis,
7457
7465
  isLinked = axis.isLinked,
7458
7466
  tickPositioner = axis.options.tickPositioner,
7459
- magnitude,
7460
7467
  maxPadding = options.maxPadding,
7461
7468
  minPadding = options.minPadding,
7462
7469
  length,
@@ -7555,6 +7562,11 @@ Axis.prototype = {
7555
7562
  if (axis.postProcessTickInterval) {
7556
7563
  axis.tickInterval = axis.postProcessTickInterval(axis.tickInterval);
7557
7564
  }
7565
+
7566
+ // In column-like charts, don't cramp in more ticks than there are points (#1943)
7567
+ if (axis.pointRange) {
7568
+ axis.tickInterval = mathMax(axis.pointRange, axis.tickInterval);
7569
+ }
7558
7570
 
7559
7571
  // Before normalizing the tick interval, handle minimum tick interval. This applies only if tickInterval is not defined.
7560
7572
  if (!tickIntervalOption && axis.tickInterval < minTickIntervalOption) {
@@ -7563,9 +7575,8 @@ Axis.prototype = {
7563
7575
 
7564
7576
  // for linear axes, get magnitude and normalize the interval
7565
7577
  if (!isDatetimeAxis && !isLog) { // linear
7566
- magnitude = math.pow(10, mathFloor(math.log(axis.tickInterval) / math.LN10));
7567
7578
  if (!tickIntervalOption) {
7568
- axis.tickInterval = normalizeTickInterval(axis.tickInterval, null, magnitude, options);
7579
+ axis.tickInterval = normalizeTickInterval(axis.tickInterval, null, getMagnitude(axis.tickInterval), options);
7569
7580
  }
7570
7581
  }
7571
7582
 
@@ -7578,6 +7589,12 @@ Axis.prototype = {
7578
7589
  [].concat(options.tickPositions) : // Work on a copy (#1565)
7579
7590
  (tickPositioner && tickPositioner.apply(axis, [axis.min, axis.max]));
7580
7591
  if (!tickPositions) {
7592
+
7593
+ // Too many ticks
7594
+ if ((axis.max - axis.min) / axis.tickInterval > 2 * axis.len) {
7595
+ error(19, true);
7596
+ }
7597
+
7581
7598
  if (isDatetimeAxis) {
7582
7599
  tickPositions = (axis.getNonLinearTimeTicks || getTimeTicks)(
7583
7600
  normalizeTimeTickInterval(axis.tickInterval, options.units),
@@ -7706,10 +7723,19 @@ Axis.prototype = {
7706
7723
  isDirtyData = true;
7707
7724
  }
7708
7725
  });
7709
-
7726
+
7710
7727
  // do we really need to go through all this?
7711
7728
  if (isDirtyAxisLength || isDirtyData || axis.isLinked || axis.forceRedraw ||
7712
7729
  axis.userMin !== axis.oldUserMin || axis.userMax !== axis.oldUserMax) {
7730
+
7731
+ // reset stacks
7732
+ if (!axis.isXAxis) {
7733
+ for (type in stacks) {
7734
+ for (i in stacks[type]) {
7735
+ stacks[type][i].total = null;
7736
+ }
7737
+ }
7738
+ }
7713
7739
 
7714
7740
  axis.forceRedraw = false;
7715
7741
 
@@ -7727,11 +7753,12 @@ Axis.prototype = {
7727
7753
  if (!axis.isDirty) {
7728
7754
  axis.isDirty = isDirtyAxisLength || axis.min !== axis.oldMin || axis.max !== axis.oldMax;
7729
7755
  }
7730
- }
7731
-
7732
-
7733
- // reset stacks
7734
- if (!axis.isXAxis) {
7756
+ } else if (!axis.isXAxis) {
7757
+ if (axis.oldStacks) {
7758
+ stacks = axis.stacks = axis.oldStacks;
7759
+ }
7760
+
7761
+ // reset stacks
7735
7762
  for (type in stacks) {
7736
7763
  for (i in stacks[type]) {
7737
7764
  stacks[type][i].cum = stacks[type][i].total;
@@ -7787,12 +7814,12 @@ Axis.prototype = {
7787
7814
  */
7788
7815
  zoom: function (newMin, newMax) {
7789
7816
 
7790
- // Prevent pinch zooming out of range
7817
+ // Prevent pinch zooming out of range. Check for defined is for #1946.
7791
7818
  if (!this.allowZoomOutside) {
7792
- if (newMin <= this.dataMin) {
7819
+ if (defined(this.dataMin) && newMin <= this.dataMin) {
7793
7820
  newMin = UNDEFINED;
7794
7821
  }
7795
- if (newMax >= this.dataMax) {
7822
+ if (defined(this.dataMax) && newMax >= this.dataMax) {
7796
7823
  newMax = UNDEFINED;
7797
7824
  }
7798
7825
  }
@@ -7903,6 +7930,24 @@ Axis.prototype = {
7903
7930
  return obj;
7904
7931
  },
7905
7932
 
7933
+ /**
7934
+ * Compute auto alignment for the axis label based on which side the axis is on
7935
+ * and the given rotation for the label
7936
+ */
7937
+ autoLabelAlign: function (rotation) {
7938
+ var ret,
7939
+ angle = (pick(rotation, 0) - (this.side * 90) + 720) % 360;
7940
+
7941
+ if (angle > 15 && angle < 165) {
7942
+ ret = 'right';
7943
+ } else if (angle > 195 && angle < 345) {
7944
+ ret = 'left';
7945
+ } else {
7946
+ ret = 'center';
7947
+ }
7948
+ return ret;
7949
+ },
7950
+
7906
7951
  /**
7907
7952
  * Render the tick labels to a preliminary position to get their sizes
7908
7953
  */
@@ -7927,11 +7972,25 @@ Axis.prototype = {
7927
7972
  axisOffset = chart.axisOffset,
7928
7973
  clipOffset = chart.clipOffset,
7929
7974
  directionFactor = [-1, 1, 1, -1][side],
7930
- n;
7975
+ n,
7976
+ i,
7977
+ autoStaggerLines = 1,
7978
+ maxStaggerLines = pick(labelOptions.maxStaggerLines, 5),
7979
+ sortedPositions,
7980
+ lastRight,
7981
+ overlap,
7982
+ pos,
7983
+ bBox,
7984
+ x,
7985
+ w,
7986
+ lineNo;
7931
7987
 
7932
7988
  // For reuse in Axis.render
7933
7989
  axis.hasData = hasData = (axis.hasVisibleSeries || (defined(axis.min) && defined(axis.max) && !!tickPositions));
7934
7990
  axis.showAxis = showAxis = hasData || pick(options.showEmpty, true);
7991
+
7992
+ // Set/reset staggerLines
7993
+ axis.staggerLines = axis.horiz && labelOptions.staggerLines;
7935
7994
 
7936
7995
  // Create the axisGroup and gridGroup elements on first iteration
7937
7996
  if (!axis.axisGroup) {
@@ -7947,18 +8006,55 @@ Axis.prototype = {
7947
8006
  }
7948
8007
 
7949
8008
  if (hasData || axis.isLinked) {
8009
+
8010
+ // Set the explicit or automatic label alignment
8011
+ axis.labelAlign = pick(labelOptions.align || axis.autoLabelAlign(labelOptions.rotation));
8012
+
7950
8013
  each(tickPositions, function (pos) {
7951
8014
  if (!ticks[pos]) {
7952
8015
  ticks[pos] = new Tick(axis, pos);
7953
8016
  } else {
7954
8017
  ticks[pos].addLabel(); // update labels depending on tick interval
7955
8018
  }
7956
-
7957
8019
  });
7958
8020
 
8021
+ // Handle automatic stagger lines
8022
+ if (axis.horiz && !axis.staggerLines && maxStaggerLines && !labelOptions.rotation) {
8023
+ sortedPositions = axis.reversed ? [].concat(tickPositions).reverse() : tickPositions;
8024
+ while (autoStaggerLines < maxStaggerLines) {
8025
+ lastRight = [];
8026
+ overlap = false;
8027
+
8028
+ for (i = 0; i < sortedPositions.length; i++) {
8029
+ pos = sortedPositions[i];
8030
+ bBox = ticks[pos].label && ticks[pos].label.bBox;
8031
+ w = bBox ? bBox.width : 0;
8032
+ lineNo = i % autoStaggerLines;
8033
+
8034
+ if (w) {
8035
+ x = axis.translate(pos); // don't handle log
8036
+ if (lastRight[lineNo] !== UNDEFINED && x < lastRight[lineNo]) {
8037
+ overlap = true;
8038
+ }
8039
+ lastRight[lineNo] = x + w;
8040
+ }
8041
+ }
8042
+ if (overlap) {
8043
+ autoStaggerLines++;
8044
+ } else {
8045
+ break;
8046
+ }
8047
+ }
8048
+
8049
+ if (autoStaggerLines > 1) {
8050
+ axis.staggerLines = autoStaggerLines;
8051
+ }
8052
+ }
8053
+
8054
+
7959
8055
  each(tickPositions, function (pos) {
7960
8056
  // left side must be align: right and right side must have align: left for labels
7961
- if (side === 0 || side === 2 || { 1: 'left', 3: 'right' }[side] === labelOptions.align) {
8057
+ if (side === 0 || side === 2 || { 1: 'left', 3: 'right' }[side] === axis.labelAlign) {
7962
8058
 
7963
8059
  // get the highest offset
7964
8060
  labelOffset = mathMax(
@@ -7968,10 +8064,11 @@ Axis.prototype = {
7968
8064
  }
7969
8065
 
7970
8066
  });
7971
-
7972
8067
  if (axis.staggerLines) {
7973
- labelOffset += (axis.staggerLines - 1) * 16;
8068
+ labelOffset *= axis.staggerLines;
8069
+ axis.labelOffset = labelOffset;
7974
8070
  }
8071
+
7975
8072
 
7976
8073
  } else { // doesn't have data
7977
8074
  for (n in ticks) {
@@ -8023,8 +8120,7 @@ Axis.prototype = {
8023
8120
  axisOffset[side],
8024
8121
  axis.axisTitleMargin + titleOffset + directionFactor * axis.offset
8025
8122
  );
8026
- clipOffset[invertedSide] = mathMax(clipOffset[invertedSide], options.lineWidth);
8027
-
8123
+ clipOffset[invertedSide] = mathMax(clipOffset[invertedSide], mathFloor(options.lineWidth / 2) * 2);
8028
8124
  },
8029
8125
 
8030
8126
  /**
@@ -8039,8 +8135,8 @@ Axis.prototype = {
8039
8135
  lineTop = chart.chartHeight - this.bottom - (opposite ? this.height : 0) + offset;
8040
8136
 
8041
8137
  this.lineTop = lineTop; // used by flag series
8042
- if (!opposite) {
8043
- lineWidth *= -1; // crispify the other way - #1480
8138
+ if (opposite) {
8139
+ lineWidth *= -1; // crispify the other way - #1480, #1687
8044
8140
  }
8045
8141
 
8046
8142
  return chart.renderer.crispLine([
@@ -8326,12 +8422,23 @@ Axis.prototype = {
8326
8422
  */
8327
8423
  removePlotBandOrLine: function (id) {
8328
8424
  var plotLinesAndBands = this.plotLinesAndBands,
8425
+ options = this.options,
8426
+ userOptions = this.userOptions,
8329
8427
  i = plotLinesAndBands.length;
8330
8428
  while (i--) {
8331
8429
  if (plotLinesAndBands[i].id === id) {
8332
8430
  plotLinesAndBands[i].destroy();
8333
8431
  }
8334
8432
  }
8433
+ each([options.plotLines || [], userOptions.plotLines || [], options.plotBands || [], userOptions.plotBands || []], function (arr) {
8434
+ i = arr.length;
8435
+ while (i--) {
8436
+ if (arr[i].id === id) {
8437
+ erase(arr, arr[i]);
8438
+ }
8439
+ }
8440
+ });
8441
+
8335
8442
  },
8336
8443
 
8337
8444
  /**
@@ -8369,6 +8476,19 @@ Axis.prototype = {
8369
8476
 
8370
8477
  },
8371
8478
 
8479
+ /**
8480
+ *
8481
+ */
8482
+ buildStacks: function () {
8483
+ if (this.isXAxis) {
8484
+ return;
8485
+ }
8486
+
8487
+ each(this.series, function (series) {
8488
+ series.setStackedPoints();
8489
+ });
8490
+ },
8491
+
8372
8492
  /**
8373
8493
  * Set new axis categories and optionally redraw
8374
8494
  * @param {Array} categories
@@ -8381,13 +8501,17 @@ Axis.prototype = {
8381
8501
  /**
8382
8502
  * Destroys an Axis instance.
8383
8503
  */
8384
- destroy: function () {
8504
+ destroy: function (keepEvents) {
8385
8505
  var axis = this,
8386
8506
  stacks = axis.stacks,
8387
- stackKey;
8507
+ stackKey,
8508
+ plotLinesAndBands = axis.plotLinesAndBands,
8509
+ i;
8388
8510
 
8389
8511
  // Remove the events
8390
- removeEvent(axis);
8512
+ if (!keepEvents) {
8513
+ removeEvent(axis);
8514
+ }
8391
8515
 
8392
8516
  // Destroy each stack total
8393
8517
  for (stackKey in stacks) {
@@ -8397,9 +8521,13 @@ Axis.prototype = {
8397
8521
  }
8398
8522
 
8399
8523
  // Destroy collections
8400
- each([axis.ticks, axis.minorTicks, axis.alternateBands, axis.plotLinesAndBands], function (coll) {
8524
+ each([axis.ticks, axis.minorTicks, axis.alternateBands], function (coll) {
8401
8525
  destroyObjectProperties(coll);
8402
8526
  });
8527
+ i = plotLinesAndBands.length;
8528
+ while (i--) { // #1975
8529
+ plotLinesAndBands[i].destroy();
8530
+ }
8403
8531
 
8404
8532
  // Destroy local variables
8405
8533
  each(['stackTotalGroup', 'axisLine', 'axisGroup', 'gridGroup', 'labelGroup', 'axisTitle'], function (prop) {
@@ -8705,7 +8833,6 @@ Tooltip.prototype = {
8705
8833
  options = tooltip.options,
8706
8834
  x,
8707
8835
  y,
8708
- show,
8709
8836
  anchor,
8710
8837
  textConfig = {},
8711
8838
  text,
@@ -8759,12 +8886,8 @@ Tooltip.prototype = {
8759
8886
  // register the current series
8760
8887
  currentSeries = point.series;
8761
8888
 
8762
-
8763
- // For line type series, hide tooltip if the point falls outside the plot
8764
- show = shared || !currentSeries.isCartesian || currentSeries.tooltipOutsidePlot || chart.isInsidePlot(x, y);
8765
-
8766
8889
  // update the inner HTML
8767
- if (text === false || !show) {
8890
+ if (text === false) {
8768
8891
  this.hide();
8769
8892
  } else {
8770
8893
 
@@ -8798,15 +8921,20 @@ Tooltip.prototype = {
8798
8921
  i = crosshairsOptions.length,
8799
8922
  attribs,
8800
8923
  axis,
8801
- val;
8924
+ val,
8925
+ series;
8802
8926
 
8803
8927
  while (i--) {
8804
- axis = point.series[i ? 'yAxis' : 'xAxis'];
8928
+ series = point.series;
8929
+ axis = series[i ? 'yAxis' : 'xAxis'];
8805
8930
  if (crosshairsOptions[i] && axis) {
8806
8931
  val = i ? pick(point.stackY, point.y) : point.x; // #814
8807
8932
  if (axis.isLog) { // #1671
8808
8933
  val = log2lin(val);
8809
8934
  }
8935
+ if (series.modifyValue) { // #1205
8936
+ val = series.modifyValue(val);
8937
+ }
8810
8938
 
8811
8939
  path = axis.getPlotLinePath(
8812
8940
  val,
@@ -8929,7 +9057,8 @@ Pointer.prototype = {
8929
9057
 
8930
9058
  // chartX and chartY
8931
9059
  if (ePos.pageX === UNDEFINED) { // IE < 9. #886.
8932
- chartX = e.x;
9060
+ chartX = mathMax(e.x, e.clientX - chartPosition.left); // #2005, #2129: the second case is
9061
+ // for IE10 quirks mode within framesets
8933
9062
  chartY = e.y;
8934
9063
  } else {
8935
9064
  chartX = ePos.pageX - chartPosition.left;
@@ -9097,18 +9226,20 @@ Pointer.prototype = {
9097
9226
  */
9098
9227
  scaleGroups: function (attribs, clip) {
9099
9228
 
9100
- var chart = this.chart;
9229
+ var chart = this.chart,
9230
+ seriesAttribs;
9101
9231
 
9102
9232
  // Scale each series
9103
9233
  each(chart.series, function (series) {
9234
+ seriesAttribs = attribs || series.getPlotBox(); // #1701
9104
9235
  if (series.xAxis && series.xAxis.zoomEnabled) {
9105
- series.group.attr(attribs);
9236
+ series.group.attr(seriesAttribs);
9106
9237
  if (series.markerGroup) {
9107
- series.markerGroup.attr(attribs);
9238
+ series.markerGroup.attr(seriesAttribs);
9108
9239
  series.markerGroup.clip(clip ? chart.clipRect : null);
9109
9240
  }
9110
9241
  if (series.dataLabelsGroup) {
9111
- series.dataLabelsGroup.attr(attribs);
9242
+ series.dataLabelsGroup.attr(seriesAttribs);
9112
9243
  }
9113
9244
  }
9114
9245
  });
@@ -9215,11 +9346,9 @@ Pointer.prototype = {
9215
9346
  transform = {},
9216
9347
  clip = {};
9217
9348
 
9218
- // On touch devices, only proceed to trigger click if a handler is defined
9219
- if (e.type === 'touchstart') {
9220
- if (followTouchMove || hasZoom) {
9221
- e.preventDefault();
9222
- }
9349
+ // If we're capturing touch, prevent pseudo click events from happening
9350
+ if (followTouchMove || hasZoom) {
9351
+ e.preventDefault();
9223
9352
  }
9224
9353
 
9225
9354
  // Normalize each touch
@@ -9293,7 +9422,7 @@ Pointer.prototype = {
9293
9422
  chart.mouseIsDown = e.type;
9294
9423
  chart.cancelClick = false;
9295
9424
  chart.mouseDownX = this.mouseDownX = e.chartX;
9296
- this.mouseDownY = e.chartY;
9425
+ chart.mouseDownY = this.mouseDownY = e.chartY;
9297
9426
  },
9298
9427
 
9299
9428
  /**
@@ -9375,7 +9504,7 @@ Pointer.prototype = {
9375
9504
 
9376
9505
  // panning
9377
9506
  if (clickedInside && !this.selectionMarker && chartOptions.panning) {
9378
- chart.pan(chartX);
9507
+ chart.pan(e, chartOptions.panning);
9379
9508
  }
9380
9509
  }
9381
9510
  },
@@ -9428,12 +9557,7 @@ Pointer.prototype = {
9428
9557
 
9429
9558
  // Reset scaling preview
9430
9559
  if (hasPinched) {
9431
- this.scaleGroups({
9432
- translateX: chart.plotLeft,
9433
- translateY: chart.plotTop,
9434
- scaleX: 1,
9435
- scaleY: 1
9436
- });
9560
+ this.scaleGroups();
9437
9561
  }
9438
9562
  }
9439
9563
 
@@ -9477,7 +9601,7 @@ Pointer.prototype = {
9477
9601
  e = washMouseEvent(e);
9478
9602
 
9479
9603
  // If we're outside, hide the tooltip
9480
- if (chartPosition && hoverSeries && hoverSeries.isCartesian &&
9604
+ if (chartPosition && hoverSeries && !this.inClass(e.target, 'highcharts-tracker') &&
9481
9605
  !chart.isInsidePlot(e.pageX - chartPosition.left - chart.plotLeft,
9482
9606
  e.pageY - chartPosition.top - chart.plotTop)) {
9483
9607
  this.reset();
@@ -9509,7 +9633,8 @@ Pointer.prototype = {
9509
9633
  }
9510
9634
 
9511
9635
  // Show the tooltip and run mouse over events (#977)
9512
- if (chart.isInsidePlot(e.chartX - chart.plotLeft, e.chartY - chart.plotTop) && !chart.openMenu) {
9636
+ if ((this.inClass(e.target, 'highcharts-tracker') ||
9637
+ chart.isInsidePlot(e.chartX - chart.plotLeft, e.chartY - chart.plotTop)) && !chart.openMenu) {
9513
9638
  this.runPointActions(e);
9514
9639
  }
9515
9640
  },
@@ -9612,10 +9737,14 @@ Pointer.prototype = {
9612
9737
  this.runPointActions(e);
9613
9738
 
9614
9739
  this.pinch(e);
9740
+
9741
+ } else {
9742
+ // Hide the tooltip on touching outside the plot area (#1203)
9743
+ this.reset();
9615
9744
  }
9616
9745
 
9617
9746
  } else if (e.touches.length === 2) {
9618
- this.pinch(e);
9747
+ this.pinch(e);
9619
9748
  }
9620
9749
  },
9621
9750
 
@@ -9763,7 +9892,6 @@ Legend.prototype = {
9763
9892
  },
9764
9893
  key,
9765
9894
  val;
9766
-
9767
9895
 
9768
9896
  if (legendItem) {
9769
9897
  legendItem.css({ fill: textColor, color: textColor }); // color for #1553, oldIE
@@ -9775,7 +9903,7 @@ Legend.prototype = {
9775
9903
  if (legendSymbol) {
9776
9904
 
9777
9905
  // Apply marker options
9778
- if (markerOptions) {
9906
+ if (markerOptions && legendSymbol.isMarker) { // #585
9779
9907
  markerOptions = item.convertAttribs(markerOptions);
9780
9908
  for (key in markerOptions) {
9781
9909
  val = markerOptions[key];
@@ -9826,7 +9954,7 @@ Legend.prototype = {
9826
9954
  // destroy SVG elements
9827
9955
  each(['legendItem', 'legendLine', 'legendSymbol', 'legendGroup'], function (key) {
9828
9956
  if (item[key]) {
9829
- item[key].destroy();
9957
+ item[key] = item[key].destroy();
9830
9958
  }
9831
9959
  });
9832
9960
 
@@ -9885,7 +10013,8 @@ Legend.prototype = {
9885
10013
  var options = this.options,
9886
10014
  padding = this.padding,
9887
10015
  titleOptions = options.title,
9888
- titleHeight = 0;
10016
+ titleHeight = 0,
10017
+ bBox;
9889
10018
 
9890
10019
  if (titleOptions.text) {
9891
10020
  if (!this.title) {
@@ -9894,7 +10023,9 @@ Legend.prototype = {
9894
10023
  .css(titleOptions.style)
9895
10024
  .add(this.group);
9896
10025
  }
9897
- titleHeight = this.title.getBBox().height;
10026
+ bBox = this.title.getBBox();
10027
+ titleHeight = bBox.height;
10028
+ this.offsetWidth = bBox.width; // #1717
9898
10029
  this.contentGroup.attr({ translateY: titleHeight });
9899
10030
  }
9900
10031
  this.titleHeight = titleHeight;
@@ -9915,6 +10046,7 @@ Legend.prototype = {
9915
10046
  itemStyle = legend.itemStyle,
9916
10047
  itemHiddenStyle = legend.itemHiddenStyle,
9917
10048
  padding = legend.padding,
10049
+ itemDistance = horizontal ? pick(options.itemDistance, 8) : 0,
9918
10050
  ltr = !options.rtl,
9919
10051
  itemHeight,
9920
10052
  widthOption = options.width,
@@ -10010,7 +10142,7 @@ Legend.prototype = {
10010
10142
  bBox = li.getBBox();
10011
10143
 
10012
10144
  itemWidth = item.legendItemWidth =
10013
- options.itemWidth || symbolWidth + symbolPadding + bBox.width + padding +
10145
+ options.itemWidth || symbolWidth + symbolPadding + bBox.width + itemDistance +
10014
10146
  (showCheckbox ? 20 : 0);
10015
10147
  legend.itemHeight = itemHeight = bBox.height;
10016
10148
 
@@ -10048,7 +10180,7 @@ Legend.prototype = {
10048
10180
 
10049
10181
  // the width of the widest item
10050
10182
  legend.offsetWidth = widthOption || mathMax(
10051
- horizontal ? legend.itemX - initialItemX : itemWidth,
10183
+ (horizontal ? legend.itemX - initialItemX - itemDistance : itemWidth) + padding,
10052
10184
  legend.offsetWidth
10053
10185
  );
10054
10186
  },
@@ -10385,7 +10517,7 @@ Chart.prototype = {
10385
10517
 
10386
10518
  var chartEvents = optionsChart.events;
10387
10519
 
10388
- this.runChartClick = chartEvents && !!chartEvents.click;
10520
+ //this.runChartClick = chartEvents && !!chartEvents.click;
10389
10521
  this.bounds = { h: {}, v: {} }; // Pixel data bounds for touch zoom
10390
10522
 
10391
10523
  this.callback = callback;
@@ -10499,6 +10631,7 @@ Chart.prototype = {
10499
10631
  series = chart.initSeries(options);
10500
10632
 
10501
10633
  chart.isDirtyLegend = true; // the series array is out of sync with the display
10634
+ chart.linkSeries();
10502
10635
  if (redraw) {
10503
10636
  chart.redraw(animation);
10504
10637
  }
@@ -10520,7 +10653,8 @@ Chart.prototype = {
10520
10653
 
10521
10654
  /*jslint unused: false*/
10522
10655
  axis = new Axis(this, merge(options, {
10523
- index: this[key].length
10656
+ index: this[key].length,
10657
+ isX: isX
10524
10658
  }));
10525
10659
  /*jslint unused: true*/
10526
10660
 
@@ -10576,6 +10710,7 @@ Chart.prototype = {
10576
10710
  legend = chart.legend,
10577
10711
  redrawLegend = chart.isDirtyLegend,
10578
10712
  hasStackedSeries,
10713
+ hasDirtyStacks,
10579
10714
  isDirtyBox = chart.isDirtyBox, // todo: check if it has actually changed?
10580
10715
  seriesLength = series.length,
10581
10716
  i = seriesLength,
@@ -10590,15 +10725,23 @@ Chart.prototype = {
10590
10725
  chart.cloneRenderTo();
10591
10726
  }
10592
10727
 
10728
+ // Adjust title layout (reflow multiline text)
10729
+ chart.layOutTitles();
10730
+
10593
10731
  // link stacked series
10594
10732
  while (i--) {
10595
10733
  serie = series[i];
10596
- if (serie.isDirty && serie.options.stacking) {
10734
+
10735
+ if (serie.options.stacking) {
10597
10736
  hasStackedSeries = true;
10598
- break;
10737
+
10738
+ if (serie.isDirty) {
10739
+ hasDirtyStacks = true;
10740
+ break;
10741
+ }
10599
10742
  }
10600
10743
  }
10601
- if (hasStackedSeries) { // mark others as dirty
10744
+ if (hasDirtyStacks) { // mark others as dirty
10602
10745
  i = seriesLength;
10603
10746
  while (i--) {
10604
10747
  serie = series[i];
@@ -10625,6 +10768,11 @@ Chart.prototype = {
10625
10768
  chart.isDirtyLegend = false;
10626
10769
  }
10627
10770
 
10771
+ // reset stacks
10772
+ if (hasStackedSeries) {
10773
+ chart.getStacks();
10774
+ }
10775
+
10628
10776
 
10629
10777
  if (chart.hasCartesianSeries) {
10630
10778
  if (!chart.isResizing) {
@@ -10637,9 +10785,17 @@ Chart.prototype = {
10637
10785
  axis.setScale();
10638
10786
  });
10639
10787
  }
10788
+
10640
10789
  chart.adjustTickAmounts();
10641
10790
  chart.getMargins();
10642
10791
 
10792
+ // If one axis is dirty, all axes must be redrawn (#792, #2169)
10793
+ each(axes, function (axis) {
10794
+ if (axis.isDirty) {
10795
+ isDirtyBox = true;
10796
+ }
10797
+ });
10798
+
10643
10799
  // redraw axes
10644
10800
  each(axes, function (axis) {
10645
10801
 
@@ -10650,10 +10806,9 @@ Chart.prototype = {
10650
10806
  fireEvent(axis, 'afterSetExtremes', axis.getExtremes()); // #747, #751
10651
10807
  });
10652
10808
  }
10653
-
10654
- if (axis.isDirty || isDirtyBox || hasStackedSeries) {
10809
+
10810
+ if (isDirtyBox || hasStackedSeries) {
10655
10811
  axis.redraw();
10656
- isDirtyBox = true; // #792
10657
10812
  }
10658
10813
  });
10659
10814
 
@@ -10665,7 +10820,6 @@ Chart.prototype = {
10665
10820
  }
10666
10821
 
10667
10822
 
10668
-
10669
10823
  // redraw affected series
10670
10824
  each(series, function (serie) {
10671
10825
  if (serie.isDirty && serie.visible &&
@@ -10861,6 +11015,26 @@ Chart.prototype = {
10861
11015
  });
10862
11016
  },
10863
11017
 
11018
+ /**
11019
+ * Generate stacks for each series and calculate stacks total values
11020
+ */
11021
+ getStacks: function () {
11022
+ var chart = this;
11023
+
11024
+ // reset stacks for each yAxis
11025
+ each(chart.yAxis, function (axis) {
11026
+ if (axis.stacks && axis.hasVisibleSeries) {
11027
+ axis.oldStacks = axis.stacks;
11028
+ }
11029
+ });
11030
+
11031
+ each(chart.series, function (series) {
11032
+ if (series.options.stacking && (series.visible === true || chart.options.chart.ignoreHiddenSeries === false)) {
11033
+ series.stackKey = series.type + pick(series.options.stack, '');
11034
+ }
11035
+ });
11036
+ },
11037
+
10864
11038
  /**
10865
11039
  * Display the zoom button
10866
11040
  */
@@ -10945,15 +11119,11 @@ Chart.prototype = {
10945
11119
  * on mouse move, and the distance to pan is computed from chartX compared to
10946
11120
  * the first chartX position in the dragging operation.
10947
11121
  */
10948
- pan: function (chartX) {
11122
+ pan: function (e, panning) {
11123
+
10949
11124
  var chart = this,
10950
- xAxis = chart.xAxis[0],
10951
- mouseDownX = chart.mouseDownX,
10952
- halfPointRange = xAxis.pointRange / 2,
10953
- extremes = xAxis.getExtremes(),
10954
- newMin = xAxis.translate(mouseDownX - chartX, true) + halfPointRange,
10955
- newMax = xAxis.translate(mouseDownX + chart.plotWidth - chartX, true) - halfPointRange,
10956
- hoverPoints = chart.hoverPoints;
11125
+ hoverPoints = chart.hoverPoints,
11126
+ doRedraw;
10957
11127
 
10958
11128
  // remove active points for shared tooltip
10959
11129
  if (hoverPoints) {
@@ -10962,11 +11132,26 @@ Chart.prototype = {
10962
11132
  });
10963
11133
  }
10964
11134
 
10965
- if (xAxis.series.length && newMin > mathMin(extremes.dataMin, extremes.min) && newMax < mathMax(extremes.dataMax, extremes.max)) {
10966
- xAxis.setExtremes(newMin, newMax, true, false, { trigger: 'pan' });
10967
- }
11135
+ each(panning === 'xy' ? [1, 0] : [1], function (isX) { // docs: panning xy
11136
+ var mousePos = e[isX ? 'chartX' : 'chartY'],
11137
+ axis = chart[isX ? 'xAxis' : 'yAxis'][0],
11138
+ startPos = chart[isX ? 'mouseDownX' : 'mouseDownY'],
11139
+ halfPointRange = (axis.pointRange || 0) / 2,
11140
+ extremes = axis.getExtremes(),
11141
+ newMin = axis.toValue(startPos - mousePos, true) + halfPointRange,
11142
+ newMax = axis.toValue(startPos + chart[isX ? 'plotWidth' : 'plotHeight'] - mousePos, true) - halfPointRange;
10968
11143
 
10969
- chart.mouseDownX = chartX; // set new reference for next run
11144
+ if (axis.series.length && newMin > mathMin(extremes.dataMin, extremes.min) && newMax < mathMax(extremes.dataMax, extremes.max)) {
11145
+ axis.setExtremes(newMin, newMax, false, false, { trigger: 'pan' });
11146
+ doRedraw = true;
11147
+ }
11148
+
11149
+ chart[isX ? 'mouseDownX' : 'mouseDownY'] = mousePos; // set new reference for next run
11150
+ });
11151
+
11152
+ if (doRedraw) {
11153
+ chart.redraw(false);
11154
+ }
10970
11155
  css(chart.container, { cursor: 'move' });
10971
11156
  },
10972
11157
 
@@ -11013,11 +11198,49 @@ Chart.prototype = {
11013
11198
  zIndex: chartTitleOptions.zIndex || 4
11014
11199
  })
11015
11200
  .css(chartTitleOptions.style)
11016
- .add()
11017
- .align(chartTitleOptions, false, 'spacingBox');
11018
- }
11201
+ .add();
11202
+ }
11019
11203
  });
11204
+ chart.layOutTitles();
11205
+ },
11206
+
11207
+ /**
11208
+ * Lay out the chart titles and cache the full offset height for use in getMargins
11209
+ */
11210
+ layOutTitles: function () {
11211
+ var titleOffset = 0,
11212
+ title = this.title,
11213
+ subtitle = this.subtitle,
11214
+ options = this.options,
11215
+ titleOptions = options.title,
11216
+ subtitleOptions = options.subtitle,
11217
+ autoWidth = this.spacingBox.width - 44; // 44 makes room for default context button
11218
+
11219
+ if (title) {
11220
+ title
11221
+ .css({ width: (titleOptions.width || autoWidth) + PX })
11222
+ .align(extend({ y: 15 }, titleOptions), false, 'spacingBox');
11223
+
11224
+ if (!titleOptions.floating && !titleOptions.verticalAlign) {
11225
+ titleOffset = title.getBBox().height;
11226
+
11227
+ // Adjust for browser consistency + backwards compat after #776 fix
11228
+ if (titleOffset >= 18 && titleOffset <= 25) {
11229
+ titleOffset = 15;
11230
+ }
11231
+ }
11232
+ }
11233
+ if (subtitle) {
11234
+ subtitle
11235
+ .css({ width: (subtitleOptions.width || autoWidth) + PX })
11236
+ .align(extend({ y: titleOffset + titleOptions.margin }, subtitleOptions), false, 'spacingBox');
11237
+
11238
+ if (!subtitleOptions.floating && !subtitleOptions.verticalAlign) {
11239
+ titleOffset = mathCeil(titleOffset + subtitle.getBBox().height);
11240
+ }
11241
+ }
11020
11242
 
11243
+ this.titleOffset = titleOffset; // used in getMargins
11021
11244
  },
11022
11245
 
11023
11246
  /**
@@ -11056,7 +11279,7 @@ Chart.prototype = {
11056
11279
 
11057
11280
  // Set up the clone
11058
11281
  } else {
11059
- if (container) {
11282
+ if (container && container.parentNode === this.renderTo) {
11060
11283
  this.renderTo.removeChild(container); // do not clone this
11061
11284
  }
11062
11285
  this.renderToClone = clone = this.renderTo.cloneNode(0);
@@ -11176,30 +11399,23 @@ Chart.prototype = {
11176
11399
  optionsMarginLeft = chart.optionsMarginLeft,
11177
11400
  optionsMarginRight = chart.optionsMarginRight,
11178
11401
  optionsMarginBottom = chart.optionsMarginBottom,
11179
- chartTitleOptions = chart.options.title,
11180
- chartSubtitleOptions = chart.options.subtitle,
11181
11402
  legendOptions = chart.options.legend,
11182
11403
  legendMargin = pick(legendOptions.margin, 10),
11183
11404
  legendX = legendOptions.x,
11184
11405
  legendY = legendOptions.y,
11185
11406
  align = legendOptions.align,
11186
11407
  verticalAlign = legendOptions.verticalAlign,
11187
- titleOffset;
11408
+ titleOffset = chart.titleOffset;
11188
11409
 
11189
11410
  chart.resetMargins();
11190
11411
  axisOffset = chart.axisOffset;
11191
11412
 
11192
- // adjust for title and subtitle
11193
- if ((chart.title || chart.subtitle) && !defined(chart.optionsMarginTop)) {
11194
- titleOffset = mathMax(
11195
- (chart.title && !chartTitleOptions.floating && !chartTitleOptions.verticalAlign && chartTitleOptions.y) || 0,
11196
- (chart.subtitle && !chartSubtitleOptions.floating && !chartSubtitleOptions.verticalAlign && chartSubtitleOptions.y) || 0
11197
- );
11198
- if (titleOffset) {
11199
- chart.plotTop = mathMax(chart.plotTop, titleOffset + pick(chartTitleOptions.margin, 15) + spacingTop);
11200
- }
11413
+ // Adjust for title and subtitle
11414
+ if (titleOffset && !defined(optionsMarginTop)) {
11415
+ chart.plotTop = mathMax(chart.plotTop, titleOffset + chart.options.title.margin + spacingTop);
11201
11416
  }
11202
- // adjust for legend
11417
+
11418
+ // Adjust for legend
11203
11419
  if (legend.display && !legendOptions.floating) {
11204
11420
  if (align === 'right') { // horizontal alignment handled first
11205
11421
  if (!defined(optionsMarginRight)) {
@@ -11410,7 +11626,7 @@ Chart.prototype = {
11410
11626
  chart.plotSizeX = inverted ? plotHeight : plotWidth;
11411
11627
  chart.plotSizeY = inverted ? plotWidth : plotHeight;
11412
11628
 
11413
- chart.plotBorderWidth = plotBorderWidth = optionsChart.plotBorderWidth || 0;
11629
+ chart.plotBorderWidth = optionsChart.plotBorderWidth || 0;
11414
11630
 
11415
11631
  // Set boxes used for alignment
11416
11632
  chart.spacingBox = renderer.spacingBox = {
@@ -11425,6 +11641,8 @@ Chart.prototype = {
11425
11641
  width: plotWidth,
11426
11642
  height: plotHeight
11427
11643
  };
11644
+
11645
+ plotBorderWidth = 2 * mathFloor(chart.plotBorderWidth / 2);
11428
11646
  clipX = mathCeil(mathMax(plotBorderWidth, clipOffset[3]) / 2);
11429
11647
  clipY = mathCeil(mathMax(plotBorderWidth, clipOffset[0]) / 2);
11430
11648
  chart.clipBox = {
@@ -11551,7 +11769,7 @@ Chart.prototype = {
11551
11769
  // Plot area border
11552
11770
  if (plotBorderWidth) {
11553
11771
  if (!plotBorder) {
11554
- chart.plotBorder = renderer.rect(plotLeft, plotTop, plotWidth, plotHeight, 0, plotBorderWidth)
11772
+ chart.plotBorder = renderer.rect(plotLeft, plotTop, plotWidth, plotHeight, 0, -plotBorderWidth)
11555
11773
  .attr({
11556
11774
  stroke: optionsChart.plotBorderColor,
11557
11775
  'stroke-width': plotBorderWidth,
@@ -11610,6 +11828,36 @@ Chart.prototype = {
11610
11828
 
11611
11829
  },
11612
11830
 
11831
+ /**
11832
+ * Link two or more series together. This is done initially from Chart.render,
11833
+ * and after Chart.addSeries and Series.remove.
11834
+ */
11835
+ linkSeries: function () {
11836
+ var chart = this,
11837
+ chartSeries = chart.series;
11838
+
11839
+ // Reset links
11840
+ each(chartSeries, function (series) {
11841
+ series.linkedSeries.length = 0;
11842
+ });
11843
+
11844
+ // Apply new links
11845
+ each(chartSeries, function (series) {
11846
+ var linkedTo = series.options.linkedTo;
11847
+ if (isString(linkedTo)) {
11848
+ if (linkedTo === ':previous') {
11849
+ linkedTo = chart.series[series.index - 1];
11850
+ } else {
11851
+ linkedTo = chart.get(linkedTo);
11852
+ }
11853
+ if (linkedTo) {
11854
+ linkedTo.linkedSeries.push(series);
11855
+ series.linkedParent = linkedTo;
11856
+ }
11857
+ }
11858
+ });
11859
+ },
11860
+
11613
11861
  /**
11614
11862
  * Render all graphics for the chart
11615
11863
  */
@@ -11630,11 +11878,14 @@ Chart.prototype = {
11630
11878
  // Legend
11631
11879
  chart.legend = new Legend(chart, options.legend);
11632
11880
 
11881
+ chart.getStacks(); // render stacks
11882
+
11633
11883
  // Get margins by pre-rendering axes
11634
11884
  // set axes scales
11635
11885
  each(axes, function (axis) {
11636
11886
  axis.setScale();
11637
11887
  });
11888
+
11638
11889
  chart.getMargins();
11639
11890
 
11640
11891
  chart.maxTicks = null; // reset for second pass
@@ -11843,6 +12094,8 @@ Chart.prototype = {
11843
12094
  chart.initSeries(serieOptions);
11844
12095
  });
11845
12096
 
12097
+ chart.linkSeries();
12098
+
11846
12099
  // Run an event after axes and series are initialized, but before render. At this stage,
11847
12100
  // the series data is indexed and cached in the xData and yData arrays, so we can access
11848
12101
  // those before rendering. Used in Highstock.
@@ -12177,7 +12430,8 @@ Point.prototype = {
12177
12430
  graphic = point.graphic,
12178
12431
  i,
12179
12432
  data = series.data,
12180
- chart = series.chart;
12433
+ chart = series.chart,
12434
+ seriesOptions = series.options;
12181
12435
 
12182
12436
  redraw = pick(redraw, true);
12183
12437
 
@@ -12190,7 +12444,11 @@ Point.prototype = {
12190
12444
  if (isObject(options)) {
12191
12445
  series.getAttribs();
12192
12446
  if (graphic) {
12193
- graphic.attr(point.pointAttr[series.state]);
12447
+ if (options.marker && options.marker.symbol) {
12448
+ point.graphic = graphic.destroy();
12449
+ } else {
12450
+ graphic.attr(point.pointAttr[series.state]);
12451
+ }
12194
12452
  }
12195
12453
  }
12196
12454
 
@@ -12199,11 +12457,13 @@ Point.prototype = {
12199
12457
  series.xData[i] = point.x;
12200
12458
  series.yData[i] = series.toYData ? series.toYData(point) : point.y;
12201
12459
  series.zData[i] = point.z;
12202
- series.options.data[i] = point.options;
12460
+ seriesOptions.data[i] = point.options;
12203
12461
 
12204
12462
  // redraw
12205
- series.isDirty = true;
12206
- series.isDirtyData = true;
12463
+ series.isDirty = series.isDirtyData = chart.isDirtyBox = true;
12464
+ if (seriesOptions.legendType === 'point') { // #1831, #1885
12465
+ chart.legend.destroyItem(point);
12466
+ }
12207
12467
  if (redraw) {
12208
12468
  chart.redraw(animation);
12209
12469
  }
@@ -12431,11 +12691,11 @@ Series.prototype = {
12431
12691
  var series = this,
12432
12692
  eventType,
12433
12693
  events,
12434
- linkedTo,
12435
12694
  chartSeries = chart.series;
12436
12695
 
12437
12696
  series.chart = chart;
12438
12697
  series.options = options = series.setOptions(options); // merge with plotOptions
12698
+ series.linkedSeries = [];
12439
12699
 
12440
12700
  // bind the axes
12441
12701
  series.bindAxes();
@@ -12491,20 +12751,6 @@ Series.prototype = {
12491
12751
  series.name = series.name || 'Series ' + (i + 1);
12492
12752
  });
12493
12753
 
12494
- // Linked series
12495
- linkedTo = options.linkedTo;
12496
- series.linkedSeries = [];
12497
- if (isString(linkedTo)) {
12498
- if (linkedTo === ':previous') {
12499
- linkedTo = chartSeries[series.index - 1];
12500
- } else {
12501
- linkedTo = chart.get(linkedTo);
12502
- }
12503
- if (linkedTo) {
12504
- linkedTo.linkedSeries.push(series);
12505
- series.linkedParent = linkedTo;
12506
- }
12507
- }
12508
12754
  },
12509
12755
 
12510
12756
  /**
@@ -12612,6 +12858,7 @@ Series.prototype = {
12612
12858
  // register it
12613
12859
  series.segments = segments;
12614
12860
  },
12861
+
12615
12862
  /**
12616
12863
  * Set the series options by merging from the options tree
12617
12864
  * @param {Object} itemOptions
@@ -12714,7 +12961,7 @@ Series.prototype = {
12714
12961
  symbolWidth = legendOptions.symbolWidth,
12715
12962
  renderer = this.chart.renderer,
12716
12963
  legendItemGroup = this.legendGroup,
12717
- baseline = legend.baseline,
12964
+ verticalCenter = legend.baseline - mathRound(renderer.fontMetrics(legendOptions.itemStyle.fontSize).b * 0.3),
12718
12965
  attr;
12719
12966
 
12720
12967
  // Draw the line
@@ -12728,10 +12975,10 @@ Series.prototype = {
12728
12975
  this.legendLine = renderer.path([
12729
12976
  M,
12730
12977
  0,
12731
- baseline - 4,
12978
+ verticalCenter,
12732
12979
  L,
12733
12980
  symbolWidth,
12734
- baseline - 4
12981
+ verticalCenter
12735
12982
  ])
12736
12983
  .attr(attr)
12737
12984
  .add(legendItemGroup);
@@ -12743,11 +12990,12 @@ Series.prototype = {
12743
12990
  this.legendSymbol = legendSymbol = renderer.symbol(
12744
12991
  this.symbol,
12745
12992
  (symbolWidth / 2) - radius,
12746
- baseline - 4 - radius,
12993
+ verticalCenter - radius,
12747
12994
  2 * radius,
12748
12995
  2 * radius
12749
12996
  )
12750
12997
  .add(legendItemGroup);
12998
+ legendSymbol.isMarker = true;
12751
12999
  }
12752
13000
  },
12753
13001
 
@@ -12778,13 +13026,14 @@ Series.prototype = {
12778
13026
  setAnimation(animation, chart);
12779
13027
 
12780
13028
  // Make graph animate sideways
12781
- if (graph && shift) {
12782
- graph.shift = currentShift + 1;
13029
+ if (shift) {
13030
+ each([graph, area, series.graphNeg, series.areaNeg], function (shape) {
13031
+ if (shape) {
13032
+ shape.shift = currentShift + 1;
13033
+ }
13034
+ });
12783
13035
  }
12784
13036
  if (area) {
12785
- if (shift) { // #780
12786
- area.shift = currentShift + 1;
12787
- }
12788
13037
  area.isArea = true; // needed in animation, both with and without shift
12789
13038
  }
12790
13039
 
@@ -12821,12 +13070,12 @@ Series.prototype = {
12821
13070
  dataOptions.shift();
12822
13071
  }
12823
13072
  }
12824
- series.getAttribs();
12825
13073
 
12826
13074
  // redraw
12827
13075
  series.isDirty = true;
12828
13076
  series.isDirtyData = true;
12829
13077
  if (redraw) {
13078
+ series.getAttribs(); // #1937
12830
13079
  chart.redraw();
12831
13080
  }
12832
13081
  },
@@ -12857,7 +13106,7 @@ Series.prototype = {
12857
13106
  yData = [],
12858
13107
  zData = [],
12859
13108
  dataLength = data ? data.length : [],
12860
- turboThreshold = options.turboThreshold || 1000,
13109
+ turboThreshold = pick(options.turboThreshold, 1000),
12861
13110
  pt,
12862
13111
  pointArrayMap = series.pointArrayMap,
12863
13112
  valueCount = pointArrayMap && pointArrayMap.length,
@@ -12867,7 +13116,7 @@ Series.prototype = {
12867
13116
  // first value is tested, and we assume that all the rest are defined the same
12868
13117
  // way. Although the 'for' loops are similar, they are repeated inside each
12869
13118
  // if-else conditional for max performance.
12870
- if (dataLength > turboThreshold) {
13119
+ if (turboThreshold && dataLength > turboThreshold) {
12871
13120
 
12872
13121
  // find the first non-null point
12873
13122
  i = 0;
@@ -12901,9 +13150,9 @@ Series.prototype = {
12901
13150
  yData[i] = pt[1];
12902
13151
  }
12903
13152
  }
12904
- } /* else {
13153
+ } else {
12905
13154
  error(12); // Highcharts expects configs to be numbers or arrays in turbo mode
12906
- }*/
13155
+ }
12907
13156
  } else {
12908
13157
  for (i = 0; i < dataLength; i++) {
12909
13158
  if (data[i] !== UNDEFINED) { // stray commas in oldIE
@@ -12913,18 +13162,12 @@ Series.prototype = {
12913
13162
  yData[i] = hasToYData ? series.toYData(pt) : pt.y;
12914
13163
  zData[i] = pt.z;
12915
13164
  if (names && pt.name) {
12916
- names[i] = pt.name;
13165
+ names[pt.x] = pt.name; // #2046
12917
13166
  }
12918
13167
  }
12919
13168
  }
12920
13169
  }
12921
13170
 
12922
- // Unsorted data is not supported by the line tooltip as well as data grouping and
12923
- // navigation in Stock charts (#725)
12924
- if (series.requireSorting && xData.length > 1 && xData[1] < xData[0]) {
12925
- error(15);
12926
- }
12927
-
12928
13171
  // Forgetting to cast strings to numbers is a common caveat when handling CSV or JSON
12929
13172
  if (isString(yData[0])) {
12930
13173
  error(14, true);
@@ -12984,6 +13227,8 @@ Series.prototype = {
12984
13227
 
12985
13228
  // redraw
12986
13229
  chart.isDirtyLegend = chart.isDirtyBox = true;
13230
+ chart.linkSeries();
13231
+
12987
13232
  if (redraw) {
12988
13233
  chart.redraw(animation);
12989
13234
  }
@@ -13002,8 +13247,8 @@ Series.prototype = {
13002
13247
  processedXData = series.xData, // copied during slice operation below
13003
13248
  processedYData = series.yData,
13004
13249
  dataLength = processedXData.length,
13250
+ croppedData,
13005
13251
  cropStart = 0,
13006
- cropEnd = dataLength,
13007
13252
  cropped,
13008
13253
  distance,
13009
13254
  closestPointRange,
@@ -13018,12 +13263,12 @@ Series.prototype = {
13018
13263
  if (isCartesian && !series.isDirty && !xAxis.isDirty && !series.yAxis.isDirty && !force) {
13019
13264
  return false;
13020
13265
  }
13266
+
13021
13267
 
13022
13268
  // optionally filter out points outside the plot area
13023
13269
  if (isCartesian && series.sorted && (!cropThreshold || dataLength > cropThreshold || series.forceCrop)) {
13024
- var extremes = xAxis.getExtremes(),
13025
- min = extremes.min,
13026
- max = extremes.max;
13270
+ var min = xAxis.min,
13271
+ max = xAxis.max;
13027
13272
 
13028
13273
  // it's outside current extremes
13029
13274
  if (processedXData[dataLength - 1] < min || processedXData[0] > max) {
@@ -13032,43 +13277,34 @@ Series.prototype = {
13032
13277
 
13033
13278
  // only crop if it's actually spilling out
13034
13279
  } else if (processedXData[0] < min || processedXData[dataLength - 1] > max) {
13035
-
13036
- // iterate up to find slice start
13037
- for (i = 0; i < dataLength; i++) {
13038
- if (processedXData[i] >= min) {
13039
- cropStart = mathMax(0, i - 1);
13040
- break;
13041
- }
13042
- }
13043
- // proceed to find slice end
13044
- for (; i < dataLength; i++) {
13045
- if (processedXData[i] > max) {
13046
- cropEnd = i + 1;
13047
- break;
13048
- }
13049
-
13050
- }
13051
- processedXData = processedXData.slice(cropStart, cropEnd);
13052
- processedYData = processedYData.slice(cropStart, cropEnd);
13280
+ croppedData = this.cropData(series.xData, series.yData, min, max);
13281
+ processedXData = croppedData.xData;
13282
+ processedYData = croppedData.yData;
13283
+ cropStart = croppedData.start;
13053
13284
  cropped = true;
13054
13285
  }
13055
13286
  }
13056
13287
 
13057
13288
 
13058
13289
  // Find the closest distance between processed points
13059
- for (i = processedXData.length - 1; i > 0; i--) {
13290
+ for (i = processedXData.length - 1; i >= 0; i--) {
13060
13291
  distance = processedXData[i] - processedXData[i - 1];
13061
13292
  if (distance > 0 && (closestPointRange === UNDEFINED || distance < closestPointRange)) {
13062
13293
  closestPointRange = distance;
13294
+
13295
+ // Unsorted data is not supported by the line tooltip, as well as data grouping and
13296
+ // navigation in Stock charts (#725) and width calculation of columns (#1900)
13297
+ } else if (distance < 0 && series.requireSorting) {
13298
+ error(15);
13063
13299
  }
13064
13300
  }
13065
-
13301
+
13066
13302
  // Record the properties
13067
13303
  series.cropped = cropped; // undefined or true
13068
13304
  series.cropStart = cropStart;
13069
13305
  series.processedXData = processedXData;
13070
13306
  series.processedYData = processedYData;
13071
-
13307
+
13072
13308
  if (options.pointRange === null) { // null means auto, as for columns, candlesticks and OHLC
13073
13309
  series.pointRange = closestPointRange || 1;
13074
13310
  }
@@ -13076,6 +13312,41 @@ Series.prototype = {
13076
13312
 
13077
13313
  },
13078
13314
 
13315
+ /**
13316
+ * Iterate over xData and crop values between min and max. Returns object containing crop start/end
13317
+ * cropped xData with corresponding part of yData, dataMin and dataMax within the cropped range
13318
+ */
13319
+ cropData: function (xData, yData, min, max) {
13320
+ var dataLength = xData.length,
13321
+ cropStart = 0,
13322
+ cropEnd = dataLength,
13323
+ i;
13324
+
13325
+ // iterate up to find slice start
13326
+ for (i = 0; i < dataLength; i++) {
13327
+ if (xData[i] >= min) {
13328
+ cropStart = mathMax(0, i - 1);
13329
+ break;
13330
+ }
13331
+ }
13332
+
13333
+ // proceed to find slice end
13334
+ for (; i < dataLength; i++) {
13335
+ if (xData[i] > max) {
13336
+ cropEnd = i + 1;
13337
+ break;
13338
+ }
13339
+ }
13340
+
13341
+ return {
13342
+ xData: xData.slice(cropStart, cropEnd),
13343
+ yData: yData.slice(cropStart, cropEnd),
13344
+ start: cropStart,
13345
+ end: cropEnd
13346
+ };
13347
+ },
13348
+
13349
+
13079
13350
  /**
13080
13351
  * Generate the data point after the data has been processed by cropping away
13081
13352
  * unused points and optionally grouped in Highcharts Stock.
@@ -13136,6 +13407,165 @@ Series.prototype = {
13136
13407
  series.points = points;
13137
13408
  },
13138
13409
 
13410
+ /**
13411
+ * Adds series' points value to corresponding stack
13412
+ */
13413
+ setStackedPoints: function () {
13414
+ if (!this.options.stacking || (this.visible !== true && this.chart.options.chart.ignoreHiddenSeries !== false)) {
13415
+ return;
13416
+ }
13417
+
13418
+ var series = this,
13419
+ xData = series.processedXData,
13420
+ yData = series.processedYData,
13421
+ yDataLength = yData.length,
13422
+ seriesOptions = series.options,
13423
+ threshold = seriesOptions.threshold,
13424
+ stackOption = seriesOptions.stack,
13425
+ stacking = seriesOptions.stacking,
13426
+ stackKey = series.stackKey,
13427
+ negKey = '-' + stackKey,
13428
+ yAxis = series.yAxis,
13429
+ stacks = yAxis.stacks,
13430
+ oldStacks = yAxis.oldStacks,
13431
+ stackExtremes = yAxis.stackExtremes,
13432
+ isNegative,
13433
+ total,
13434
+ stack,
13435
+ key,
13436
+ i,
13437
+ x,
13438
+ y;
13439
+
13440
+ // loop over the non-null y values and read them into a local array
13441
+ for (i = 0; i < yDataLength; i++) {
13442
+ x = xData[i];
13443
+ y = yData[i];
13444
+
13445
+ // Read stacked values into a stack based on the x value,
13446
+ // the sign of y and the stack key. Stacking is also handled for null values (#739)
13447
+ isNegative = series.negStacks && y < threshold;
13448
+ key = isNegative ? negKey : stackKey;
13449
+
13450
+ // Set default stackExtremes value for this stack
13451
+ if (typeof y === 'number' && !stackExtremes[stackKey]) {
13452
+ stackExtremes[stackKey] = {
13453
+ dataMin: y,
13454
+ dataMax: y
13455
+ };
13456
+ }
13457
+
13458
+ // Create empty object for this stack if it doesn't exist yet
13459
+ if (!stacks[key]) {
13460
+ stacks[key] = {};
13461
+ }
13462
+
13463
+ // Initialize StackItem for this x
13464
+ if (!stacks[key][x]) {
13465
+ if (oldStacks[key] && oldStacks[key][x]) {
13466
+ stacks[key][x] = oldStacks[key][x];
13467
+ stacks[key][x].total = null;
13468
+ } else {
13469
+ stacks[key][x] = new StackItem(yAxis, yAxis.options.stackLabels, isNegative, x, stackOption, stacking);
13470
+ }
13471
+ }
13472
+
13473
+ // If the StackItem doesn't exist, create it first
13474
+ stack = stacks[key][x];
13475
+ total = stack.total;
13476
+
13477
+
13478
+ // add value to the stack total
13479
+ stack.addValue(y || 0);
13480
+ stack.cacheExtremes(series, [total, total + (y || 0)]);
13481
+
13482
+ if (typeof y === 'number') {
13483
+ stackExtremes[stackKey].dataMin = mathMin(stackExtremes[stackKey].dataMin, stack.total, y);
13484
+ stackExtremes[stackKey].dataMax = mathMax(stackExtremes[stackKey].dataMax, stack.total, y);
13485
+ }
13486
+
13487
+ }
13488
+
13489
+ // reset old stacks
13490
+ yAxis.oldStacks = {};
13491
+ },
13492
+
13493
+ /**
13494
+ * Calculate Y extremes for visible data
13495
+ */
13496
+ getExtremes: function () {
13497
+ var xAxis = this.xAxis,
13498
+ yAxis = this.yAxis,
13499
+ stackKey = this.stackKey,
13500
+ stackExtremes,
13501
+ stackMin,
13502
+ stackMax,
13503
+ options = this.options,
13504
+ threshold = yAxis.isLog ? null : options.threshold,
13505
+ xData = this.processedXData,
13506
+ yData = this.processedYData,
13507
+ yDataLength = yData.length,
13508
+ activeYData = [],
13509
+ activeCounter = 0,
13510
+ xExtremes = xAxis.getExtremes(), // #2117, need to compensate for log X axis
13511
+ xMin = xExtremes.min,
13512
+ xMax = xExtremes.max,
13513
+ validValue,
13514
+ withinRange,
13515
+ dataMin,
13516
+ dataMax,
13517
+ x,
13518
+ y,
13519
+ i,
13520
+ j;
13521
+
13522
+ // For stacked series, get the value from the stack
13523
+ if (options.stacking) {
13524
+ stackExtremes = yAxis.stackExtremes[stackKey];
13525
+ stackMin = stackExtremes.dataMin;
13526
+ stackMax = stackExtremes.dataMax;
13527
+
13528
+ dataMin = mathMin(stackMin, pick(threshold, stackMin));
13529
+ dataMax = mathMax(stackMax, pick(threshold, stackMax));
13530
+ }
13531
+
13532
+ // If not stacking or threshold is null, iterate over values that are within the visible range
13533
+ if (!defined(dataMin) || !defined(dataMax)) {
13534
+
13535
+ for (i = 0; i < yDataLength; i++) {
13536
+
13537
+ x = xData[i];
13538
+ y = yData[i];
13539
+
13540
+ // For points within the visible range, including the first point outside the
13541
+ // visible range, consider y extremes
13542
+ validValue = y !== null && y !== UNDEFINED && (!yAxis.isLog || (y.length || y > 0));
13543
+ withinRange = this.getExtremesFromAll || this.cropped || ((xData[i + 1] || x) >= xMin &&
13544
+ (xData[i - 1] || x) <= xMax);
13545
+
13546
+ if (validValue && withinRange) {
13547
+
13548
+ j = y.length;
13549
+ if (j) { // array, like ohlc or range data
13550
+ while (j--) {
13551
+ if (y[j] !== null) {
13552
+ activeYData[activeCounter++] = y[j];
13553
+ }
13554
+ }
13555
+ } else {
13556
+ activeYData[activeCounter++] = y;
13557
+ }
13558
+ }
13559
+ }
13560
+ dataMin = pick(dataMin, arrayMin(activeYData));
13561
+ dataMax = pick(dataMax, arrayMax(activeYData));
13562
+ }
13563
+
13564
+ // Set
13565
+ this.dataMin = dataMin;
13566
+ this.dataMax = dataMax;
13567
+ },
13568
+
13139
13569
  /**
13140
13570
  * Translate data points from raw data values to chart specific positioning data
13141
13571
  * needed later in drawPoints, drawGraph and drawTracker.
@@ -13154,27 +13584,11 @@ Series.prototype = {
13154
13584
  points = series.points,
13155
13585
  dataLength = points.length,
13156
13586
  hasModifyValue = !!series.modifyValue,
13157
- isBottomSeries,
13158
- allStackSeries,
13159
13587
  i,
13160
- placeBetween = options.pointPlacement === 'between',
13588
+ pointPlacement = options.pointPlacement,
13589
+ dynamicallyPlaced = pointPlacement === 'between' || isNumber(pointPlacement),
13161
13590
  threshold = options.threshold;
13162
- //nextSeriesDown;
13163
-
13164
- // Is it the last visible series? (#809, #1722).
13165
- // TODO: After merging in the 'stacking' branch, this logic should probably be moved to Chart.getStacks
13166
- allStackSeries = yAxis.series.sort(function (a, b) {
13167
- return a.index - b.index;
13168
- });
13169
- i = allStackSeries.length;
13170
- while (i--) {
13171
- if (allStackSeries[i].visible) {
13172
- if (allStackSeries[i] === series) { // #809
13173
- isBottomSeries = true;
13174
- }
13175
- break;
13176
- }
13177
- }
13591
+
13178
13592
 
13179
13593
  // Translate each point
13180
13594
  for (i = 0; i < dataLength; i++) {
@@ -13182,7 +13596,7 @@ Series.prototype = {
13182
13596
  xValue = point.x,
13183
13597
  yValue = point.y,
13184
13598
  yBottom = point.low,
13185
- stack = yAxis.stacks[(yValue < threshold ? '-' : '') + series.stackKey],
13599
+ stack = yAxis.stacks[(series.negStacks && yValue < threshold ? '-' : '') + series.stackKey],
13186
13600
  pointStack,
13187
13601
  pointStackTotal;
13188
13602
 
@@ -13192,16 +13606,18 @@ Series.prototype = {
13192
13606
  }
13193
13607
 
13194
13608
  // Get the plotX translation
13195
- point.plotX = xAxis.translate(xValue, 0, 0, 0, 1, placeBetween); // Math.round fixes #591
13609
+ point.plotX = xAxis.translate(xValue, 0, 0, 0, 1, pointPlacement); // Math.round fixes #591
13196
13610
 
13197
13611
  // Calculate the bottom y value for stacked series
13198
13612
  if (stacking && series.visible && stack && stack[xValue]) {
13613
+
13614
+
13199
13615
  pointStack = stack[xValue];
13200
13616
  pointStackTotal = pointStack.total;
13201
13617
  pointStack.cum = yBottom = pointStack.cum - yValue; // start from top
13202
13618
  yValue = yBottom + yValue;
13203
13619
 
13204
- if (isBottomSeries) {
13620
+ if (pointStack.cum === 0) {
13205
13621
  yBottom = pick(threshold, yAxis.min);
13206
13622
  }
13207
13623
 
@@ -13217,6 +13633,10 @@ Series.prototype = {
13217
13633
  point.percentage = pointStackTotal ? point.y * 100 / pointStackTotal : 0;
13218
13634
  point.total = point.stackTotal = pointStackTotal;
13219
13635
  point.stackY = yValue;
13636
+
13637
+ // Place the stack label
13638
+ pointStack.setOffset(series.pointXOffset || 0, series.barW || 0);
13639
+
13220
13640
  }
13221
13641
 
13222
13642
  // Set translated yBottom or remove it
@@ -13231,11 +13651,12 @@ Series.prototype = {
13231
13651
 
13232
13652
  // Set the the plotY value, reset it for redraws
13233
13653
  point.plotY = (typeof yValue === 'number' && yValue !== Infinity) ?
13234
- mathRound(yAxis.translate(yValue, 0, 1, 0, 1) * 10) / 10 : // Math.round fixes #591
13654
+ //mathRound(yAxis.translate(yValue, 0, 1, 0, 1) * 10) / 10 : // Math.round fixes #591
13655
+ yAxis.translate(yValue, 0, 1, 0, 1) :
13235
13656
  UNDEFINED;
13236
13657
 
13237
13658
  // Set client related positions for mouse tracking
13238
- point.clientX = placeBetween ? xAxis.translate(xValue, 0, 0, 0, 1) : point.plotX; // #1514
13659
+ point.clientX = dynamicallyPlaced ? xAxis.translate(xValue, 0, 0, 0, 1) : point.plotX; // #1514
13239
13660
 
13240
13661
  point.negative = point.y < (threshold || 0);
13241
13662
 
@@ -13261,6 +13682,7 @@ Series.prototype = {
13261
13682
  xAxis = series.xAxis,
13262
13683
  axisLength = xAxis ? (xAxis.tooltipLen || xAxis.len) : series.chart.plotSizeX, // tooltipLen and tooltipPosName used in polar
13263
13684
  point,
13685
+ nextPoint,
13264
13686
  i,
13265
13687
  tooltipPoints = []; // a lookup array for each pixel in the x dimension
13266
13688
 
@@ -13284,15 +13706,24 @@ Series.prototype = {
13284
13706
  points = points.reverse();
13285
13707
  }
13286
13708
 
13709
+ // Polar needs additional shaping
13710
+ if (series.orderTooltipPoints) {
13711
+ series.orderTooltipPoints(points);
13712
+ }
13713
+
13287
13714
  // Assign each pixel position to the nearest point
13288
13715
  pointsLength = points.length;
13289
13716
  for (i = 0; i < pointsLength; i++) {
13290
13717
  point = points[i];
13718
+ nextPoint = points[i + 1];
13719
+
13291
13720
  // Set this range's low to the last range's high plus one
13292
13721
  low = points[i - 1] ? high + 1 : 0;
13293
13722
  // Now find the new high
13294
13723
  high = points[i + 1] ?
13295
- mathMax(0, mathFloor((point.clientX + (points[i + 1] ? points[i + 1].clientX : axisLength)) / 2)) :
13724
+ mathMin(mathMax(0, mathFloor( // #2070
13725
+ (point.clientX + (nextPoint ? (nextPoint.wrappedClientX || nextPoint.clientX) : axisLength)) / 2
13726
+ )), axisLength) :
13296
13727
  axisLength;
13297
13728
 
13298
13729
  while (low >= 0 && low <= high) {
@@ -13512,7 +13943,7 @@ Series.prototype = {
13512
13943
  i = points.length;
13513
13944
  while (i--) {
13514
13945
  point = points[i];
13515
- plotX = point.plotX;
13946
+ plotX = mathFloor(point.plotX); // #1843
13516
13947
  plotY = point.plotY;
13517
13948
  graphic = point.graphic;
13518
13949
  pointMarkerOptions = point.marker || {};
@@ -13738,7 +14169,7 @@ Series.prototype = {
13738
14169
  animation: false,
13739
14170
  index: this.index,
13740
14171
  pointStart: this.xData[0] // when updating after addPoint
13741
- }, newOptions);
14172
+ }, { data: this.options.data }, newOptions);
13742
14173
 
13743
14174
  // Destroy the series and reinsert methods from the type prototype
13744
14175
  this.remove(false);
@@ -13879,12 +14310,12 @@ Series.prototype = {
13879
14310
  // in the point options, or if they fall outside the plot area.
13880
14311
  } else if (enabled) {
13881
14312
 
13882
- rotation = options.rotation;
13883
-
13884
14313
  // Create individual options structure that can be extended without
13885
14314
  // affecting others
13886
14315
  options = merge(generalOptions, pointOptions);
13887
-
14316
+
14317
+ rotation = options.rotation;
14318
+
13888
14319
  // Get the string
13889
14320
  labelConfig = point.getLabelConfig();
13890
14321
  str = options.format ?
@@ -13980,7 +14411,7 @@ Series.prototype = {
13980
14411
  width: bBox.width,
13981
14412
  height: bBox.height
13982
14413
  });
13983
-
14414
+
13984
14415
  // Allow a hook for changing alignment in the last moment, then do the alignment
13985
14416
  if (options.rotation) { // Fancy box alignment isn't supported for rotated text
13986
14417
  alignAttr = {
@@ -13996,7 +14427,8 @@ Series.prototype = {
13996
14427
 
13997
14428
  // Show or hide based on the final aligned position
13998
14429
  dataLabel.attr({
13999
- visibility: options.crop === false || /*chart.isInsidePlot(alignAttr.x, alignAttr.y) || */chart.isInsidePlot(plotX, plotY, inverted) ?
14430
+ visibility: options.crop === false ||
14431
+ (chart.isInsidePlot(alignAttr.x, alignAttr.y) && chart.isInsidePlot(alignAttr.x + bBox.width, alignAttr.y + bBox.height)) ?
14000
14432
  (chart.renderer.isSVG ? 'inherit' : VISIBLE) :
14001
14433
  HIDDEN
14002
14434
  });
@@ -14143,7 +14575,7 @@ Series.prototype = {
14143
14575
  var options = this.options,
14144
14576
  chart = this.chart,
14145
14577
  renderer = chart.renderer,
14146
- negativeColor = options.negativeColor,
14578
+ negativeColor = options.negativeColor || options.negativeFillColor,
14147
14579
  translatedThreshold,
14148
14580
  posAttr,
14149
14581
  negAttr,
@@ -14154,11 +14586,12 @@ Series.prototype = {
14154
14586
  chartWidth = chart.chartWidth,
14155
14587
  chartHeight = chart.chartHeight,
14156
14588
  chartSizeMax = mathMax(chartWidth, chartHeight),
14589
+ yAxis = this.yAxis,
14157
14590
  above,
14158
14591
  below;
14159
14592
 
14160
14593
  if (negativeColor && (graph || area)) {
14161
- translatedThreshold = mathCeil(this.yAxis.len - this.yAxis.translate(options.threshold || 0));
14594
+ translatedThreshold = mathRound(yAxis.toPixels(options.threshold || 0, true));
14162
14595
  above = {
14163
14596
  x: 0,
14164
14597
  y: 0,
@@ -14169,25 +14602,29 @@ Series.prototype = {
14169
14602
  x: 0,
14170
14603
  y: translatedThreshold,
14171
14604
  width: chartSizeMax,
14172
- height: chartSizeMax - translatedThreshold
14605
+ height: chartSizeMax
14173
14606
  };
14174
14607
 
14175
- if (chart.inverted && renderer.isVML) {
14176
- above = {
14177
- x: chart.plotWidth - translatedThreshold - chart.plotLeft,
14178
- y: 0,
14179
- width: chartWidth,
14180
- height: chartHeight
14181
- };
14182
- below = {
14183
- x: translatedThreshold + chart.plotLeft - chartWidth,
14184
- y: 0,
14185
- width: chart.plotLeft + translatedThreshold,
14186
- height: chartWidth
14187
- };
14608
+ if (chart.inverted) {
14609
+
14610
+ above.height = below.y = chart.plotWidth - translatedThreshold;
14611
+ if (renderer.isVML) {
14612
+ above = {
14613
+ x: chart.plotWidth - translatedThreshold - chart.plotLeft,
14614
+ y: 0,
14615
+ width: chartWidth,
14616
+ height: chartHeight
14617
+ };
14618
+ below = {
14619
+ x: translatedThreshold + chart.plotLeft - chartWidth,
14620
+ y: 0,
14621
+ width: chart.plotLeft + translatedThreshold,
14622
+ height: chartWidth
14623
+ };
14624
+ }
14188
14625
  }
14189
14626
 
14190
- if (this.yAxis.reversed) {
14627
+ if (yAxis.reversed) {
14191
14628
  posAttr = below;
14192
14629
  negAttr = above;
14193
14630
  } else {
@@ -14203,7 +14640,7 @@ Series.prototype = {
14203
14640
  this.posClip = posClip = renderer.clipRect(posAttr);
14204
14641
  this.negClip = negClip = renderer.clipRect(negAttr);
14205
14642
 
14206
- if (graph) {
14643
+ if (graph && this.graphNeg) {
14207
14644
  graph.clip(posClip);
14208
14645
  this.graphNeg.clip(negClip);
14209
14646
  }
@@ -14260,14 +14697,11 @@ Series.prototype = {
14260
14697
  */
14261
14698
  plotGroup: function (prop, name, visibility, zIndex, parent) {
14262
14699
  var group = this[prop],
14263
- isNew = !group,
14264
- chart = this.chart,
14265
- xAxis = this.xAxis,
14266
- yAxis = this.yAxis;
14700
+ isNew = !group;
14267
14701
 
14268
14702
  // Generate it on first call
14269
14703
  if (isNew) {
14270
- this[prop] = group = chart.renderer.g(name)
14704
+ this[prop] = group = this.chart.renderer.g(name)
14271
14705
  .attr({
14272
14706
  visibility: visibility,
14273
14707
  zIndex: zIndex || 0.1 // IE8 needs this
@@ -14275,14 +14709,20 @@ Series.prototype = {
14275
14709
  .add(parent);
14276
14710
  }
14277
14711
  // Place it on first and subsequent (redraw) calls
14278
- group[isNew ? 'attr' : 'animate']({
14279
- translateX: xAxis ? xAxis.left : chart.plotLeft,
14280
- translateY: yAxis ? yAxis.top : chart.plotTop,
14712
+ group[isNew ? 'attr' : 'animate'](this.getPlotBox());
14713
+ return group;
14714
+ },
14715
+
14716
+ /**
14717
+ * Get the translation and scale for the plot area of this series
14718
+ */
14719
+ getPlotBox: function () {
14720
+ return {
14721
+ translateX: this.xAxis ? this.xAxis.left : this.chart.plotLeft,
14722
+ translateY: this.yAxis ? this.yAxis.top : this.chart.plotTop,
14281
14723
  scaleX: 1, // #1623
14282
14724
  scaleY: 1
14283
- });
14284
- return group;
14285
-
14725
+ };
14286
14726
  },
14287
14727
 
14288
14728
  /**
@@ -14596,9 +15036,8 @@ Series.prototype = {
14596
15036
 
14597
15037
  } else { // create
14598
15038
 
14599
- series.tracker = tracker = renderer.path(trackerPath)
15039
+ series.tracker = renderer.path(trackerPath)
14600
15040
  .attr({
14601
- 'class': PREFIX + 'tracker',
14602
15041
  'stroke-linejoin': 'round', // #1225
14603
15042
  visibility: series.visible ? VISIBLE : HIDDEN,
14604
15043
  stroke: TRACKER_FILL,
@@ -14606,15 +15045,20 @@ Series.prototype = {
14606
15045
  'stroke-width' : options.lineWidth + (trackByArea ? 0 : 2 * snap),
14607
15046
  zIndex: 2
14608
15047
  })
14609
- .addClass(PREFIX + 'tracker')
14610
- .on('mouseover', onMouseOver)
14611
- .on('mouseout', function (e) { pointer.onTrackerMouseOut(e); })
14612
- .css(css)
14613
- .add(series.markerGroup);
15048
+ .add(series.group);
14614
15049
 
14615
- if (hasTouch) {
14616
- tracker.on('touchstart', onMouseOver);
14617
- }
15050
+ // The tracker is added to the series group, which is clipped, but is covered
15051
+ // by the marker group. So the marker group also needs to capture events.
15052
+ each([series.tracker, series.markerGroup], function (tracker) {
15053
+ tracker.addClass(PREFIX + 'tracker')
15054
+ .on('mouseover', onMouseOver)
15055
+ .on('mouseout', function (e) { pointer.onTrackerMouseOut(e); })
15056
+ .css(css);
15057
+
15058
+ if (hasTouch) {
15059
+ tracker.on('touchstart', onMouseOver);
15060
+ }
15061
+ });
14618
15062
  }
14619
15063
 
14620
15064
  }
@@ -14661,6 +15105,7 @@ var AreaSeries = extendClass(Series, {
14661
15105
  plotX,
14662
15106
  plotY,
14663
15107
  points = this.points,
15108
+ val,
14664
15109
  i,
14665
15110
  x;
14666
15111
 
@@ -14688,7 +15133,8 @@ var AreaSeries = extendClass(Series, {
14688
15133
  // correctly.
14689
15134
  } else {
14690
15135
  plotX = xAxis.translate(x);
14691
- plotY = yAxis.toPixels(stack[x].cum, true);
15136
+ val = stack[x].percent ? (stack[x].total ? stack[x].cum * 100 / stack[x].total : 0) : stack[x].cum; // #1991
15137
+ plotY = yAxis.toPixels(val, true);
14692
15138
  segment.push({
14693
15139
  y: null,
14694
15140
  plotX: plotX,
@@ -14784,10 +15230,11 @@ var AreaSeries = extendClass(Series, {
14784
15230
  areaPath = this.areaPath,
14785
15231
  options = this.options,
14786
15232
  negativeColor = options.negativeColor,
15233
+ negativeFillColor = options.negativeFillColor,
14787
15234
  props = [['area', this.color, options.fillColor]]; // area name, main color, fill color
14788
15235
 
14789
- if (negativeColor) {
14790
- props.push(['areaNeg', options.negativeColor, options.negativeFillColor]);
15236
+ if (negativeColor || negativeFillColor) {
15237
+ props.push(['areaNeg', negativeColor, negativeFillColor]);
14791
15238
  }
14792
15239
 
14793
15240
  each(props, function (prop) {
@@ -14803,7 +15250,7 @@ var AreaSeries = extendClass(Series, {
14803
15250
  .attr({
14804
15251
  fill: pick(
14805
15252
  prop[2],
14806
- Color(prop[1]).setOpacity(options.fillOpacity || 0.75).get()
15253
+ Color(prop[1]).setOpacity(pick(options.fillOpacity, 0.75)).get()
14807
15254
  ),
14808
15255
  zIndex: 0 // #1069
14809
15256
  }).add(series.group);
@@ -14973,7 +15420,8 @@ var areaProto = AreaSeries.prototype,
14973
15420
  // Mix in methods from the area series
14974
15421
  getSegmentPath: areaProto.getSegmentPath,
14975
15422
  closeSegment: areaProto.closeSegment,
14976
- drawGraph: areaProto.drawGraph
15423
+ drawGraph: areaProto.drawGraph,
15424
+ drawLegendSymbol: areaProto.drawLegendSymbol
14977
15425
  });
14978
15426
  seriesTypes.areaspline = AreaSplineSeries;
14979
15427
 
@@ -15018,8 +15466,6 @@ defaultPlotOptions.column = merge(defaultSeriesOptions, {
15018
15466
  */
15019
15467
  var ColumnSeries = extendClass(Series, {
15020
15468
  type: 'column',
15021
- tooltipOutsidePlot: true,
15022
- requireSorting: false,
15023
15469
  pointAttrToOptions: { // mapping between SVG attributes and the corresponding options
15024
15470
  stroke: 'borderColor',
15025
15471
  'stroke-width': 'borderWidth',
@@ -15027,6 +15473,8 @@ var ColumnSeries = extendClass(Series, {
15027
15473
  r: 'borderRadius'
15028
15474
  },
15029
15475
  trackerGroups: ['group', 'dataLabelsGroup'],
15476
+ negStacks: true, // use separate negative stacks, unlike area stacks where a negative
15477
+ // point is substracted from previous (#1910)
15030
15478
 
15031
15479
  /**
15032
15480
  * Initialize the series
@@ -15055,9 +15503,9 @@ var ColumnSeries = extendClass(Series, {
15055
15503
  getColumnMetrics: function () {
15056
15504
 
15057
15505
  var series = this,
15058
- chart = series.chart,
15059
15506
  options = series.options,
15060
- xAxis = this.xAxis,
15507
+ xAxis = series.xAxis,
15508
+ yAxis = series.yAxis,
15061
15509
  reversedXAxis = xAxis.reversed,
15062
15510
  stackKey,
15063
15511
  stackGroups = {},
@@ -15070,10 +15518,11 @@ var ColumnSeries = extendClass(Series, {
15070
15518
  if (options.grouping === false) {
15071
15519
  columnCount = 1;
15072
15520
  } else {
15073
- each(chart.series, function (otherSeries) {
15074
- var otherOptions = otherSeries.options;
15521
+ each(series.chart.series, function (otherSeries) {
15522
+ var otherOptions = otherSeries.options,
15523
+ otherYAxis = otherSeries.yAxis;
15075
15524
  if (otherSeries.type === series.type && otherSeries.visible &&
15076
- series.options.group === otherOptions.group) { // used in Stock charts navigator series
15525
+ yAxis.len === otherYAxis.len && yAxis.pos === otherYAxis.pos) { // #642, #2086
15077
15526
  if (otherOptions.stacking) {
15078
15527
  stackKey = otherSeries.stackKey;
15079
15528
  if (stackGroups[stackKey] === UNDEFINED) {
@@ -15121,7 +15570,6 @@ var ColumnSeries = extendClass(Series, {
15121
15570
  var series = this,
15122
15571
  chart = series.chart,
15123
15572
  options = series.options,
15124
- stacking = options.stacking,
15125
15573
  borderWidth = options.borderWidth,
15126
15574
  yAxis = series.yAxis,
15127
15575
  threshold = options.threshold,
@@ -15129,8 +15577,14 @@ var ColumnSeries = extendClass(Series, {
15129
15577
  minPointLength = pick(options.minPointLength, 5),
15130
15578
  metrics = series.getColumnMetrics(),
15131
15579
  pointWidth = metrics.width,
15132
- barW = mathCeil(mathMax(pointWidth, 1 + 2 * borderWidth)), // rounded and postprocessed for border width
15133
- pointXOffset = metrics.offset;
15580
+ seriesBarW = series.barW = mathCeil(mathMax(pointWidth, 1 + 2 * borderWidth)), // rounded and postprocessed for border width
15581
+ pointXOffset = series.pointXOffset = metrics.offset,
15582
+ xCrisp = -(borderWidth % 2 ? 0.5 : 0),
15583
+ yCrisp = borderWidth % 2 ? 0.5 : 1;
15584
+
15585
+ if (chart.renderer.isVML && chart.inverted) {
15586
+ yCrisp += 1;
15587
+ }
15134
15588
 
15135
15589
  Series.prototype.translate.apply(series);
15136
15590
 
@@ -15139,39 +15593,59 @@ var ColumnSeries = extendClass(Series, {
15139
15593
  var plotY = mathMin(mathMax(-999, point.plotY), yAxis.len + 999), // Don't draw too far outside plot area (#1303)
15140
15594
  yBottom = pick(point.yBottom, translatedThreshold),
15141
15595
  barX = point.plotX + pointXOffset,
15142
- barY = mathCeil(mathMin(plotY, yBottom)),
15143
- barH = mathCeil(mathMax(plotY, yBottom) - barY),
15144
- stack = yAxis.stacks[(point.y < 0 ? '-' : '') + series.stackKey],
15145
- shapeArgs;
15146
-
15147
- // Record the offset'ed position and width of the bar to be able to align the stacking total correctly
15148
- if (stacking && series.visible && stack && stack[point.x]) {
15149
- stack[point.x].setOffset(pointXOffset, barW);
15150
- }
15151
-
15152
- // handle options.minPointLength
15596
+ barW = seriesBarW,
15597
+ barY = mathMin(plotY, yBottom),
15598
+ right,
15599
+ bottom,
15600
+ fromTop,
15601
+ fromLeft,
15602
+ barH = mathMax(plotY, yBottom) - barY;
15603
+
15604
+ // Handle options.minPointLength
15153
15605
  if (mathAbs(barH) < minPointLength) {
15154
15606
  if (minPointLength) {
15155
15607
  barH = minPointLength;
15156
15608
  barY =
15157
- mathAbs(barY - translatedThreshold) > minPointLength ? // stacked
15609
+ mathRound(mathAbs(barY - translatedThreshold) > minPointLength ? // stacked
15158
15610
  yBottom - minPointLength : // keep position
15159
- translatedThreshold - (yAxis.translate(point.y, 0, 1, 0, 1) <= translatedThreshold ? minPointLength : 0); // use exact yAxis.translation (#1485)
15611
+ translatedThreshold - (yAxis.translate(point.y, 0, 1, 0, 1) <= translatedThreshold ? minPointLength : 0)); // use exact yAxis.translation (#1485)
15160
15612
  }
15161
15613
  }
15162
15614
 
15615
+ // Cache for access in polar
15163
15616
  point.barX = barX;
15164
15617
  point.pointWidth = pointWidth;
15165
15618
 
15166
- // create shape type and shape args that are reused in drawPoints and drawTracker
15167
- point.shapeType = 'rect';
15168
- point.shapeArgs = shapeArgs = chart.renderer.Element.prototype.crisp.call(0, borderWidth, barX, barY, barW, barH);
15169
-
15170
- if (borderWidth % 2) { // correct for shorting in crisp method, visible in stacked columns with 1px border
15171
- shapeArgs.y -= 1;
15172
- shapeArgs.height += 1;
15619
+
15620
+ // Round off to obtain crisp edges
15621
+ fromLeft = mathAbs(barX) < 0.5;
15622
+ right = mathRound(barX + barW) + xCrisp;
15623
+ barX = mathRound(barX) + xCrisp;
15624
+ barW = right - barX;
15625
+
15626
+ fromTop = mathAbs(barY) < 0.5;
15627
+ bottom = mathRound(barY + barH) + yCrisp;
15628
+ barY = mathRound(barY) + yCrisp;
15629
+ barH = bottom - barY;
15630
+
15631
+ // Top and left edges are exceptions
15632
+ if (fromLeft) {
15633
+ barX += 1;
15634
+ barW -= 1;
15635
+ }
15636
+ if (fromTop) {
15637
+ barY -= 1;
15638
+ barH += 1;
15173
15639
  }
15174
15640
 
15641
+ // Register shape type and arguments to be used in drawPoints
15642
+ point.shapeType = 'rect';
15643
+ point.shapeArgs = {
15644
+ x: barX,
15645
+ y: barY,
15646
+ width: barW,
15647
+ height: barH
15648
+ };
15175
15649
  });
15176
15650
 
15177
15651
  },
@@ -15232,20 +15706,22 @@ var ColumnSeries = extendClass(Series, {
15232
15706
  */
15233
15707
  drawTracker: function () {
15234
15708
  var series = this,
15235
- pointer = series.chart.pointer,
15709
+ chart = series.chart,
15710
+ pointer = chart.pointer,
15236
15711
  cursor = series.options.cursor,
15237
15712
  css = cursor && { cursor: cursor },
15238
15713
  onMouseOver = function (e) {
15239
15714
  var target = e.target,
15240
15715
  point;
15241
15716
 
15242
- series.onMouseOver();
15243
-
15717
+ if (chart.hoverSeries !== series) {
15718
+ series.onMouseOver();
15719
+ }
15244
15720
  while (target && !point) {
15245
15721
  point = target.point;
15246
15722
  target = target.parentNode;
15247
15723
  }
15248
- if (point !== UNDEFINED) { // undefined on graph in scatterchart
15724
+ if (point !== UNDEFINED && point !== chart.hoverPoint) { // undefined on graph in scatterchart
15249
15725
  point.onMouseOver(e);
15250
15726
  }
15251
15727
  };
@@ -15274,8 +15750,6 @@ var ColumnSeries = extendClass(Series, {
15274
15750
  }
15275
15751
  }
15276
15752
  });
15277
-
15278
- } else {
15279
15753
  series._hasTracking = true;
15280
15754
  }
15281
15755
  },
@@ -15495,8 +15969,8 @@ var PiePoint = extendClass(Point, {
15495
15969
  });
15496
15970
 
15497
15971
  // add event listener for select
15498
- toggleSlice = function () {
15499
- point.slice();
15972
+ toggleSlice = function (e) {
15973
+ point.slice(e.type === 'select');
15500
15974
  };
15501
15975
  addEvent(point, 'select', toggleSlice);
15502
15976
  addEvent(point, 'unselect', toggleSlice);
@@ -15641,6 +16115,39 @@ var PieSeries = {
15641
16115
  this.chart.redraw();
15642
16116
  }
15643
16117
  },
16118
+
16119
+ /**
16120
+ * Extend the generatePoints method by adding total and percentage properties to each point
16121
+ */
16122
+ generatePoints: function () {
16123
+ var i,
16124
+ total = 0,
16125
+ points,
16126
+ len,
16127
+ point,
16128
+ ignoreHiddenPoint = this.options.ignoreHiddenPoint;
16129
+
16130
+ Series.prototype.generatePoints.call(this);
16131
+
16132
+ // Populate local vars
16133
+ points = this.points;
16134
+ len = points.length;
16135
+
16136
+ // Get the total sum
16137
+ for (i = 0; i < len; i++) {
16138
+ point = points[i];
16139
+ total += (ignoreHiddenPoint && !point.visible) ? 0 : point.y;
16140
+ }
16141
+ this.total = total;
16142
+
16143
+ // Set each point's properties
16144
+ for (i = 0; i < len; i++) {
16145
+ point = points[i];
16146
+ point.percentage = total > 0 ? (point.y / total) * 100 : 0;
16147
+ point.total = total;
16148
+ }
16149
+
16150
+ },
15644
16151
 
15645
16152
  /**
15646
16153
  * Get the center of the pie based on the size and center options relative to the
@@ -15679,8 +16186,7 @@ var PieSeries = {
15679
16186
  translate: function (positions) {
15680
16187
  this.generatePoints();
15681
16188
 
15682
- var total = 0,
15683
- series = this,
16189
+ var series = this,
15684
16190
  cumulative = 0,
15685
16191
  precision = 1000, // issue #172
15686
16192
  options = series.options,
@@ -15692,7 +16198,6 @@ var PieSeries = {
15692
16198
  startAngleRad = series.startAngleRad = mathPI / 180 * ((options.startAngle || 0) % 360 - 90),
15693
16199
  points = series.points,
15694
16200
  circ = 2 * mathPI,
15695
- fraction,
15696
16201
  radiusX, // the x component of the radius vector for a given point
15697
16202
  radiusY,
15698
16203
  labelDistance = options.dataLabels.distance,
@@ -15718,22 +16223,15 @@ var PieSeries = {
15718
16223
  (mathCos(angle) * (positions[2] / 2 + labelDistance));
15719
16224
  };
15720
16225
 
15721
- // get the total sum
15722
- for (i = 0; i < len; i++) {
15723
- point = points[i];
15724
- total += (ignoreHiddenPoint && !point.visible) ? 0 : point.y;
15725
- }
15726
-
15727
16226
  // Calculate the geometry for each point
15728
16227
  for (i = 0; i < len; i++) {
15729
16228
 
15730
16229
  point = points[i];
15731
16230
 
15732
16231
  // set start and end angle
15733
- fraction = total ? point.y / total : 0;
15734
16232
  start = mathRound((startAngleRad + (cumulative * circ)) * precision) / precision;
15735
16233
  if (!ignoreHiddenPoint || point.visible) {
15736
- cumulative += fraction;
16234
+ cumulative += point.percentage / 100;
15737
16235
  }
15738
16236
  end = mathRound((startAngleRad + (cumulative * circ)) * precision) / precision;
15739
16237
 
@@ -15783,10 +16281,6 @@ var PieSeries = {
15783
16281
  point.half ? 'right' : 'left', // alignment
15784
16282
  angle // center angle
15785
16283
  ];
15786
-
15787
- // API properties
15788
- point.percentage = fraction * 100;
15789
- point.total = total;
15790
16284
 
15791
16285
  }
15792
16286
 
@@ -15909,7 +16403,7 @@ var PieSeries = {
15909
16403
  };
15910
16404
 
15911
16405
  // get out if not enabled
15912
- if (!options.enabled && !series._hasPointLabels) {
16406
+ if (!series.visible || (!options.enabled && !series._hasPointLabels)) {
15913
16407
  return;
15914
16408
  }
15915
16409