highcharts-rails 3.0.2 → 3.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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