highcharts-rails 3.0.2 → 3.0.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -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.3 (2013-07-31)
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.3',
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
 
@@ -1462,7 +1473,7 @@ var
1462
1473
  defaultLabelOptions = {
1463
1474
  enabled: true,
1464
1475
  // rotation: 0,
1465
- align: 'center',
1476
+ // align: 'center',
1466
1477
  x: 0,
1467
1478
  y: 15,
1468
1479
  /*formatter: function () {
@@ -1494,8 +1505,8 @@ defaultOptions = {
1494
1505
  },
1495
1506
  global: {
1496
1507
  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'
1508
+ canvasToolsURL: 'http://code.highcharts.com/3.0.3/modules/canvas-tools.js',
1509
+ VMLRadialGradientURL: 'http://code.highcharts.com/3.0.3/gfx/vml-radial-gradient.png'
1499
1510
  },
1500
1511
  chart: {
1501
1512
  //animation: true,
@@ -1546,10 +1557,10 @@ defaultOptions = {
1546
1557
  text: 'Chart title',
1547
1558
  align: 'center',
1548
1559
  // floating: false,
1549
- // margin: 15,
1560
+ margin: 15,
1550
1561
  // x: 0,
1551
1562
  // verticalAlign: 'top',
1552
- y: 15,
1563
+ // y: null,
1553
1564
  style: {
1554
1565
  color: '#274b6d',//#3E576F',
1555
1566
  fontSize: '16px'
@@ -1562,7 +1573,7 @@ defaultOptions = {
1562
1573
  // floating: false
1563
1574
  // x: 0,
1564
1575
  // verticalAlign: 'top',
1565
- y: 30,
1576
+ // y: null,
1566
1577
  style: {
1567
1578
  color: '#4d759e'
1568
1579
  }
@@ -1608,9 +1619,10 @@ defaultOptions = {
1608
1619
  events: {}
1609
1620
  },
1610
1621
  dataLabels: merge(defaultLabelOptions, {
1622
+ align: 'center',
1611
1623
  enabled: false,
1612
1624
  formatter: function () {
1613
- return numberFormat(this.y, -1);
1625
+ return this.y === null ? '' : numberFormat(this.y, -1);
1614
1626
  },
1615
1627
  verticalAlign: 'bottom', // above singular point
1616
1628
  y: 0
@@ -2158,7 +2170,7 @@ SVGElement.prototype = {
2158
2170
 
2159
2171
  i = value.length;
2160
2172
  while (i--) {
2161
- value[i] = pInt(value[i]) * hash['stroke-width'];
2173
+ value[i] = pInt(value[i]) * pick(hash['stroke-width'], wrapper['stroke-width']);
2162
2174
  }
2163
2175
  value = value.join(',');
2164
2176
  }
@@ -2216,7 +2228,7 @@ SVGElement.prototype = {
2216
2228
  }
2217
2229
 
2218
2230
  // let the shadow follow the main element
2219
- if (shadows && /^(width|height|visibility|x|y|d|transform)$/.test(key)) {
2231
+ if (shadows && /^(width|height|visibility|x|y|d|transform|cx|cy|r)$/.test(key)) {
2220
2232
  i = shadows.length;
2221
2233
  while (i--) {
2222
2234
  attr(
@@ -2271,7 +2283,12 @@ SVGElement.prototype = {
2271
2283
  * Add a class name to an element
2272
2284
  */
2273
2285
  addClass: function (className) {
2274
- attr(this.element, 'class', attr(this.element, 'class') + ' ' + className);
2286
+ var element = this.element,
2287
+ currentClassName = attr(element, 'class') || '';
2288
+
2289
+ if (currentClassName.indexOf(className) === -1) {
2290
+ attr(element, 'class', currentClassName + ' ' + className);
2291
+ }
2275
2292
  return this;
2276
2293
  },
2277
2294
  /* hasClass and removeClass are not (yet) needed
@@ -2414,15 +2431,16 @@ SVGElement.prototype = {
2414
2431
  * @param {Function} handler
2415
2432
  */
2416
2433
  on: function (eventType, handler) {
2434
+ var element = this.element;
2417
2435
  // touch
2418
2436
  if (hasTouch && eventType === 'click') {
2419
- this.element.ontouchstart = function (e) {
2437
+ element.ontouchstart = function (e) {
2420
2438
  e.preventDefault();
2421
- handler();
2439
+ handler.call(element, e);
2422
2440
  };
2423
2441
  }
2424
2442
  // simplest possible event model for internal use
2425
- this.element['on' + eventType] = handler;
2443
+ element['on' + eventType] = handler;
2426
2444
  return this;
2427
2445
  },
2428
2446
 
@@ -2568,33 +2586,18 @@ SVGElement.prototype = {
2568
2586
  textWidth = pInt(wrapper.textWidth),
2569
2587
  xCorr = wrapper.xCorr || 0,
2570
2588
  yCorr = wrapper.yCorr || 0,
2571
- currentTextTransform = [rotation, align, elem.innerHTML, wrapper.textWidth].join(','),
2572
- rotationStyle = {},
2573
- cssTransformKey;
2589
+ currentTextTransform = [rotation, align, elem.innerHTML, wrapper.textWidth].join(',');
2574
2590
 
2575
2591
  if (currentTextTransform !== wrapper.cTT) { // do the calculations and DOM access only if properties changed
2576
2592
 
2577
2593
  if (defined(rotation)) {
2578
2594
 
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);
2595
+ radians = rotation * deg2rad; // deg to rad
2596
+ costheta = mathCos(radians);
2597
+ sintheta = mathSin(radians);
2598
+
2599
+ wrapper.setSpanRotation(rotation, sintheta, costheta);
2600
+
2598
2601
  }
2599
2602
 
2600
2603
  width = pick(wrapper.elemWidth, elem.offsetWidth);
@@ -2652,6 +2655,17 @@ SVGElement.prototype = {
2652
2655
  }
2653
2656
  },
2654
2657
 
2658
+ /**
2659
+ * Set the rotation of an individual HTML span
2660
+ */
2661
+ setSpanRotation: function (rotation) {
2662
+ var rotationStyle = {},
2663
+ cssTransformKey = isIE ? '-ms-transform' : isWebKit ? '-webkit-transform' : isFirefox ? 'MozTransform' : isOpera ? '-o-transform' : '';
2664
+
2665
+ rotationStyle[cssTransformKey] = rotationStyle.transform = 'rotate(' + rotation + 'deg)';
2666
+ css(this.element, rotationStyle);
2667
+ },
2668
+
2655
2669
  /**
2656
2670
  * Private method to update the transform attribute based on internal
2657
2671
  * properties
@@ -2954,6 +2968,8 @@ SVGElement.prototype = {
2954
2968
  var wrapper = this,
2955
2969
  element = wrapper.element || {},
2956
2970
  shadows = wrapper.shadows,
2971
+ parentToClean = wrapper.renderer.isSVG && element.nodeName === 'SPAN' && element.parentNode,
2972
+ grandParent,
2957
2973
  key,
2958
2974
  i;
2959
2975
 
@@ -2983,6 +2999,13 @@ SVGElement.prototype = {
2983
2999
  });
2984
3000
  }
2985
3001
 
3002
+ // In case of useHTML, clean up empty containers emulating SVG groups (#1960).
3003
+ while (parentToClean && parentToClean.childNodes.length === 0) {
3004
+ grandParent = parentToClean.parentNode;
3005
+ wrapper.safeRemoveChild(parentToClean);
3006
+ parentToClean = grandParent;
3007
+ }
3008
+
2986
3009
  // remove from alignObjects
2987
3010
  if (wrapper.alignTo) {
2988
3011
  erase(wrapper.renderer.alignedObjects, wrapper);
@@ -3071,18 +3094,24 @@ SVGRenderer.prototype = {
3071
3094
  var renderer = this,
3072
3095
  loc = location,
3073
3096
  boxWrapper,
3097
+ element,
3074
3098
  desc;
3075
3099
 
3076
3100
  boxWrapper = renderer.createElement('svg')
3077
3101
  .attr({
3078
- xmlns: SVG_NS,
3079
3102
  version: '1.1'
3080
3103
  });
3081
- container.appendChild(boxWrapper.element);
3104
+ element = boxWrapper.element;
3105
+ container.appendChild(element);
3106
+
3107
+ // For browsers other than IE, add the namespace attribute (#1978)
3108
+ if (container.innerHTML.indexOf('xmlns') === -1) {
3109
+ attr(element, 'xmlns', SVG_NS);
3110
+ }
3082
3111
 
3083
3112
  // object properties
3084
3113
  renderer.isSVG = true;
3085
- renderer.box = boxWrapper.element;
3114
+ renderer.box = element;
3086
3115
  renderer.boxWrapper = boxWrapper;
3087
3116
  renderer.alignedObjects = [];
3088
3117
 
@@ -3203,7 +3232,7 @@ SVGRenderer.prototype = {
3203
3232
  .split(/<br.*?>/g),
3204
3233
  childNodes = textNode.childNodes,
3205
3234
  styleRegex = /style="([^"]+)"/,
3206
- hrefRegex = /href="([^"]+)"/,
3235
+ hrefRegex = /href="(http[^"]+)"/,
3207
3236
  parentX = attr(textNode, 'x'),
3208
3237
  textStyles = wrapper.styles,
3209
3238
  width = textStyles && textStyles.width && pInt(textStyles.width),
@@ -3249,82 +3278,86 @@ SVGRenderer.prototype = {
3249
3278
  .replace(/&lt;/g, '<')
3250
3279
  .replace(/&gt;/g, '>');
3251
3280
 
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
- }
3281
+ // Nested tags aren't supported, and cause crash in Safari (#1596)
3282
+ if (span !== ' ') {
3283
+
3284
+ // add the text node
3285
+ tspan.appendChild(doc.createTextNode(span));
3260
3286
 
3261
- // add attributes
3262
- attr(tspan, attributes);
3287
+ if (!spanNo) { // first span in a line, align it to the left
3288
+ attributes.x = parentX;
3289
+ } else {
3290
+ attributes.dx = 0; // #16
3291
+ }
3263
3292
 
3264
- // first span on subsequent line, add the line height
3265
- if (!spanNo && lineNo) {
3293
+ // add attributes
3294
+ attr(tspan, attributes);
3266
3295
 
3267
- // allow getting the right offset height in exporting in IE
3268
- if (!hasSVG && forExport) {
3269
- css(tspan, { display: 'block' });
3270
- }
3296
+ // first span on subsequent line, add the line height
3297
+ if (!spanNo && lineNo) {
3271
3298
 
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
- }
3299
+ // allow getting the right offset height in exporting in IE
3300
+ if (!hasSVG && forExport) {
3301
+ css(tspan, { display: 'block' });
3302
+ }
3287
3303
 
3288
- // Append it
3289
- textNode.appendChild(tspan);
3304
+ // Set the line height based on the font size of either
3305
+ // the text element or the tspan element
3306
+ attr(
3307
+ tspan,
3308
+ 'dy',
3309
+ textLineHeight || renderer.fontMetrics(
3310
+ /px$/.test(tspan.style.fontSize) ?
3311
+ tspan.style.fontSize :
3312
+ textStyles.fontSize
3313
+ ).h,
3314
+ // Safari 6.0.2 - too optimized for its own good (#1539)
3315
+ // TODO: revisit this with future versions of Safari
3316
+ isWebKit && tspan.offsetHeight
3317
+ );
3318
+ }
3290
3319
 
3291
- spanNo++;
3320
+ // Append it
3321
+ textNode.appendChild(tspan);
3292
3322
 
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 = [];
3323
+ spanNo++;
3299
3324
 
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;
3325
+ // check width and apply soft breaks
3326
+ if (width) {
3327
+ var words = span.replace(/([^\^])-/g, '$1- ').split(' '), // #1273
3328
+ tooLong,
3329
+ actualWidth,
3306
3330
  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
3331
 
3318
- if (actualWidth > width) { // a single word is pressing it out
3319
- width = actualWidth;
3332
+ while (words.length || rest.length) {
3333
+ delete wrapper.bBox; // delete cache
3334
+ actualWidth = wrapper.getBBox().width;
3335
+ tooLong = actualWidth > width;
3336
+ if (!tooLong || words.length === 1) { // new line needed
3337
+ words = rest;
3338
+ rest = [];
3339
+ if (words.length) {
3340
+ tspan = doc.createElementNS(SVG_NS, 'tspan');
3341
+ attr(tspan, {
3342
+ dy: textLineHeight || 16,
3343
+ x: parentX
3344
+ });
3345
+ if (spanStyle) { // #390
3346
+ attr(tspan, 'style', spanStyle);
3347
+ }
3348
+ textNode.appendChild(tspan);
3349
+
3350
+ if (actualWidth > width) { // a single word is pressing it out
3351
+ width = actualWidth;
3352
+ }
3320
3353
  }
3354
+ } else { // append to existing line tspan
3355
+ tspan.removeChild(tspan.firstChild);
3356
+ rest.unshift(words.pop());
3357
+ }
3358
+ if (words.length) {
3359
+ tspan.appendChild(doc.createTextNode(words.join(' ').replace(/- /g, '-')));
3321
3360
  }
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
3361
  }
3329
3362
  }
3330
3363
  }
@@ -3402,12 +3435,12 @@ SVGRenderer.prototype = {
3402
3435
  pressedStyle = pressedState[STYLE];
3403
3436
  delete pressedState[STYLE];
3404
3437
 
3405
- // add the events
3406
- addEvent(label.element, 'mouseenter', function () {
3438
+ // Add the events. IE9 and IE10 need mouseover and mouseout to funciton (#667).
3439
+ addEvent(label.element, isIE ? 'mouseover' : 'mouseenter', function () {
3407
3440
  label.attr(hoverState)
3408
3441
  .css(hoverStyle);
3409
3442
  });
3410
- addEvent(label.element, 'mouseleave', function () {
3443
+ addEvent(label.element, isIE ? 'mouseout' : 'mouseleave', function () {
3411
3444
  stateOptions = [normalState, hoverState, pressedState][curState];
3412
3445
  stateStyle = [normalStyle, hoverStyle, pressedStyle][curState];
3413
3446
  label.attr(stateOptions)
@@ -3496,8 +3529,7 @@ SVGRenderer.prototype = {
3496
3529
  * @param {Number} end Ending angle
3497
3530
  */
3498
3531
  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
3532
+ var arc;
3501
3533
 
3502
3534
  if (isObject(x)) {
3503
3535
  y = x.y;
@@ -3507,11 +3539,16 @@ SVGRenderer.prototype = {
3507
3539
  end = x.end;
3508
3540
  x = x.x;
3509
3541
  }
3510
- return this.symbol('arc', x || 0, y || 0, r || 0, r || 0, {
3542
+
3543
+ // Arcs are defined as symbols for the ability to set
3544
+ // attributes in attr and animate
3545
+ arc = this.symbol('arc', x || 0, y || 0, r || 0, r || 0, {
3511
3546
  innerR: innerR || 0,
3512
3547
  start: start || 0,
3513
3548
  end: end || 0
3514
3549
  });
3550
+ arc.r = r; // #959
3551
+ return arc;
3515
3552
  },
3516
3553
 
3517
3554
  /**
@@ -4537,6 +4574,22 @@ Highcharts.VMLElement = VMLElement = {
4537
4574
  */
4538
4575
  updateTransform: SVGElement.prototype.htmlUpdateTransform,
4539
4576
 
4577
+ /**
4578
+ * Set the rotation of a span with oldIE's filter
4579
+ */
4580
+ setSpanRotation: function (rotation, sintheta, costheta) {
4581
+ // Adjust for alignment and rotation. Rotation of useHTML content is not yet implemented
4582
+ // but it can probably be implemented for Firefox 3.5+ on user request. FF3.5+
4583
+ // has support for CSS3 transform. The getBBox method also needs to be updated
4584
+ // to compensate for the rotation, like it currently does for SVG.
4585
+ // Test case: http://highcharts.com/tests/?file=text-rotation
4586
+ css(this.element, {
4587
+ filter: rotation ? ['progid:DXImageTransform.Microsoft.Matrix(M11=', costheta,
4588
+ ', M12=', -sintheta, ', M21=', sintheta, ', M22=', costheta,
4589
+ ', sizingMethod=\'auto expand\')'].join('') : NONE
4590
+ });
4591
+ },
4592
+
4540
4593
  /**
4541
4594
  * Get or set attributes
4542
4595
  */
@@ -4759,7 +4812,9 @@ Highcharts.VMLElement = VMLElement = {
4759
4812
 
4760
4813
  // rotation on VML elements
4761
4814
  } else if (nodeName === 'shape' && key === 'rotation') {
4762
- wrapper[key] = value;
4815
+
4816
+ wrapper[key] = element.style[key] = value; // style is for #1873
4817
+
4763
4818
  // Correction for the 1x1 size of the shape container. Used in gauge needles.
4764
4819
  element.style.left = -mathRound(mathSin(value * deg2rad) + 1) + PX;
4765
4820
  element.style.top = mathRound(mathCos(value * deg2rad)) + PX;
@@ -5669,7 +5724,7 @@ Tick.prototype = {
5669
5724
  !labelOptions.step && !labelOptions.staggerLines &&
5670
5725
  !labelOptions.rotation &&
5671
5726
  chart.plotWidth / tickPositions.length) ||
5672
- (!horiz && (chart.optionsMarginLeft || chart.plotWidth / 2)), // #1580
5727
+ (!horiz && (chart.optionsMarginLeft || chart.chartWidth * 0.33)), // #1580, #1931
5673
5728
  isFirst = pos === tickPositions[0],
5674
5729
  isLast = pos === tickPositions[tickPositions.length - 1],
5675
5730
  css,
@@ -5708,7 +5763,7 @@ Tick.prototype = {
5708
5763
  // first call
5709
5764
  if (!defined(label)) {
5710
5765
  attr = {
5711
- align: labelOptions.align
5766
+ align: axis.labelAlign
5712
5767
  };
5713
5768
  if (isNumber(labelOptions.rotation)) {
5714
5769
  attr.rotation = labelOptions.rotation;
@@ -5757,7 +5812,7 @@ Tick.prototype = {
5757
5812
  options = axis.options,
5758
5813
  labelOptions = options.labels,
5759
5814
  width = bBox.width,
5760
- leftSide = width * { left: 0, center: 0.5, right: 1 }[labelOptions.align] - labelOptions.x;
5815
+ leftSide = width * { left: 0, center: 0.5, right: 1 }[axis.labelAlign] - labelOptions.x;
5761
5816
 
5762
5817
  return [-leftSide, width - leftSide];
5763
5818
  },
@@ -5847,21 +5902,28 @@ Tick.prototype = {
5847
5902
  var axis = this.axis,
5848
5903
  transA = axis.transA,
5849
5904
  reversed = axis.reversed,
5850
- staggerLines = axis.staggerLines;
5905
+ staggerLines = axis.staggerLines,
5906
+ baseline = axis.chart.renderer.fontMetrics(labelOptions.style.fontSize).b,
5907
+ rotation = labelOptions.rotation;
5851
5908
 
5852
5909
  x = x + labelOptions.x - (tickmarkOffset && horiz ?
5853
5910
  tickmarkOffset * transA * (reversed ? -1 : 1) : 0);
5854
5911
  y = y + labelOptions.y - (tickmarkOffset && !horiz ?
5855
5912
  tickmarkOffset * transA * (reversed ? 1 : -1) : 0);
5913
+
5914
+ // Correct for rotation (#1764)
5915
+ if (rotation && axis.side === 2) {
5916
+ y -= baseline - baseline * mathCos(rotation * deg2rad);
5917
+ }
5856
5918
 
5857
5919
  // Vertically centered
5858
- if (!defined(labelOptions.y)) {
5859
- y += pInt(label.styles.lineHeight) * 0.9 - label.getBBox().height / 2;
5920
+ if (!defined(labelOptions.y) && !rotation) { // #1951
5921
+ y += baseline - label.getBBox().height / 2;
5860
5922
  }
5861
5923
 
5862
5924
  // Correct for staggered labels
5863
5925
  if (staggerLines) {
5864
- y += (index / (step || 1) % staggerLines) * 16;
5926
+ y += (index / (step || 1) % staggerLines) * (axis.labelOffset / staggerLines);
5865
5927
  }
5866
5928
 
5867
5929
  return {
@@ -6160,7 +6222,8 @@ PlotLineOrBand.prototype = {
6160
6222
  plotLine.label = label = renderer.text(
6161
6223
  optionsLabel.text,
6162
6224
  0,
6163
- 0
6225
+ 0,
6226
+ optionsLabel.useHTML // docs: useHTML for plotLines and plotBands
6164
6227
  )
6165
6228
  .attr({
6166
6229
  align: optionsLabel.textAlign || optionsLabel.align,
@@ -6224,6 +6287,12 @@ function StackItem(axis, options, isNegative, x, stackOption, stacking) {
6224
6287
  // Save the x value to be able to position the label later
6225
6288
  this.x = x;
6226
6289
 
6290
+ // Initialize total value
6291
+ this.total = 0;
6292
+
6293
+ // This will keep each points' extremes stored by series.index
6294
+ this.points = {};
6295
+
6227
6296
  // Save the stack option on the series configuration object, and whether to treat it as percent
6228
6297
  this.stack = stackOption;
6229
6298
  this.percent = stacking === 'percent';
@@ -6255,12 +6324,19 @@ StackItem.prototype = {
6255
6324
  this.cum = total;
6256
6325
  },
6257
6326
 
6327
+ /**
6328
+ * Adds value to stack total, this method takes care of correcting floats
6329
+ */
6330
+ addValue: function (y) {
6331
+ this.setTotal(correctFloat(this.total + y));
6332
+ },
6333
+
6258
6334
  /**
6259
6335
  * Renders the stack total label and adds it to the stack label group.
6260
6336
  */
6261
6337
  render: function (group) {
6262
6338
  var options = this.options,
6263
- formatOption = options.format, // docs: added stackLabel.format option
6339
+ formatOption = options.format,
6264
6340
  str = formatOption ?
6265
6341
  format(formatOption, this) :
6266
6342
  options.formatter.call(this); // format the text in the label
@@ -6282,6 +6358,10 @@ StackItem.prototype = {
6282
6358
  }
6283
6359
  },
6284
6360
 
6361
+ cacheExtremes: function (series, extremes) {
6362
+ this.points[series.index] = extremes;
6363
+ },
6364
+
6285
6365
  /**
6286
6366
  * Sets the offset that the stack has from the x value and repositions the label.
6287
6367
  */
@@ -6421,7 +6501,6 @@ Axis.prototype = {
6421
6501
  tickPixelInterval: 72,
6422
6502
  showLastLabel: true,
6423
6503
  labels: {
6424
- align: 'right',
6425
6504
  x: -8,
6426
6505
  y: 3
6427
6506
  },
@@ -6454,7 +6533,6 @@ Axis.prototype = {
6454
6533
  */
6455
6534
  defaultLeftAxisOptions: {
6456
6535
  labels: {
6457
- align: 'right',
6458
6536
  x: -8,
6459
6537
  y: null
6460
6538
  },
@@ -6468,7 +6546,6 @@ Axis.prototype = {
6468
6546
  */
6469
6547
  defaultRightAxisOptions: {
6470
6548
  labels: {
6471
- align: 'left',
6472
6549
  x: 8,
6473
6550
  y: null
6474
6551
  },
@@ -6482,7 +6559,6 @@ Axis.prototype = {
6482
6559
  */
6483
6560
  defaultBottomAxisOptions: {
6484
6561
  labels: {
6485
- align: 'center',
6486
6562
  x: 0,
6487
6563
  y: 14
6488
6564
  // overflow: undefined,
@@ -6497,7 +6573,6 @@ Axis.prototype = {
6497
6573
  */
6498
6574
  defaultTopAxisOptions: {
6499
6575
  labels: {
6500
- align: 'center',
6501
6576
  x: 0,
6502
6577
  y: -5
6503
6578
  // overflow: undefined
@@ -6541,7 +6616,6 @@ Axis.prototype = {
6541
6616
 
6542
6617
 
6543
6618
  // Flag, stagger lines or not
6544
- axis.staggerLines = axis.horiz && options.labels.staggerLines;
6545
6619
  axis.userOptions = userOptions;
6546
6620
 
6547
6621
  //axis.axisTitleMargin = UNDEFINED,// = options.title.margin,
@@ -6619,8 +6693,13 @@ Axis.prototype = {
6619
6693
 
6620
6694
  // Dictionary for stacks
6621
6695
  axis.stacks = {};
6696
+ axis.oldStacks = {};
6697
+
6698
+ // Dictionary for stacks max values
6699
+ axis.stacksMax = {};
6700
+
6622
6701
  axis._stacksTouched = 0;
6623
-
6702
+
6624
6703
  // Min and max in the data
6625
6704
  //axis.dataMin = UNDEFINED,
6626
6705
  //axis.dataMax = UNDEFINED,
@@ -6691,10 +6770,10 @@ Axis.prototype = {
6691
6770
 
6692
6771
  newOptions = chart.options[this.xOrY + 'Axis'][this.options.index] = merge(this.userOptions, newOptions);
6693
6772
 
6694
- this.destroy();
6773
+ this.destroy(true);
6695
6774
  this._addedPlotLB = false; // #1611
6696
6775
 
6697
- this.init(chart, newOptions);
6776
+ this.init(chart, extend(newOptions, { events: UNDEFINED }));
6698
6777
 
6699
6778
  chart.isDirtyBox = true;
6700
6779
  if (pick(redraw, true)) {
@@ -6778,25 +6857,24 @@ Axis.prototype = {
6778
6857
 
6779
6858
  return ret;
6780
6859
  },
6781
-
6860
+
6782
6861
  /**
6783
6862
  * Get the minimum and maximum for the series of each axis
6784
6863
  */
6785
6864
  getSeriesExtremes: function () {
6786
6865
  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
-
6866
+ chart = axis.chart;
6867
+
6795
6868
  axis.hasVisibleSeries = false;
6796
6869
 
6797
6870
  // reset dataMin and dataMax in case we're redrawing
6798
6871
  axis.dataMin = axis.dataMax = null;
6799
6872
 
6873
+ // reset cached stacking extremes
6874
+ axis.stacksMax = {};
6875
+
6876
+ axis.buildStacks();
6877
+
6800
6878
  // loop through this axis' series
6801
6879
  each(axis.series, function (series) {
6802
6880
 
@@ -6804,27 +6882,16 @@ Axis.prototype = {
6804
6882
 
6805
6883
  var seriesOptions = series.options,
6806
6884
  stacking,
6807
- posPointStack,
6808
- negPointStack,
6809
- stackKey,
6810
- stackOption,
6811
- negKey,
6812
6885
  xData,
6813
- yData,
6814
- x,
6815
- y,
6816
6886
  threshold = seriesOptions.threshold,
6817
- yDataLength,
6818
- activeYData = [],
6819
6887
  seriesDataMin,
6820
- seriesDataMax,
6821
- activeCounter = 0;
6822
-
6823
- axis.hasVisibleSeries = true;
6824
-
6888
+ seriesDataMax;
6889
+
6890
+ axis.hasVisibleSeries = true;
6891
+
6825
6892
  // Validate threshold in logarithmic axes
6826
6893
  if (axis.isLog && threshold <= 0) {
6827
- threshold = seriesOptions.threshold = null;
6894
+ threshold = null;
6828
6895
  }
6829
6896
 
6830
6897
  // Get dataMin and dataMax for X axes
@@ -6837,112 +6904,27 @@ Axis.prototype = {
6837
6904
 
6838
6905
  // Get dataMin and dataMax for Y axes, as well as handle stacking and processed data
6839
6906
  } 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
6907
 
6850
6908
  // Handle stacking
6851
6909
  stacking = seriesOptions.stacking;
6852
6910
  axis.usePercentage = stacking === 'percent';
6853
6911
 
6854
6912
  // 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
6913
  if (axis.usePercentage) {
6868
6914
  axis.dataMin = 0;
6869
6915
  axis.dataMax = 99;
6870
6916
  }
6871
6917
 
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
- }
6918
+
6919
+ // get this particular series extremes
6920
+ series.getExtremes();
6921
+ seriesDataMax = series.dataMax;
6922
+ seriesDataMin = series.dataMin;
6940
6923
 
6941
6924
  // 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);
6925
+ // always 0 and 100. If seriesDataMin and seriesDataMax is null, then series
6926
+ // doesn't have active y data, we continue with nulls
6927
+ if (!axis.usePercentage && defined(seriesDataMin) && defined(seriesDataMax)) {
6946
6928
  axis.dataMin = mathMin(pick(axis.dataMin, seriesDataMin), seriesDataMin);
6947
6929
  axis.dataMax = mathMax(pick(axis.dataMax, seriesDataMax), seriesDataMax);
6948
6930
  }
@@ -6960,24 +6942,13 @@ Axis.prototype = {
6960
6942
  }
6961
6943
  }
6962
6944
  });
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
6945
  },
6975
6946
 
6976
6947
  /**
6977
6948
  * Translate from axis value to pixel position on the chart, or back
6978
6949
  *
6979
6950
  */
6980
- translate: function (val, backwards, cvsCoord, old, handleLog, pointPlacementBetween) {
6951
+ translate: function (val, backwards, cvsCoord, old, handleLog, pointPlacement) {
6981
6952
  var axis = this,
6982
6953
  axisLength = axis.len,
6983
6954
  sign = 1,
@@ -7020,9 +6991,11 @@ Axis.prototype = {
7020
6991
  if (postTranslate) { // log and ordinal axes
7021
6992
  val = axis.val2lin(val);
7022
6993
  }
7023
-
6994
+ if (pointPlacement === 'between') {
6995
+ pointPlacement = 0.5;
6996
+ }
7024
6997
  returnValue = sign * (val - localMin) * localA + cvsOffset + (sign * minPixelPadding) +
7025
- (pointPlacementBetween ? localA * axis.pointRange / 2 : 0);
6998
+ (isNumber(pointPlacement) ? localA * pointPlacement * axis.pointRange : 0);
7026
6999
  }
7027
7000
 
7028
7001
  return returnValue;
@@ -7226,7 +7199,7 @@ Axis.prototype = {
7226
7199
  interval = normalizeTickInterval(
7227
7200
  interval,
7228
7201
  null,
7229
- math.pow(10, mathFloor(math.log(interval) / math.LN10))
7202
+ getMagnitude(interval)
7230
7203
  );
7231
7204
 
7232
7205
  positions = map(axis.getLinearTickPositions(
@@ -7390,7 +7363,7 @@ Axis.prototype = {
7390
7363
  var seriesPointRange = series.pointRange,
7391
7364
  pointPlacement = series.options.pointPlacement,
7392
7365
  seriesClosestPointRange = series.closestPointRange;
7393
-
7366
+
7394
7367
  if (seriesPointRange > range) { // #1446
7395
7368
  seriesPointRange = 0;
7396
7369
  }
@@ -7401,7 +7374,7 @@ Axis.prototype = {
7401
7374
  // is 'between' or 'on', this padding does not apply.
7402
7375
  minPointOffset = mathMax(
7403
7376
  minPointOffset,
7404
- pointPlacement ? 0 : seriesPointRange / 2
7377
+ isString(pointPlacement) ? 0 : seriesPointRange / 2
7405
7378
  );
7406
7379
 
7407
7380
  // Determine the total padding needed to the length of the axis to make room for the
@@ -7456,7 +7429,6 @@ Axis.prototype = {
7456
7429
  isXAxis = axis.isXAxis,
7457
7430
  isLinked = axis.isLinked,
7458
7431
  tickPositioner = axis.options.tickPositioner,
7459
- magnitude,
7460
7432
  maxPadding = options.maxPadding,
7461
7433
  minPadding = options.minPadding,
7462
7434
  length,
@@ -7555,6 +7527,11 @@ Axis.prototype = {
7555
7527
  if (axis.postProcessTickInterval) {
7556
7528
  axis.tickInterval = axis.postProcessTickInterval(axis.tickInterval);
7557
7529
  }
7530
+
7531
+ // In column-like charts, don't cramp in more ticks than there are points (#1943)
7532
+ if (axis.pointRange) {
7533
+ axis.tickInterval = mathMax(axis.pointRange, axis.tickInterval);
7534
+ }
7558
7535
 
7559
7536
  // Before normalizing the tick interval, handle minimum tick interval. This applies only if tickInterval is not defined.
7560
7537
  if (!tickIntervalOption && axis.tickInterval < minTickIntervalOption) {
@@ -7563,9 +7540,8 @@ Axis.prototype = {
7563
7540
 
7564
7541
  // for linear axes, get magnitude and normalize the interval
7565
7542
  if (!isDatetimeAxis && !isLog) { // linear
7566
- magnitude = math.pow(10, mathFloor(math.log(axis.tickInterval) / math.LN10));
7567
7543
  if (!tickIntervalOption) {
7568
- axis.tickInterval = normalizeTickInterval(axis.tickInterval, null, magnitude, options);
7544
+ axis.tickInterval = normalizeTickInterval(axis.tickInterval, null, getMagnitude(axis.tickInterval), options);
7569
7545
  }
7570
7546
  }
7571
7547
 
@@ -7706,10 +7682,20 @@ Axis.prototype = {
7706
7682
  isDirtyData = true;
7707
7683
  }
7708
7684
  });
7709
-
7685
+
7686
+
7710
7687
  // do we really need to go through all this?
7711
7688
  if (isDirtyAxisLength || isDirtyData || axis.isLinked || axis.forceRedraw ||
7712
7689
  axis.userMin !== axis.oldUserMin || axis.userMax !== axis.oldUserMax) {
7690
+
7691
+ // reset stacks
7692
+ if (!axis.isXAxis) {
7693
+ for (type in stacks) {
7694
+ for (i in stacks[type]) {
7695
+ stacks[type][i].total = null;
7696
+ }
7697
+ }
7698
+ }
7713
7699
 
7714
7700
  axis.forceRedraw = false;
7715
7701
 
@@ -7727,11 +7713,12 @@ Axis.prototype = {
7727
7713
  if (!axis.isDirty) {
7728
7714
  axis.isDirty = isDirtyAxisLength || axis.min !== axis.oldMin || axis.max !== axis.oldMax;
7729
7715
  }
7730
- }
7731
-
7732
-
7733
- // reset stacks
7734
- if (!axis.isXAxis) {
7716
+ } else if (!axis.isXAxis) {
7717
+ if (axis.oldStacks) {
7718
+ stacks = axis.stacks = axis.oldStacks;
7719
+ }
7720
+
7721
+ // reset stacks
7735
7722
  for (type in stacks) {
7736
7723
  for (i in stacks[type]) {
7737
7724
  stacks[type][i].cum = stacks[type][i].total;
@@ -7787,12 +7774,12 @@ Axis.prototype = {
7787
7774
  */
7788
7775
  zoom: function (newMin, newMax) {
7789
7776
 
7790
- // Prevent pinch zooming out of range
7777
+ // Prevent pinch zooming out of range. Check for defined is for #1946.
7791
7778
  if (!this.allowZoomOutside) {
7792
- if (newMin <= this.dataMin) {
7779
+ if (defined(this.dataMin) && newMin <= this.dataMin) {
7793
7780
  newMin = UNDEFINED;
7794
7781
  }
7795
- if (newMax >= this.dataMax) {
7782
+ if (defined(this.dataMax) && newMax >= this.dataMax) {
7796
7783
  newMax = UNDEFINED;
7797
7784
  }
7798
7785
  }
@@ -7903,6 +7890,24 @@ Axis.prototype = {
7903
7890
  return obj;
7904
7891
  },
7905
7892
 
7893
+ /**
7894
+ * Compute auto alignment for the axis label based on which side the axis is on
7895
+ * and the given rotation for the label
7896
+ */
7897
+ autoLabelAlign: function (rotation) {
7898
+ var ret,
7899
+ angle = (pick(rotation, 0) - (this.side * 90) + 720) % 360;
7900
+
7901
+ if (angle > 15 && angle < 165) {
7902
+ ret = 'right';
7903
+ } else if (angle > 195 && angle < 345) {
7904
+ ret = 'left';
7905
+ } else {
7906
+ ret = 'center';
7907
+ }
7908
+ return ret;
7909
+ },
7910
+
7906
7911
  /**
7907
7912
  * Render the tick labels to a preliminary position to get their sizes
7908
7913
  */
@@ -7927,11 +7932,24 @@ Axis.prototype = {
7927
7932
  axisOffset = chart.axisOffset,
7928
7933
  clipOffset = chart.clipOffset,
7929
7934
  directionFactor = [-1, 1, 1, -1][side],
7930
- n;
7935
+ n,
7936
+ i,
7937
+ autoStaggerLines = 1,
7938
+ maxStaggerLines = pick(labelOptions.maxStaggerLines, 5), // docs
7939
+ lastRight,
7940
+ overlap,
7941
+ pos,
7942
+ bBox,
7943
+ x,
7944
+ w,
7945
+ lineNo;
7931
7946
 
7932
7947
  // For reuse in Axis.render
7933
7948
  axis.hasData = hasData = (axis.hasVisibleSeries || (defined(axis.min) && defined(axis.max) && !!tickPositions));
7934
7949
  axis.showAxis = showAxis = hasData || pick(options.showEmpty, true);
7950
+
7951
+ // Set/reset staggerLines
7952
+ axis.staggerLines = axis.horiz && labelOptions.staggerLines;
7935
7953
 
7936
7954
  // Create the axisGroup and gridGroup elements on first iteration
7937
7955
  if (!axis.axisGroup) {
@@ -7947,18 +7965,54 @@ Axis.prototype = {
7947
7965
  }
7948
7966
 
7949
7967
  if (hasData || axis.isLinked) {
7968
+
7969
+ // Set the explicit or automatic label alignment
7970
+ axis.labelAlign = pick(labelOptions.align || axis.autoLabelAlign(labelOptions.rotation));
7971
+
7950
7972
  each(tickPositions, function (pos) {
7951
7973
  if (!ticks[pos]) {
7952
7974
  ticks[pos] = new Tick(axis, pos);
7953
7975
  } else {
7954
7976
  ticks[pos].addLabel(); // update labels depending on tick interval
7955
7977
  }
7956
-
7957
7978
  });
7958
7979
 
7980
+ // Handle automatic stagger lines
7981
+ if (axis.horiz && !axis.staggerLines && maxStaggerLines && !labelOptions.rotation) {
7982
+ while (autoStaggerLines < maxStaggerLines) {
7983
+ lastRight = [];
7984
+ overlap = false;
7985
+
7986
+ for (i = 0; i < tickPositions.length; i++) {
7987
+ pos = tickPositions[i];
7988
+ bBox = ticks[pos].label && ticks[pos].label.bBox;
7989
+ w = bBox ? bBox.width : 0;
7990
+ lineNo = i % autoStaggerLines;
7991
+
7992
+ if (w) {
7993
+ x = axis.translate(pos); // don't handle log
7994
+ if (lastRight[lineNo] !== UNDEFINED && x < lastRight[lineNo]) {
7995
+ overlap = true;
7996
+ }
7997
+ lastRight[lineNo] = x + w;
7998
+ }
7999
+ }
8000
+ if (overlap) {
8001
+ autoStaggerLines++;
8002
+ } else {
8003
+ break;
8004
+ }
8005
+ }
8006
+
8007
+ if (autoStaggerLines > 1) {
8008
+ axis.staggerLines = autoStaggerLines;
8009
+ }
8010
+ }
8011
+
8012
+
7959
8013
  each(tickPositions, function (pos) {
7960
8014
  // 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) {
8015
+ if (side === 0 || side === 2 || { 1: 'left', 3: 'right' }[side] === axis.labelAlign) {
7962
8016
 
7963
8017
  // get the highest offset
7964
8018
  labelOffset = mathMax(
@@ -7968,10 +8022,11 @@ Axis.prototype = {
7968
8022
  }
7969
8023
 
7970
8024
  });
7971
-
7972
8025
  if (axis.staggerLines) {
7973
- labelOffset += (axis.staggerLines - 1) * 16;
8026
+ labelOffset *= axis.staggerLines;
8027
+ axis.labelOffset = labelOffset;
7974
8028
  }
8029
+
7975
8030
 
7976
8031
  } else { // doesn't have data
7977
8032
  for (n in ticks) {
@@ -8326,12 +8381,23 @@ Axis.prototype = {
8326
8381
  */
8327
8382
  removePlotBandOrLine: function (id) {
8328
8383
  var plotLinesAndBands = this.plotLinesAndBands,
8384
+ options = this.options,
8385
+ userOptions = this.userOptions,
8329
8386
  i = plotLinesAndBands.length;
8330
8387
  while (i--) {
8331
8388
  if (plotLinesAndBands[i].id === id) {
8332
8389
  plotLinesAndBands[i].destroy();
8333
8390
  }
8334
8391
  }
8392
+ each([options.plotLines || [], userOptions.plotLines || [], options.plotBands || [], userOptions.plotBands || []], function (arr) {
8393
+ i = arr.length;
8394
+ while (i--) {
8395
+ if (arr[i].id === id) {
8396
+ erase(arr, arr[i]);
8397
+ }
8398
+ }
8399
+ });
8400
+
8335
8401
  },
8336
8402
 
8337
8403
  /**
@@ -8369,6 +8435,22 @@ Axis.prototype = {
8369
8435
 
8370
8436
  },
8371
8437
 
8438
+ /**
8439
+ *
8440
+ */
8441
+ buildStacks: function () {
8442
+ if (this.isXAxis) {
8443
+ return;
8444
+ }
8445
+
8446
+ var series = this.series,
8447
+ last = series.length - 1;
8448
+
8449
+ each(series, function (serie, i) {
8450
+ serie.setStackedPoints(i === last);
8451
+ });
8452
+ },
8453
+
8372
8454
  /**
8373
8455
  * Set new axis categories and optionally redraw
8374
8456
  * @param {Array} categories
@@ -8381,13 +8463,15 @@ Axis.prototype = {
8381
8463
  /**
8382
8464
  * Destroys an Axis instance.
8383
8465
  */
8384
- destroy: function () {
8466
+ destroy: function (keepEvents) {
8385
8467
  var axis = this,
8386
8468
  stacks = axis.stacks,
8387
8469
  stackKey;
8388
8470
 
8389
8471
  // Remove the events
8390
- removeEvent(axis);
8472
+ if (!keepEvents) {
8473
+ removeEvent(axis);
8474
+ }
8391
8475
 
8392
8476
  // Destroy each stack total
8393
8477
  for (stackKey in stacks) {
@@ -8798,15 +8882,20 @@ Tooltip.prototype = {
8798
8882
  i = crosshairsOptions.length,
8799
8883
  attribs,
8800
8884
  axis,
8801
- val;
8885
+ val,
8886
+ series;
8802
8887
 
8803
8888
  while (i--) {
8804
- axis = point.series[i ? 'yAxis' : 'xAxis'];
8889
+ series = point.series;
8890
+ axis = series[i ? 'yAxis' : 'xAxis'];
8805
8891
  if (crosshairsOptions[i] && axis) {
8806
8892
  val = i ? pick(point.stackY, point.y) : point.x; // #814
8807
8893
  if (axis.isLog) { // #1671
8808
8894
  val = log2lin(val);
8809
8895
  }
8896
+ if (series.modifyValue) { // #1205
8897
+ val = series.modifyValue(val);
8898
+ }
8810
8899
 
8811
8900
  path = axis.getPlotLinePath(
8812
8901
  val,
@@ -8908,8 +8997,6 @@ Pointer.prototype = {
8908
8997
  */
8909
8998
  normalize: function (e) {
8910
8999
  var chartPosition,
8911
- chartX,
8912
- chartY,
8913
9000
  ePos;
8914
9001
 
8915
9002
  // common IE normalizing
@@ -8927,18 +9014,10 @@ Pointer.prototype = {
8927
9014
  // get mouse position
8928
9015
  this.chartPosition = chartPosition = offset(this.chart.container);
8929
9016
 
8930
- // chartX and chartY
8931
- if (ePos.pageX === UNDEFINED) { // IE < 9. #886.
8932
- chartX = e.x;
8933
- chartY = e.y;
8934
- } else {
8935
- chartX = ePos.pageX - chartPosition.left;
8936
- chartY = ePos.pageY - chartPosition.top;
8937
- }
8938
-
9017
+ // Old IE and compatibility mode use clientX. #886, #2005.
8939
9018
  return extend(e, {
8940
- chartX: mathRound(chartX),
8941
- chartY: mathRound(chartY)
9019
+ chartX: mathRound(pick(ePos.pageX, ePos.clientX) - chartPosition.left),
9020
+ chartY: mathRound(pick(ePos.pageY, ePos.clientY) - chartPosition.top)
8942
9021
  });
8943
9022
  },
8944
9023
 
@@ -9097,18 +9176,20 @@ Pointer.prototype = {
9097
9176
  */
9098
9177
  scaleGroups: function (attribs, clip) {
9099
9178
 
9100
- var chart = this.chart;
9179
+ var chart = this.chart,
9180
+ seriesAttribs;
9101
9181
 
9102
9182
  // Scale each series
9103
9183
  each(chart.series, function (series) {
9184
+ seriesAttribs = attribs || series.getPlotBox(); // #1701
9104
9185
  if (series.xAxis && series.xAxis.zoomEnabled) {
9105
- series.group.attr(attribs);
9186
+ series.group.attr(seriesAttribs);
9106
9187
  if (series.markerGroup) {
9107
- series.markerGroup.attr(attribs);
9188
+ series.markerGroup.attr(seriesAttribs);
9108
9189
  series.markerGroup.clip(clip ? chart.clipRect : null);
9109
9190
  }
9110
9191
  if (series.dataLabelsGroup) {
9111
- series.dataLabelsGroup.attr(attribs);
9192
+ series.dataLabelsGroup.attr(seriesAttribs);
9112
9193
  }
9113
9194
  }
9114
9195
  });
@@ -9428,12 +9509,7 @@ Pointer.prototype = {
9428
9509
 
9429
9510
  // Reset scaling preview
9430
9511
  if (hasPinched) {
9431
- this.scaleGroups({
9432
- translateX: chart.plotLeft,
9433
- translateY: chart.plotTop,
9434
- scaleX: 1,
9435
- scaleY: 1
9436
- });
9512
+ this.scaleGroups();
9437
9513
  }
9438
9514
  }
9439
9515
 
@@ -9612,6 +9688,10 @@ Pointer.prototype = {
9612
9688
  this.runPointActions(e);
9613
9689
 
9614
9690
  this.pinch(e);
9691
+
9692
+ } else {
9693
+ // Hide the tooltip on touching outside the plot area (#1203)
9694
+ this.reset();
9615
9695
  }
9616
9696
 
9617
9697
  } else if (e.touches.length === 2) {
@@ -9763,7 +9843,6 @@ Legend.prototype = {
9763
9843
  },
9764
9844
  key,
9765
9845
  val;
9766
-
9767
9846
 
9768
9847
  if (legendItem) {
9769
9848
  legendItem.css({ fill: textColor, color: textColor }); // color for #1553, oldIE
@@ -9775,7 +9854,7 @@ Legend.prototype = {
9775
9854
  if (legendSymbol) {
9776
9855
 
9777
9856
  // Apply marker options
9778
- if (markerOptions) {
9857
+ if (markerOptions && legendSymbol.isMarker) { // #585
9779
9858
  markerOptions = item.convertAttribs(markerOptions);
9780
9859
  for (key in markerOptions) {
9781
9860
  val = markerOptions[key];
@@ -9826,7 +9905,7 @@ Legend.prototype = {
9826
9905
  // destroy SVG elements
9827
9906
  each(['legendItem', 'legendLine', 'legendSymbol', 'legendGroup'], function (key) {
9828
9907
  if (item[key]) {
9829
- item[key].destroy();
9908
+ item[key] = item[key].destroy();
9830
9909
  }
9831
9910
  });
9832
9911
 
@@ -9885,7 +9964,8 @@ Legend.prototype = {
9885
9964
  var options = this.options,
9886
9965
  padding = this.padding,
9887
9966
  titleOptions = options.title,
9888
- titleHeight = 0;
9967
+ titleHeight = 0,
9968
+ bBox;
9889
9969
 
9890
9970
  if (titleOptions.text) {
9891
9971
  if (!this.title) {
@@ -9894,7 +9974,9 @@ Legend.prototype = {
9894
9974
  .css(titleOptions.style)
9895
9975
  .add(this.group);
9896
9976
  }
9897
- titleHeight = this.title.getBBox().height;
9977
+ bBox = this.title.getBBox();
9978
+ titleHeight = bBox.height;
9979
+ this.offsetWidth = bBox.width; // #1717
9898
9980
  this.contentGroup.attr({ translateY: titleHeight });
9899
9981
  }
9900
9982
  this.titleHeight = titleHeight;
@@ -9915,6 +9997,7 @@ Legend.prototype = {
9915
9997
  itemStyle = legend.itemStyle,
9916
9998
  itemHiddenStyle = legend.itemHiddenStyle,
9917
9999
  padding = legend.padding,
10000
+ itemDistance = horizontal ? pick(options.itemDistance, 8) : 0, // docs
9918
10001
  ltr = !options.rtl,
9919
10002
  itemHeight,
9920
10003
  widthOption = options.width,
@@ -10010,7 +10093,7 @@ Legend.prototype = {
10010
10093
  bBox = li.getBBox();
10011
10094
 
10012
10095
  itemWidth = item.legendItemWidth =
10013
- options.itemWidth || symbolWidth + symbolPadding + bBox.width + padding +
10096
+ options.itemWidth || symbolWidth + symbolPadding + bBox.width + itemDistance +
10014
10097
  (showCheckbox ? 20 : 0);
10015
10098
  legend.itemHeight = itemHeight = bBox.height;
10016
10099
 
@@ -10048,7 +10131,7 @@ Legend.prototype = {
10048
10131
 
10049
10132
  // the width of the widest item
10050
10133
  legend.offsetWidth = widthOption || mathMax(
10051
- horizontal ? legend.itemX - initialItemX : itemWidth,
10134
+ (horizontal ? legend.itemX - initialItemX - itemDistance : itemWidth) + padding,
10052
10135
  legend.offsetWidth
10053
10136
  );
10054
10137
  },
@@ -10385,7 +10468,7 @@ Chart.prototype = {
10385
10468
 
10386
10469
  var chartEvents = optionsChart.events;
10387
10470
 
10388
- this.runChartClick = chartEvents && !!chartEvents.click;
10471
+ //this.runChartClick = chartEvents && !!chartEvents.click;
10389
10472
  this.bounds = { h: {}, v: {} }; // Pixel data bounds for touch zoom
10390
10473
 
10391
10474
  this.callback = callback;
@@ -10520,7 +10603,8 @@ Chart.prototype = {
10520
10603
 
10521
10604
  /*jslint unused: false*/
10522
10605
  axis = new Axis(this, merge(options, {
10523
- index: this[key].length
10606
+ index: this[key].length,
10607
+ isX: isX
10524
10608
  }));
10525
10609
  /*jslint unused: true*/
10526
10610
 
@@ -10576,6 +10660,7 @@ Chart.prototype = {
10576
10660
  legend = chart.legend,
10577
10661
  redrawLegend = chart.isDirtyLegend,
10578
10662
  hasStackedSeries,
10663
+ hasDirtyStacks,
10579
10664
  isDirtyBox = chart.isDirtyBox, // todo: check if it has actually changed?
10580
10665
  seriesLength = series.length,
10581
10666
  i = seriesLength,
@@ -10590,15 +10675,23 @@ Chart.prototype = {
10590
10675
  chart.cloneRenderTo();
10591
10676
  }
10592
10677
 
10678
+ // Adjust title layout (reflow multiline text)
10679
+ chart.layOutTitles();
10680
+
10593
10681
  // link stacked series
10594
10682
  while (i--) {
10595
10683
  serie = series[i];
10596
- if (serie.isDirty && serie.options.stacking) {
10684
+
10685
+ if (serie.options.stacking) {
10597
10686
  hasStackedSeries = true;
10598
- break;
10687
+
10688
+ if (serie.isDirty) {
10689
+ hasDirtyStacks = true;
10690
+ break;
10691
+ }
10599
10692
  }
10600
10693
  }
10601
- if (hasStackedSeries) { // mark others as dirty
10694
+ if (hasDirtyStacks) { // mark others as dirty
10602
10695
  i = seriesLength;
10603
10696
  while (i--) {
10604
10697
  serie = series[i];
@@ -10625,6 +10718,11 @@ Chart.prototype = {
10625
10718
  chart.isDirtyLegend = false;
10626
10719
  }
10627
10720
 
10721
+ // reset stacks
10722
+ if (hasStackedSeries) {
10723
+ chart.getStacks();
10724
+ }
10725
+
10628
10726
 
10629
10727
  if (chart.hasCartesianSeries) {
10630
10728
  if (!chart.isResizing) {
@@ -10636,6 +10734,11 @@ Chart.prototype = {
10636
10734
  each(axes, function (axis) {
10637
10735
  axis.setScale();
10638
10736
  });
10737
+ } else {
10738
+ // build stacks
10739
+ each(axes, function (axis) {
10740
+ axis.buildStacks();
10741
+ });
10639
10742
  }
10640
10743
  chart.adjustTickAmounts();
10641
10744
  chart.getMargins();
@@ -10665,7 +10768,6 @@ Chart.prototype = {
10665
10768
  }
10666
10769
 
10667
10770
 
10668
-
10669
10771
  // redraw affected series
10670
10772
  each(series, function (serie) {
10671
10773
  if (serie.isDirty && serie.visible &&
@@ -10861,6 +10963,26 @@ Chart.prototype = {
10861
10963
  });
10862
10964
  },
10863
10965
 
10966
+ /**
10967
+ * Generate stacks for each series and calculate stacks total values
10968
+ */
10969
+ getStacks: function () {
10970
+ var chart = this;
10971
+
10972
+ // reset stacks for each yAxis
10973
+ each(chart.yAxis, function (axis) {
10974
+ if (axis.stacks && axis.hasVisibleSeries) {
10975
+ axis.oldStacks = axis.stacks;
10976
+ }
10977
+ });
10978
+
10979
+ each(chart.series, function (series) {
10980
+ if (series.options.stacking && (series.visible === true || chart.options.chart.ignoreHiddenSeries === false)) {
10981
+ series.stackKey = series.type + pick(series.options.stack, '');
10982
+ }
10983
+ });
10984
+ },
10985
+
10864
10986
  /**
10865
10987
  * Display the zoom button
10866
10988
  */
@@ -11013,11 +11135,49 @@ Chart.prototype = {
11013
11135
  zIndex: chartTitleOptions.zIndex || 4
11014
11136
  })
11015
11137
  .css(chartTitleOptions.style)
11016
- .add()
11017
- .align(chartTitleOptions, false, 'spacingBox');
11018
- }
11138
+ .add();
11139
+ }
11019
11140
  });
11141
+ chart.layOutTitles();
11142
+ },
11143
+
11144
+ /**
11145
+ * Lay out the chart titles and cache the full offset height for use in getMargins
11146
+ */
11147
+ layOutTitles: function () {
11148
+ var titleOffset = 0,
11149
+ title = this.title,
11150
+ subtitle = this.subtitle,
11151
+ options = this.options,
11152
+ titleOptions = options.title,
11153
+ subtitleOptions = options.subtitle,
11154
+ autoWidth = this.spacingBox.width - 44; // 44 makes room for default context button
11155
+
11156
+ if (title) {
11157
+ title
11158
+ .css({ width: (titleOptions.width || autoWidth) + PX })
11159
+ .align(extend({ y: 15 }, titleOptions), false, 'spacingBox');
11160
+
11161
+ if (!titleOptions.floating && !titleOptions.verticalAlign) {
11162
+ titleOffset = title.getBBox().height;
11163
+
11164
+ // Adjust for browser consistency + backwards compat after #776 fix
11165
+ if (titleOffset >= 18 && titleOffset <= 25) {
11166
+ titleOffset = 15;
11167
+ }
11168
+ }
11169
+ }
11170
+ if (subtitle) {
11171
+ subtitle
11172
+ .css({ width: (subtitleOptions.width || autoWidth) + PX })
11173
+ .align(extend({ y: titleOffset + titleOptions.margin }, subtitleOptions), false, 'spacingBox');
11174
+
11175
+ if (!subtitleOptions.floating && !subtitleOptions.verticalAlign) {
11176
+ titleOffset = mathCeil(titleOffset + subtitle.getBBox().height);
11177
+ }
11178
+ }
11020
11179
 
11180
+ this.titleOffset = titleOffset; // used in getMargins
11021
11181
  },
11022
11182
 
11023
11183
  /**
@@ -11056,7 +11216,7 @@ Chart.prototype = {
11056
11216
 
11057
11217
  // Set up the clone
11058
11218
  } else {
11059
- if (container) {
11219
+ if (container && container.parentNode === this.renderTo) {
11060
11220
  this.renderTo.removeChild(container); // do not clone this
11061
11221
  }
11062
11222
  this.renderToClone = clone = this.renderTo.cloneNode(0);
@@ -11176,30 +11336,23 @@ Chart.prototype = {
11176
11336
  optionsMarginLeft = chart.optionsMarginLeft,
11177
11337
  optionsMarginRight = chart.optionsMarginRight,
11178
11338
  optionsMarginBottom = chart.optionsMarginBottom,
11179
- chartTitleOptions = chart.options.title,
11180
- chartSubtitleOptions = chart.options.subtitle,
11181
11339
  legendOptions = chart.options.legend,
11182
11340
  legendMargin = pick(legendOptions.margin, 10),
11183
11341
  legendX = legendOptions.x,
11184
11342
  legendY = legendOptions.y,
11185
11343
  align = legendOptions.align,
11186
11344
  verticalAlign = legendOptions.verticalAlign,
11187
- titleOffset;
11345
+ titleOffset = chart.titleOffset;
11188
11346
 
11189
11347
  chart.resetMargins();
11190
11348
  axisOffset = chart.axisOffset;
11191
11349
 
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
- }
11350
+ // Adjust for title and subtitle
11351
+ if (titleOffset && !defined(optionsMarginTop)) {
11352
+ chart.plotTop = mathMax(chart.plotTop, titleOffset + chart.options.title.margin + spacingTop);
11201
11353
  }
11202
- // adjust for legend
11354
+
11355
+ // Adjust for legend
11203
11356
  if (legend.display && !legendOptions.floating) {
11204
11357
  if (align === 'right') { // horizontal alignment handled first
11205
11358
  if (!defined(optionsMarginRight)) {
@@ -11630,11 +11783,14 @@ Chart.prototype = {
11630
11783
  // Legend
11631
11784
  chart.legend = new Legend(chart, options.legend);
11632
11785
 
11786
+ chart.getStacks(); // render stacks
11787
+
11633
11788
  // Get margins by pre-rendering axes
11634
11789
  // set axes scales
11635
11790
  each(axes, function (axis) {
11636
11791
  axis.setScale();
11637
11792
  });
11793
+
11638
11794
  chart.getMargins();
11639
11795
 
11640
11796
  chart.maxTicks = null; // reset for second pass
@@ -12177,7 +12333,8 @@ Point.prototype = {
12177
12333
  graphic = point.graphic,
12178
12334
  i,
12179
12335
  data = series.data,
12180
- chart = series.chart;
12336
+ chart = series.chart,
12337
+ seriesOptions = series.options;
12181
12338
 
12182
12339
  redraw = pick(redraw, true);
12183
12340
 
@@ -12199,11 +12356,13 @@ Point.prototype = {
12199
12356
  series.xData[i] = point.x;
12200
12357
  series.yData[i] = series.toYData ? series.toYData(point) : point.y;
12201
12358
  series.zData[i] = point.z;
12202
- series.options.data[i] = point.options;
12359
+ seriesOptions.data[i] = point.options;
12203
12360
 
12204
12361
  // redraw
12205
- series.isDirty = true;
12206
- series.isDirtyData = true;
12362
+ series.isDirty = series.isDirtyData = chart.isDirtyBox = true;
12363
+ if (seriesOptions.legendType === 'point') { // #1831, #1885
12364
+ chart.legend.destroyItem(point);
12365
+ }
12207
12366
  if (redraw) {
12208
12367
  chart.redraw(animation);
12209
12368
  }
@@ -12612,6 +12771,7 @@ Series.prototype = {
12612
12771
  // register it
12613
12772
  series.segments = segments;
12614
12773
  },
12774
+
12615
12775
  /**
12616
12776
  * Set the series options by merging from the options tree
12617
12777
  * @param {Object} itemOptions
@@ -12714,7 +12874,7 @@ Series.prototype = {
12714
12874
  symbolWidth = legendOptions.symbolWidth,
12715
12875
  renderer = this.chart.renderer,
12716
12876
  legendItemGroup = this.legendGroup,
12717
- baseline = legend.baseline,
12877
+ verticalCenter = legend.baseline - mathRound(renderer.fontMetrics(legendOptions.itemStyle.fontSize).b * 0.3),
12718
12878
  attr;
12719
12879
 
12720
12880
  // Draw the line
@@ -12728,10 +12888,10 @@ Series.prototype = {
12728
12888
  this.legendLine = renderer.path([
12729
12889
  M,
12730
12890
  0,
12731
- baseline - 4,
12891
+ verticalCenter,
12732
12892
  L,
12733
12893
  symbolWidth,
12734
- baseline - 4
12894
+ verticalCenter
12735
12895
  ])
12736
12896
  .attr(attr)
12737
12897
  .add(legendItemGroup);
@@ -12743,11 +12903,12 @@ Series.prototype = {
12743
12903
  this.legendSymbol = legendSymbol = renderer.symbol(
12744
12904
  this.symbol,
12745
12905
  (symbolWidth / 2) - radius,
12746
- baseline - 4 - radius,
12906
+ verticalCenter - radius,
12747
12907
  2 * radius,
12748
12908
  2 * radius
12749
12909
  )
12750
12910
  .add(legendItemGroup);
12911
+ legendSymbol.isMarker = true;
12751
12912
  }
12752
12913
  },
12753
12914
 
@@ -12778,13 +12939,14 @@ Series.prototype = {
12778
12939
  setAnimation(animation, chart);
12779
12940
 
12780
12941
  // Make graph animate sideways
12781
- if (graph && shift) {
12782
- graph.shift = currentShift + 1;
12942
+ if (shift) {
12943
+ each([graph, area, series.graphNeg, series.areaNeg], function (shape) {
12944
+ if (shape) {
12945
+ shape.shift = currentShift + 1;
12946
+ }
12947
+ });
12783
12948
  }
12784
12949
  if (area) {
12785
- if (shift) { // #780
12786
- area.shift = currentShift + 1;
12787
- }
12788
12950
  area.isArea = true; // needed in animation, both with and without shift
12789
12951
  }
12790
12952
 
@@ -12821,12 +12983,12 @@ Series.prototype = {
12821
12983
  dataOptions.shift();
12822
12984
  }
12823
12985
  }
12824
- series.getAttribs();
12825
12986
 
12826
12987
  // redraw
12827
12988
  series.isDirty = true;
12828
12989
  series.isDirtyData = true;
12829
12990
  if (redraw) {
12991
+ series.getAttribs(); // #1937
12830
12992
  chart.redraw();
12831
12993
  }
12832
12994
  },
@@ -12857,7 +13019,7 @@ Series.prototype = {
12857
13019
  yData = [],
12858
13020
  zData = [],
12859
13021
  dataLength = data ? data.length : [],
12860
- turboThreshold = options.turboThreshold || 1000,
13022
+ turboThreshold = pick(options.turboThreshold, 1000), // docs: 0 to disable
12861
13023
  pt,
12862
13024
  pointArrayMap = series.pointArrayMap,
12863
13025
  valueCount = pointArrayMap && pointArrayMap.length,
@@ -12867,7 +13029,7 @@ Series.prototype = {
12867
13029
  // first value is tested, and we assume that all the rest are defined the same
12868
13030
  // way. Although the 'for' loops are similar, they are repeated inside each
12869
13031
  // if-else conditional for max performance.
12870
- if (dataLength > turboThreshold) {
13032
+ if (turboThreshold && dataLength > turboThreshold) {
12871
13033
 
12872
13034
  // find the first non-null point
12873
13035
  i = 0;
@@ -12913,18 +13075,12 @@ Series.prototype = {
12913
13075
  yData[i] = hasToYData ? series.toYData(pt) : pt.y;
12914
13076
  zData[i] = pt.z;
12915
13077
  if (names && pt.name) {
12916
- names[i] = pt.name;
13078
+ names[pt.x] = pt.name; // #2046
12917
13079
  }
12918
13080
  }
12919
13081
  }
12920
13082
  }
12921
13083
 
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
13084
  // Forgetting to cast strings to numbers is a common caveat when handling CSV or JSON
12929
13085
  if (isString(yData[0])) {
12930
13086
  error(14, true);
@@ -13002,8 +13158,8 @@ Series.prototype = {
13002
13158
  processedXData = series.xData, // copied during slice operation below
13003
13159
  processedYData = series.yData,
13004
13160
  dataLength = processedXData.length,
13161
+ croppedData,
13005
13162
  cropStart = 0,
13006
- cropEnd = dataLength,
13007
13163
  cropped,
13008
13164
  distance,
13009
13165
  closestPointRange,
@@ -13018,12 +13174,12 @@ Series.prototype = {
13018
13174
  if (isCartesian && !series.isDirty && !xAxis.isDirty && !series.yAxis.isDirty && !force) {
13019
13175
  return false;
13020
13176
  }
13177
+
13021
13178
 
13022
13179
  // optionally filter out points outside the plot area
13023
13180
  if (isCartesian && series.sorted && (!cropThreshold || dataLength > cropThreshold || series.forceCrop)) {
13024
- var extremes = xAxis.getExtremes(),
13025
- min = extremes.min,
13026
- max = extremes.max;
13181
+ var min = xAxis.min,
13182
+ max = xAxis.max;
13027
13183
 
13028
13184
  // it's outside current extremes
13029
13185
  if (processedXData[dataLength - 1] < min || processedXData[0] > max) {
@@ -13032,43 +13188,34 @@ Series.prototype = {
13032
13188
 
13033
13189
  // only crop if it's actually spilling out
13034
13190
  } 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);
13191
+ croppedData = this.cropData(series.xData, series.yData, min, max);
13192
+ processedXData = croppedData.xData;
13193
+ processedYData = croppedData.yData;
13194
+ cropStart = croppedData.start;
13053
13195
  cropped = true;
13054
13196
  }
13055
13197
  }
13056
13198
 
13057
13199
 
13058
13200
  // Find the closest distance between processed points
13059
- for (i = processedXData.length - 1; i > 0; i--) {
13201
+ for (i = processedXData.length - 1; i >= 0; i--) {
13060
13202
  distance = processedXData[i] - processedXData[i - 1];
13061
13203
  if (distance > 0 && (closestPointRange === UNDEFINED || distance < closestPointRange)) {
13062
13204
  closestPointRange = distance;
13205
+
13206
+ // Unsorted data is not supported by the line tooltip, as well as data grouping and
13207
+ // navigation in Stock charts (#725) and width calculation of columns (#1900)
13208
+ } else if (distance < 0 && series.requireSorting) {
13209
+ error(15);
13063
13210
  }
13064
13211
  }
13065
-
13212
+
13066
13213
  // Record the properties
13067
13214
  series.cropped = cropped; // undefined or true
13068
13215
  series.cropStart = cropStart;
13069
13216
  series.processedXData = processedXData;
13070
13217
  series.processedYData = processedYData;
13071
-
13218
+
13072
13219
  if (options.pointRange === null) { // null means auto, as for columns, candlesticks and OHLC
13073
13220
  series.pointRange = closestPointRange || 1;
13074
13221
  }
@@ -13076,6 +13223,41 @@ Series.prototype = {
13076
13223
 
13077
13224
  },
13078
13225
 
13226
+ /**
13227
+ * Iterate over xData and crop values between min and max. Returns object containing crop start/end
13228
+ * cropped xData with corresponding part of yData, dataMin and dataMax within the cropped range
13229
+ */
13230
+ cropData: function (xData, yData, min, max) {
13231
+ var dataLength = xData.length,
13232
+ cropStart = 0,
13233
+ cropEnd = dataLength,
13234
+ i;
13235
+
13236
+ // iterate up to find slice start
13237
+ for (i = 0; i < dataLength; i++) {
13238
+ if (xData[i] >= min) {
13239
+ cropStart = mathMax(0, i - 1);
13240
+ break;
13241
+ }
13242
+ }
13243
+
13244
+ // proceed to find slice end
13245
+ for (; i < dataLength; i++) {
13246
+ if (xData[i] > max) {
13247
+ cropEnd = i + 1;
13248
+ break;
13249
+ }
13250
+ }
13251
+
13252
+ return {
13253
+ xData: xData.slice(cropStart, cropEnd),
13254
+ yData: yData.slice(cropStart, cropEnd),
13255
+ start: cropStart,
13256
+ end: cropEnd
13257
+ };
13258
+ },
13259
+
13260
+
13079
13261
  /**
13080
13262
  * Generate the data point after the data has been processed by cropping away
13081
13263
  * unused points and optionally grouped in Highcharts Stock.
@@ -13136,6 +13318,154 @@ Series.prototype = {
13136
13318
  series.points = points;
13137
13319
  },
13138
13320
 
13321
+ /**
13322
+ * Adds series' points value to corresponding stack
13323
+ */
13324
+ setStackedPoints: function () {
13325
+ if (!this.options.stacking || (this.visible !== true && this.chart.options.chart.ignoreHiddenSeries !== false)) {
13326
+ return;
13327
+ }
13328
+
13329
+ var series = this,
13330
+ xData = series.processedXData,
13331
+ yData = series.processedYData,
13332
+ yDataLength = yData.length,
13333
+ seriesOptions = series.options,
13334
+ threshold = seriesOptions.threshold,
13335
+ stackOption = seriesOptions.stack,
13336
+ stacking = seriesOptions.stacking,
13337
+ stackKey = series.stackKey,
13338
+ negKey = '-' + stackKey,
13339
+ yAxis = series.yAxis,
13340
+ stacks = yAxis.stacks,
13341
+ oldStacks = yAxis.oldStacks,
13342
+ stacksMax = yAxis.stacksMax,
13343
+ isNegative,
13344
+ total,
13345
+ stack,
13346
+ key,
13347
+ i,
13348
+ x,
13349
+ y;
13350
+
13351
+ // loop over the non-null y values and read them into a local array
13352
+ for (i = 0; i < yDataLength; i++) {
13353
+ x = xData[i];
13354
+ y = yData[i];
13355
+
13356
+ // Read stacked values into a stack based on the x value,
13357
+ // the sign of y and the stack key. Stacking is also handled for null values (#739)
13358
+ isNegative = y < threshold;
13359
+ key = isNegative ? negKey : stackKey;
13360
+
13361
+ // Set default stacksMax value for this stack
13362
+ if (!stacksMax[key]) {
13363
+ stacksMax[key] = y;
13364
+ }
13365
+
13366
+ // Create empty object for this stack if it doesn't exist yet
13367
+ if (!stacks[key]) {
13368
+ stacks[key] = {};
13369
+ }
13370
+
13371
+ // Initialize StackItem for this x
13372
+ if (oldStacks[key] && oldStacks[key][x]) {
13373
+ stacks[key][x] = oldStacks[key][x];
13374
+ stacks[key][x].total = null;
13375
+ } else if (!stacks[key][x]) {
13376
+ stacks[key][x] = new StackItem(yAxis, yAxis.options.stackLabels, isNegative, x, stackOption, stacking);
13377
+ }
13378
+
13379
+ // If the StackItem doesn't exist, create it first
13380
+ stack = stacks[key][x];
13381
+ total = stack.total;
13382
+
13383
+
13384
+ // add value to the stack total
13385
+ stack.addValue(y);
13386
+
13387
+ stack.cacheExtremes(series, [total, total + y]);
13388
+
13389
+
13390
+ if (stack.total > stacksMax[key] && !isNegative) {
13391
+ stacksMax[key] = stack.total;
13392
+ } else if (stack.total < stacksMax[key] && isNegative) {
13393
+ stacksMax[key] = stack.total;
13394
+ }
13395
+ }
13396
+
13397
+ // reset old stacks
13398
+ yAxis.oldStacks = {};
13399
+ },
13400
+
13401
+ /**
13402
+ * Calculate x and y extremes for visible data
13403
+ */
13404
+ getExtremes: function () {
13405
+ var xAxis = this.xAxis,
13406
+ yAxis = this.yAxis,
13407
+ stackKey = this.stackKey,
13408
+ options = this.options,
13409
+ threshold = options.threshold,
13410
+ xData = this.processedXData,
13411
+ yData = this.processedYData,
13412
+ yDataLength = yData.length,
13413
+ activeYData = [],
13414
+ activeCounter = 0,
13415
+ xMin = xAxis.min,
13416
+ xMax = xAxis.max,
13417
+ validValue,
13418
+ withinRange,
13419
+ dataMin,
13420
+ dataMax,
13421
+ x,
13422
+ y,
13423
+ i,
13424
+ j;
13425
+
13426
+ // For stacked series, get the value from the stack
13427
+ if (options.stacking) {
13428
+ dataMin = yAxis.stacksMax['-' + stackKey] || threshold;
13429
+ dataMax = yAxis.stacksMax[stackKey] || threshold;
13430
+ }
13431
+
13432
+ // If not stacking or threshold is null, iterate over values that are within the visible range
13433
+ if (!defined(dataMin) || !defined(dataMax)) {
13434
+
13435
+ for (i = 0; i < yDataLength; i++) {
13436
+
13437
+ x = xData[i];
13438
+ y = yData[i];
13439
+
13440
+ // For points within the visible range, including the first point outside the
13441
+ // visible range, consider y extremes
13442
+ validValue = y !== null && y !== UNDEFINED && (!yAxis.isLog || (y.length || y > 0));
13443
+ withinRange = this.getExtremesFromAll || this.cropped || ((xData[i + 1] || x) >= xMin &&
13444
+ (xData[i - 1] || x) <= xMax);
13445
+
13446
+ if (validValue && withinRange) {
13447
+
13448
+ j = y.length;
13449
+ if (j) { // array, like ohlc or range data
13450
+ while (j--) {
13451
+ if (y[j] !== null) {
13452
+ activeYData[activeCounter++] = y[j];
13453
+ }
13454
+ }
13455
+ } else {
13456
+ activeYData[activeCounter++] = y;
13457
+ }
13458
+ }
13459
+ }
13460
+ dataMin = pick(dataMin, arrayMin(activeYData));
13461
+ dataMax = pick(dataMax, arrayMax(activeYData));
13462
+ }
13463
+
13464
+ // Set
13465
+ this.dataMin = dataMin;
13466
+ this.dataMax = dataMax;
13467
+ },
13468
+
13139
13469
  /**
13140
13470
  * Translate data points from raw data values to chart specific positioning data
13141
13471
  * needed later in drawPoints, drawGraph and drawTracker.
@@ -13154,27 +13484,11 @@ Series.prototype = {
13154
13484
  points = series.points,
13155
13485
  dataLength = points.length,
13156
13486
  hasModifyValue = !!series.modifyValue,
13157
- isBottomSeries,
13158
- allStackSeries,
13159
13487
  i,
13160
- placeBetween = options.pointPlacement === 'between',
13488
+ pointPlacement = options.pointPlacement, // docs: accept numbers
13489
+ dynamicallyPlaced = pointPlacement === 'between' || isNumber(pointPlacement),
13161
13490
  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
- }
13491
+
13178
13492
 
13179
13493
  // Translate each point
13180
13494
  for (i = 0; i < dataLength; i++) {
@@ -13192,16 +13506,18 @@ Series.prototype = {
13192
13506
  }
13193
13507
 
13194
13508
  // Get the plotX translation
13195
- point.plotX = xAxis.translate(xValue, 0, 0, 0, 1, placeBetween); // Math.round fixes #591
13509
+ point.plotX = xAxis.translate(xValue, 0, 0, 0, 1, pointPlacement); // Math.round fixes #591
13196
13510
 
13197
13511
  // Calculate the bottom y value for stacked series
13198
13512
  if (stacking && series.visible && stack && stack[xValue]) {
13513
+
13514
+
13199
13515
  pointStack = stack[xValue];
13200
13516
  pointStackTotal = pointStack.total;
13201
13517
  pointStack.cum = yBottom = pointStack.cum - yValue; // start from top
13202
13518
  yValue = yBottom + yValue;
13203
13519
 
13204
- if (isBottomSeries) {
13520
+ if (pointStack.cum === 0) {
13205
13521
  yBottom = pick(threshold, yAxis.min);
13206
13522
  }
13207
13523
 
@@ -13217,6 +13533,10 @@ Series.prototype = {
13217
13533
  point.percentage = pointStackTotal ? point.y * 100 / pointStackTotal : 0;
13218
13534
  point.total = point.stackTotal = pointStackTotal;
13219
13535
  point.stackY = yValue;
13536
+
13537
+ // Place the stack label
13538
+ pointStack.setOffset(series.pointXOffset || 0, series.barW || 0);
13539
+
13220
13540
  }
13221
13541
 
13222
13542
  // Set translated yBottom or remove it
@@ -13235,7 +13555,7 @@ Series.prototype = {
13235
13555
  UNDEFINED;
13236
13556
 
13237
13557
  // Set client related positions for mouse tracking
13238
- point.clientX = placeBetween ? xAxis.translate(xValue, 0, 0, 0, 1) : point.plotX; // #1514
13558
+ point.clientX = dynamicallyPlaced ? xAxis.translate(xValue, 0, 0, 0, 1) : point.plotX; // #1514
13239
13559
 
13240
13560
  point.negative = point.y < (threshold || 0);
13241
13561
 
@@ -13261,6 +13581,7 @@ Series.prototype = {
13261
13581
  xAxis = series.xAxis,
13262
13582
  axisLength = xAxis ? (xAxis.tooltipLen || xAxis.len) : series.chart.plotSizeX, // tooltipLen and tooltipPosName used in polar
13263
13583
  point,
13584
+ nextPoint,
13264
13585
  i,
13265
13586
  tooltipPoints = []; // a lookup array for each pixel in the x dimension
13266
13587
 
@@ -13284,15 +13605,24 @@ Series.prototype = {
13284
13605
  points = points.reverse();
13285
13606
  }
13286
13607
 
13608
+ // Polar needs additional shaping
13609
+ if (series.orderTooltipPoints) {
13610
+ series.orderTooltipPoints(points);
13611
+ }
13612
+
13287
13613
  // Assign each pixel position to the nearest point
13288
13614
  pointsLength = points.length;
13289
13615
  for (i = 0; i < pointsLength; i++) {
13290
13616
  point = points[i];
13617
+ nextPoint = points[i + 1];
13618
+
13291
13619
  // Set this range's low to the last range's high plus one
13292
13620
  low = points[i - 1] ? high + 1 : 0;
13293
13621
  // Now find the new high
13294
13622
  high = points[i + 1] ?
13295
- mathMax(0, mathFloor((point.clientX + (points[i + 1] ? points[i + 1].clientX : axisLength)) / 2)) :
13623
+ mathMin(mathMax(0, mathFloor( // #2070
13624
+ (point.clientX + (nextPoint ? (nextPoint.wrappedClientX || nextPoint.clientX) : axisLength)) / 2
13625
+ )), axisLength) :
13296
13626
  axisLength;
13297
13627
 
13298
13628
  while (low >= 0 && low <= high) {
@@ -13512,7 +13842,7 @@ Series.prototype = {
13512
13842
  i = points.length;
13513
13843
  while (i--) {
13514
13844
  point = points[i];
13515
- plotX = point.plotX;
13845
+ plotX = mathFloor(point.plotX); // #1843
13516
13846
  plotY = point.plotY;
13517
13847
  graphic = point.graphic;
13518
13848
  pointMarkerOptions = point.marker || {};
@@ -13738,7 +14068,7 @@ Series.prototype = {
13738
14068
  animation: false,
13739
14069
  index: this.index,
13740
14070
  pointStart: this.xData[0] // when updating after addPoint
13741
- }, newOptions);
14071
+ }, { data: this.options.data }, newOptions);
13742
14072
 
13743
14073
  // Destroy the series and reinsert methods from the type prototype
13744
14074
  this.remove(false);
@@ -13879,12 +14209,12 @@ Series.prototype = {
13879
14209
  // in the point options, or if they fall outside the plot area.
13880
14210
  } else if (enabled) {
13881
14211
 
13882
- rotation = options.rotation;
13883
-
13884
14212
  // Create individual options structure that can be extended without
13885
14213
  // affecting others
13886
14214
  options = merge(generalOptions, pointOptions);
13887
-
14215
+
14216
+ rotation = options.rotation;
14217
+
13888
14218
  // Get the string
13889
14219
  labelConfig = point.getLabelConfig();
13890
14220
  str = options.format ?
@@ -13980,7 +14310,7 @@ Series.prototype = {
13980
14310
  width: bBox.width,
13981
14311
  height: bBox.height
13982
14312
  });
13983
-
14313
+
13984
14314
  // Allow a hook for changing alignment in the last moment, then do the alignment
13985
14315
  if (options.rotation) { // Fancy box alignment isn't supported for rotated text
13986
14316
  alignAttr = {
@@ -13996,7 +14326,8 @@ Series.prototype = {
13996
14326
 
13997
14327
  // Show or hide based on the final aligned position
13998
14328
  dataLabel.attr({
13999
- visibility: options.crop === false || /*chart.isInsidePlot(alignAttr.x, alignAttr.y) || */chart.isInsidePlot(plotX, plotY, inverted) ?
14329
+ visibility: options.crop === false ||
14330
+ (chart.isInsidePlot(alignAttr.x, alignAttr.y) && chart.isInsidePlot(alignAttr.x + bBox.width, alignAttr.y + bBox.height)) ?
14000
14331
  (chart.renderer.isSVG ? 'inherit' : VISIBLE) :
14001
14332
  HIDDEN
14002
14333
  });
@@ -14143,7 +14474,7 @@ Series.prototype = {
14143
14474
  var options = this.options,
14144
14475
  chart = this.chart,
14145
14476
  renderer = chart.renderer,
14146
- negativeColor = options.negativeColor,
14477
+ negativeColor = options.negativeColor || options.negativeFillColor,
14147
14478
  translatedThreshold,
14148
14479
  posAttr,
14149
14480
  negAttr,
@@ -14154,11 +14485,12 @@ Series.prototype = {
14154
14485
  chartWidth = chart.chartWidth,
14155
14486
  chartHeight = chart.chartHeight,
14156
14487
  chartSizeMax = mathMax(chartWidth, chartHeight),
14488
+ yAxis = this.yAxis,
14157
14489
  above,
14158
14490
  below;
14159
14491
 
14160
14492
  if (negativeColor && (graph || area)) {
14161
- translatedThreshold = mathCeil(this.yAxis.len - this.yAxis.translate(options.threshold || 0));
14493
+ translatedThreshold = mathRound(yAxis.toPixels(options.threshold || 0, true));
14162
14494
  above = {
14163
14495
  x: 0,
14164
14496
  y: 0,
@@ -14169,25 +14501,29 @@ Series.prototype = {
14169
14501
  x: 0,
14170
14502
  y: translatedThreshold,
14171
14503
  width: chartSizeMax,
14172
- height: chartSizeMax - translatedThreshold
14504
+ height: chartSizeMax
14173
14505
  };
14174
14506
 
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
- };
14507
+ if (chart.inverted) {
14508
+
14509
+ above.height = below.y = chart.plotWidth - translatedThreshold;
14510
+ if (renderer.isVML) {
14511
+ above = {
14512
+ x: chart.plotWidth - translatedThreshold - chart.plotLeft,
14513
+ y: 0,
14514
+ width: chartWidth,
14515
+ height: chartHeight
14516
+ };
14517
+ below = {
14518
+ x: translatedThreshold + chart.plotLeft - chartWidth,
14519
+ y: 0,
14520
+ width: chart.plotLeft + translatedThreshold,
14521
+ height: chartWidth
14522
+ };
14523
+ }
14188
14524
  }
14189
14525
 
14190
- if (this.yAxis.reversed) {
14526
+ if (yAxis.reversed) {
14191
14527
  posAttr = below;
14192
14528
  negAttr = above;
14193
14529
  } else {
@@ -14203,7 +14539,7 @@ Series.prototype = {
14203
14539
  this.posClip = posClip = renderer.clipRect(posAttr);
14204
14540
  this.negClip = negClip = renderer.clipRect(negAttr);
14205
14541
 
14206
- if (graph) {
14542
+ if (graph && this.graphNeg) {
14207
14543
  graph.clip(posClip);
14208
14544
  this.graphNeg.clip(negClip);
14209
14545
  }
@@ -14260,14 +14596,11 @@ Series.prototype = {
14260
14596
  */
14261
14597
  plotGroup: function (prop, name, visibility, zIndex, parent) {
14262
14598
  var group = this[prop],
14263
- isNew = !group,
14264
- chart = this.chart,
14265
- xAxis = this.xAxis,
14266
- yAxis = this.yAxis;
14599
+ isNew = !group;
14267
14600
 
14268
14601
  // Generate it on first call
14269
14602
  if (isNew) {
14270
- this[prop] = group = chart.renderer.g(name)
14603
+ this[prop] = group = this.chart.renderer.g(name)
14271
14604
  .attr({
14272
14605
  visibility: visibility,
14273
14606
  zIndex: zIndex || 0.1 // IE8 needs this
@@ -14275,14 +14608,20 @@ Series.prototype = {
14275
14608
  .add(parent);
14276
14609
  }
14277
14610
  // 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,
14611
+ group[isNew ? 'attr' : 'animate'](this.getPlotBox());
14612
+ return group;
14613
+ },
14614
+
14615
+ /**
14616
+ * Get the translation and scale for the plot area of this series
14617
+ */
14618
+ getPlotBox: function () {
14619
+ return {
14620
+ translateX: this.xAxis ? this.xAxis.left : this.chart.plotLeft,
14621
+ translateY: this.yAxis ? this.yAxis.top : this.chart.plotTop,
14281
14622
  scaleX: 1, // #1623
14282
14623
  scaleY: 1
14283
- });
14284
- return group;
14285
-
14624
+ };
14286
14625
  },
14287
14626
 
14288
14627
  /**
@@ -14661,6 +15000,7 @@ var AreaSeries = extendClass(Series, {
14661
15000
  plotX,
14662
15001
  plotY,
14663
15002
  points = this.points,
15003
+ val,
14664
15004
  i,
14665
15005
  x;
14666
15006
 
@@ -14688,7 +15028,8 @@ var AreaSeries = extendClass(Series, {
14688
15028
  // correctly.
14689
15029
  } else {
14690
15030
  plotX = xAxis.translate(x);
14691
- plotY = yAxis.toPixels(stack[x].cum, true);
15031
+ val = stack[x].percent ? (stack[x].total ? stack[x].cum * 100 / stack[x].total : 0) : stack[x].cum; // #1991
15032
+ plotY = yAxis.toPixels(val, true);
14692
15033
  segment.push({
14693
15034
  y: null,
14694
15035
  plotX: plotX,
@@ -14784,10 +15125,11 @@ var AreaSeries = extendClass(Series, {
14784
15125
  areaPath = this.areaPath,
14785
15126
  options = this.options,
14786
15127
  negativeColor = options.negativeColor,
15128
+ negativeFillColor = options.negativeFillColor,
14787
15129
  props = [['area', this.color, options.fillColor]]; // area name, main color, fill color
14788
15130
 
14789
- if (negativeColor) {
14790
- props.push(['areaNeg', options.negativeColor, options.negativeFillColor]);
15131
+ if (negativeColor || negativeFillColor) {
15132
+ props.push(['areaNeg', negativeColor, negativeFillColor]);
14791
15133
  }
14792
15134
 
14793
15135
  each(props, function (prop) {
@@ -14803,7 +15145,7 @@ var AreaSeries = extendClass(Series, {
14803
15145
  .attr({
14804
15146
  fill: pick(
14805
15147
  prop[2],
14806
- Color(prop[1]).setOpacity(options.fillOpacity || 0.75).get()
15148
+ Color(prop[1]).setOpacity(pick(options.fillOpacity, 0.75)).get()
14807
15149
  ),
14808
15150
  zIndex: 0 // #1069
14809
15151
  }).add(series.group);
@@ -14973,7 +15315,8 @@ var areaProto = AreaSeries.prototype,
14973
15315
  // Mix in methods from the area series
14974
15316
  getSegmentPath: areaProto.getSegmentPath,
14975
15317
  closeSegment: areaProto.closeSegment,
14976
- drawGraph: areaProto.drawGraph
15318
+ drawGraph: areaProto.drawGraph,
15319
+ drawLegendSymbol: areaProto.drawLegendSymbol
14977
15320
  });
14978
15321
  seriesTypes.areaspline = AreaSplineSeries;
14979
15322
 
@@ -15019,7 +15362,6 @@ defaultPlotOptions.column = merge(defaultSeriesOptions, {
15019
15362
  var ColumnSeries = extendClass(Series, {
15020
15363
  type: 'column',
15021
15364
  tooltipOutsidePlot: true,
15022
- requireSorting: false,
15023
15365
  pointAttrToOptions: { // mapping between SVG attributes and the corresponding options
15024
15366
  stroke: 'borderColor',
15025
15367
  'stroke-width': 'borderWidth',
@@ -15055,7 +15397,6 @@ var ColumnSeries = extendClass(Series, {
15055
15397
  getColumnMetrics: function () {
15056
15398
 
15057
15399
  var series = this,
15058
- chart = series.chart,
15059
15400
  options = series.options,
15060
15401
  xAxis = this.xAxis,
15061
15402
  reversedXAxis = xAxis.reversed,
@@ -15070,7 +15411,7 @@ var ColumnSeries = extendClass(Series, {
15070
15411
  if (options.grouping === false) {
15071
15412
  columnCount = 1;
15072
15413
  } else {
15073
- each(chart.series, function (otherSeries) {
15414
+ each(series.yAxis.series, function (otherSeries) { // use Y axes separately, #642
15074
15415
  var otherOptions = otherSeries.options;
15075
15416
  if (otherSeries.type === series.type && otherSeries.visible &&
15076
15417
  series.options.group === otherOptions.group) { // used in Stock charts navigator series
@@ -15121,7 +15462,6 @@ var ColumnSeries = extendClass(Series, {
15121
15462
  var series = this,
15122
15463
  chart = series.chart,
15123
15464
  options = series.options,
15124
- stacking = options.stacking,
15125
15465
  borderWidth = options.borderWidth,
15126
15466
  yAxis = series.yAxis,
15127
15467
  threshold = options.threshold,
@@ -15129,8 +15469,8 @@ var ColumnSeries = extendClass(Series, {
15129
15469
  minPointLength = pick(options.minPointLength, 5),
15130
15470
  metrics = series.getColumnMetrics(),
15131
15471
  pointWidth = metrics.width,
15132
- barW = mathCeil(mathMax(pointWidth, 1 + 2 * borderWidth)), // rounded and postprocessed for border width
15133
- pointXOffset = metrics.offset;
15472
+ barW = series.barW = mathCeil(mathMax(pointWidth, 1 + 2 * borderWidth)), // rounded and postprocessed for border width
15473
+ pointXOffset = series.pointXOffset = metrics.offset;
15134
15474
 
15135
15475
  Series.prototype.translate.apply(series);
15136
15476
 
@@ -15141,22 +15481,16 @@ var ColumnSeries = extendClass(Series, {
15141
15481
  barX = point.plotX + pointXOffset,
15142
15482
  barY = mathCeil(mathMin(plotY, yBottom)),
15143
15483
  barH = mathCeil(mathMax(plotY, yBottom) - barY),
15144
- stack = yAxis.stacks[(point.y < 0 ? '-' : '') + series.stackKey],
15145
15484
  shapeArgs;
15146
15485
 
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
15486
  // handle options.minPointLength
15153
15487
  if (mathAbs(barH) < minPointLength) {
15154
15488
  if (minPointLength) {
15155
15489
  barH = minPointLength;
15156
15490
  barY =
15157
- mathAbs(barY - translatedThreshold) > minPointLength ? // stacked
15491
+ mathRound(mathAbs(barY - translatedThreshold) > minPointLength ? // stacked
15158
15492
  yBottom - minPointLength : // keep position
15159
- translatedThreshold - (yAxis.translate(point.y, 0, 1, 0, 1) <= translatedThreshold ? minPointLength : 0); // use exact yAxis.translation (#1485)
15493
+ translatedThreshold - (yAxis.translate(point.y, 0, 1, 0, 1) <= translatedThreshold ? minPointLength : 0)); // use exact yAxis.translation (#1485)
15160
15494
  }
15161
15495
  }
15162
15496
 
@@ -15232,20 +15566,22 @@ var ColumnSeries = extendClass(Series, {
15232
15566
  */
15233
15567
  drawTracker: function () {
15234
15568
  var series = this,
15235
- pointer = series.chart.pointer,
15569
+ chart = series.chart,
15570
+ pointer = chart.pointer,
15236
15571
  cursor = series.options.cursor,
15237
15572
  css = cursor && { cursor: cursor },
15238
15573
  onMouseOver = function (e) {
15239
15574
  var target = e.target,
15240
15575
  point;
15241
15576
 
15242
- series.onMouseOver();
15243
-
15577
+ if (chart.hoverSeries !== series) {
15578
+ series.onMouseOver();
15579
+ }
15244
15580
  while (target && !point) {
15245
15581
  point = target.point;
15246
15582
  target = target.parentNode;
15247
15583
  }
15248
- if (point !== UNDEFINED) { // undefined on graph in scatterchart
15584
+ if (point !== UNDEFINED && point !== chart.hoverPoint) { // undefined on graph in scatterchart
15249
15585
  point.onMouseOver(e);
15250
15586
  }
15251
15587
  };
@@ -15495,8 +15831,8 @@ var PiePoint = extendClass(Point, {
15495
15831
  });
15496
15832
 
15497
15833
  // add event listener for select
15498
- toggleSlice = function () {
15499
- point.slice();
15834
+ toggleSlice = function (e) {
15835
+ point.slice(e.type === 'select');
15500
15836
  };
15501
15837
  addEvent(point, 'select', toggleSlice);
15502
15838
  addEvent(point, 'unselect', toggleSlice);
@@ -15641,6 +15977,39 @@ var PieSeries = {
15641
15977
  this.chart.redraw();
15642
15978
  }
15643
15979
  },
15980
+
15981
+ /**
15982
+ * Extend the generatePoints method by adding total and percentage properties to each point
15983
+ */
15984
+ generatePoints: function () {
15985
+ var i,
15986
+ total = 0,
15987
+ points,
15988
+ len,
15989
+ point,
15990
+ ignoreHiddenPoint = this.options.ignoreHiddenPoint;
15991
+
15992
+ Series.prototype.generatePoints.call(this);
15993
+
15994
+ // Populate local vars
15995
+ points = this.points;
15996
+ len = points.length;
15997
+
15998
+ // Get the total sum
15999
+ for (i = 0; i < len; i++) {
16000
+ point = points[i];
16001
+ total += (ignoreHiddenPoint && !point.visible) ? 0 : point.y;
16002
+ }
16003
+ this.total = total;
16004
+
16005
+ // Set each point's properties
16006
+ for (i = 0; i < len; i++) {
16007
+ point = points[i];
16008
+ point.percentage = (point.y / total) * 100;
16009
+ point.total = total;
16010
+ }
16011
+
16012
+ },
15644
16013
 
15645
16014
  /**
15646
16015
  * Get the center of the pie based on the size and center options relative to the
@@ -15679,8 +16048,7 @@ var PieSeries = {
15679
16048
  translate: function (positions) {
15680
16049
  this.generatePoints();
15681
16050
 
15682
- var total = 0,
15683
- series = this,
16051
+ var series = this,
15684
16052
  cumulative = 0,
15685
16053
  precision = 1000, // issue #172
15686
16054
  options = series.options,
@@ -15692,7 +16060,6 @@ var PieSeries = {
15692
16060
  startAngleRad = series.startAngleRad = mathPI / 180 * ((options.startAngle || 0) % 360 - 90),
15693
16061
  points = series.points,
15694
16062
  circ = 2 * mathPI,
15695
- fraction,
15696
16063
  radiusX, // the x component of the radius vector for a given point
15697
16064
  radiusY,
15698
16065
  labelDistance = options.dataLabels.distance,
@@ -15718,22 +16085,15 @@ var PieSeries = {
15718
16085
  (mathCos(angle) * (positions[2] / 2 + labelDistance));
15719
16086
  };
15720
16087
 
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
16088
  // Calculate the geometry for each point
15728
16089
  for (i = 0; i < len; i++) {
15729
16090
 
15730
16091
  point = points[i];
15731
16092
 
15732
16093
  // set start and end angle
15733
- fraction = total ? point.y / total : 0;
15734
16094
  start = mathRound((startAngleRad + (cumulative * circ)) * precision) / precision;
15735
16095
  if (!ignoreHiddenPoint || point.visible) {
15736
- cumulative += fraction;
16096
+ cumulative += point.percentage / 100;
15737
16097
  }
15738
16098
  end = mathRound((startAngleRad + (cumulative * circ)) * precision) / precision;
15739
16099
 
@@ -15783,10 +16143,6 @@ var PieSeries = {
15783
16143
  point.half ? 'right' : 'left', // alignment
15784
16144
  angle // center angle
15785
16145
  ];
15786
-
15787
- // API properties
15788
- point.percentage = fraction * 100;
15789
- point.total = total;
15790
16146
 
15791
16147
  }
15792
16148
 
@@ -15909,7 +16265,7 @@ var PieSeries = {
15909
16265
  };
15910
16266
 
15911
16267
  // get out if not enabled
15912
- if (!options.enabled && !series._hasPointLabels) {
16268
+ if (!series.visible || (!options.enabled && !series._hasPointLabels)) {
15913
16269
  return;
15914
16270
  }
15915
16271