contour 2.1.0.beta16 → 2.1.0.beta17

Sign up to get free protection for your applications and to get access to all the features.
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