highcharts-rails 2.3.3.1 → 2.3.5

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.
data/CHANGELOG.markdown CHANGED
@@ -1,3 +1,7 @@
1
+ ## 2.3.5 (2013-01-146)
2
+
3
+ * Updated Highcharts to 2.3.5
4
+
1
5
  ## 2.3.3.1 (2012-12-14)
2
6
 
3
7
  * Relaxed railties dependency to prepare for Rails 4
@@ -1,3 +1,3 @@
1
1
  module Highcharts
2
- VERSION = "2.3.3.1"
2
+ VERSION = "2.3.5"
3
3
  end
@@ -2,9 +2,9 @@
2
2
  // @compilation_level SIMPLE_OPTIMIZATIONS
3
3
 
4
4
  /**
5
- * @license Highcharts JS v2.3.3 (2012-10-04)
5
+ * @license Highcharts JS v2.3.5 (2012-12-19)
6
6
  *
7
- * (c) 2009-2011 Torstein Hønsi
7
+ * (c) 2009-2012 Torstein Hønsi
8
8
  *
9
9
  * License: www.highcharts.com/license
10
10
  */
@@ -37,6 +37,7 @@ var UNDEFINED,
37
37
  docMode8 = doc.documentMode === 8,
38
38
  isWebKit = /AppleWebKit/.test(userAgent),
39
39
  isFirefox = /Firefox/.test(userAgent),
40
+ isTouchDevice = /(Mobile|Android|Windows Phone)/.test(userAgent),
40
41
  SVG_NS = 'http://www.w3.org/2000/svg',
41
42
  hasSVG = !!doc.createElementNS && !!doc.createElementNS(SVG_NS, 'svg').createSVGRect,
42
43
  hasBidiBug = isFirefox && parseInt(userAgent.split('Firefox/')[1], 10) < 4, // issue #38
@@ -52,6 +53,7 @@ var UNDEFINED,
52
53
  pathAnim,
53
54
  timeUnits,
54
55
  noop = function () {},
56
+ charts = [],
55
57
 
56
58
  // some constants for frequently used strings
57
59
  DIV = 'div',
@@ -70,12 +72,13 @@ var UNDEFINED,
70
72
  * IE7: 0.002
71
73
  * IE8: 0.002
72
74
  * IE9: 0.00000000001 (unlimited)
75
+ * IE10: 0.0001 (exporting only)
73
76
  * FF: 0.00000000001 (unlimited)
74
77
  * Chrome: 0.000001
75
78
  * Safari: 0.000001
76
79
  * Opera: 0.00000000001 (unlimited)
77
80
  */
78
- TRACKER_FILL = 'rgba(192,192,192,' + (hasSVG ? 0.000001 : 0.002) + ')', // invisible but clickable
81
+ TRACKER_FILL = 'rgba(192,192,192,' + (hasSVG ? 0.0001 : 0.002) + ')', // invisible but clickable
79
82
  //TRACKER_FILL = 'rgba(192,192,192,0.5)',
80
83
  NORMAL_STATE = '',
81
84
  HOVER_STATE = 'hover',
@@ -461,7 +464,9 @@ dateFormat = function (format, timestamp, capitalize) {
461
464
 
462
465
  // do the replaces
463
466
  for (key in replacements) {
464
- format = format.replace('%' + key, replacements[key]);
467
+ while (format.indexOf('%' + key) !== -1) { // regex would do it in one line, but this is faster
468
+ format = format.replace('%' + key, replacements[key]);
469
+ }
465
470
  }
466
471
 
467
472
  // Optionally capitalize the string and return
@@ -610,91 +615,91 @@ function getTimeTicks(normalizedInterval, min, max, startOfWeek) {
610
615
  interval = normalizedInterval.unitRange,
611
616
  count = normalizedInterval.count;
612
617
 
618
+ if (defined(min)) { // #1300
619
+ if (interval >= timeUnits[SECOND]) { // second
620
+ minDate.setMilliseconds(0);
621
+ minDate.setSeconds(interval >= timeUnits[MINUTE] ? 0 :
622
+ count * mathFloor(minDate.getSeconds() / count));
623
+ }
613
624
 
614
-
615
- if (interval >= timeUnits[SECOND]) { // second
616
- minDate.setMilliseconds(0);
617
- minDate.setSeconds(interval >= timeUnits[MINUTE] ? 0 :
618
- count * mathFloor(minDate.getSeconds() / count));
619
- }
620
-
621
- if (interval >= timeUnits[MINUTE]) { // minute
622
- minDate[setMinutes](interval >= timeUnits[HOUR] ? 0 :
623
- count * mathFloor(minDate[getMinutes]() / count));
624
- }
625
-
626
- if (interval >= timeUnits[HOUR]) { // hour
627
- minDate[setHours](interval >= timeUnits[DAY] ? 0 :
628
- count * mathFloor(minDate[getHours]() / count));
629
- }
630
-
631
- if (interval >= timeUnits[DAY]) { // day
632
- minDate[setDate](interval >= timeUnits[MONTH] ? 1 :
633
- count * mathFloor(minDate[getDate]() / count));
634
- }
635
-
636
- if (interval >= timeUnits[MONTH]) { // month
637
- minDate[setMonth](interval >= timeUnits[YEAR] ? 0 :
638
- count * mathFloor(minDate[getMonth]() / count));
625
+ if (interval >= timeUnits[MINUTE]) { // minute
626
+ minDate[setMinutes](interval >= timeUnits[HOUR] ? 0 :
627
+ count * mathFloor(minDate[getMinutes]() / count));
628
+ }
629
+
630
+ if (interval >= timeUnits[HOUR]) { // hour
631
+ minDate[setHours](interval >= timeUnits[DAY] ? 0 :
632
+ count * mathFloor(minDate[getHours]() / count));
633
+ }
634
+
635
+ if (interval >= timeUnits[DAY]) { // day
636
+ minDate[setDate](interval >= timeUnits[MONTH] ? 1 :
637
+ count * mathFloor(minDate[getDate]() / count));
638
+ }
639
+
640
+ if (interval >= timeUnits[MONTH]) { // month
641
+ minDate[setMonth](interval >= timeUnits[YEAR] ? 0 :
642
+ count * mathFloor(minDate[getMonth]() / count));
643
+ minYear = minDate[getFullYear]();
644
+ }
645
+
646
+ if (interval >= timeUnits[YEAR]) { // year
647
+ minYear -= minYear % count;
648
+ minDate[setFullYear](minYear);
649
+ }
650
+
651
+ // week is a special case that runs outside the hierarchy
652
+ if (interval === timeUnits[WEEK]) {
653
+ // get start of current week, independent of count
654
+ minDate[setDate](minDate[getDate]() - minDate[getDay]() +
655
+ pick(startOfWeek, 1));
656
+ }
657
+
658
+
659
+ // get tick positions
660
+ i = 1;
639
661
  minYear = minDate[getFullYear]();
640
- }
641
-
642
- if (interval >= timeUnits[YEAR]) { // year
643
- minYear -= minYear % count;
644
- minDate[setFullYear](minYear);
645
- }
646
-
647
- // week is a special case that runs outside the hierarchy
648
- if (interval === timeUnits[WEEK]) {
649
- // get start of current week, independent of count
650
- minDate[setDate](minDate[getDate]() - minDate[getDay]() +
651
- pick(startOfWeek, 1));
652
- }
653
-
654
-
655
- // get tick positions
656
- i = 1;
657
- minYear = minDate[getFullYear]();
658
- var time = minDate.getTime(),
659
- minMonth = minDate[getMonth](),
660
- minDateDate = minDate[getDate](),
661
- timezoneOffset = useUTC ?
662
- 0 :
663
- (24 * 3600 * 1000 + minDate.getTimezoneOffset() * 60 * 1000) % (24 * 3600 * 1000); // #950
664
-
665
- // iterate and add tick positions at appropriate values
666
- while (time < max) {
667
- tickPositions.push(time);
668
-
669
- // if the interval is years, use Date.UTC to increase years
670
- if (interval === timeUnits[YEAR]) {
671
- time = makeTime(minYear + i * count, 0);
672
-
673
- // if the interval is months, use Date.UTC to increase months
674
- } else if (interval === timeUnits[MONTH]) {
675
- time = makeTime(minYear, minMonth + i * count);
676
-
677
- // if we're using global time, the interval is not fixed as it jumps
678
- // one hour at the DST crossover
679
- } else if (!useUTC && (interval === timeUnits[DAY] || interval === timeUnits[WEEK])) {
680
- time = makeTime(minYear, minMonth, minDateDate +
681
- i * count * (interval === timeUnits[DAY] ? 1 : 7));
682
-
683
- // else, the interval is fixed and we use simple addition
684
- } else {
685
- time += interval * count;
686
-
687
- // mark new days if the time is dividable by day
688
- if (interval <= timeUnits[HOUR] && time % timeUnits[DAY] === timezoneOffset) {
689
- higherRanks[time] = DAY;
662
+ var time = minDate.getTime(),
663
+ minMonth = minDate[getMonth](),
664
+ minDateDate = minDate[getDate](),
665
+ timezoneOffset = useUTC ?
666
+ 0 :
667
+ (24 * 3600 * 1000 + minDate.getTimezoneOffset() * 60 * 1000) % (24 * 3600 * 1000); // #950
668
+
669
+ // iterate and add tick positions at appropriate values
670
+ while (time < max) {
671
+ tickPositions.push(time);
672
+
673
+ // if the interval is years, use Date.UTC to increase years
674
+ if (interval === timeUnits[YEAR]) {
675
+ time = makeTime(minYear + i * count, 0);
676
+
677
+ // if the interval is months, use Date.UTC to increase months
678
+ } else if (interval === timeUnits[MONTH]) {
679
+ time = makeTime(minYear, minMonth + i * count);
680
+
681
+ // if we're using global time, the interval is not fixed as it jumps
682
+ // one hour at the DST crossover
683
+ } else if (!useUTC && (interval === timeUnits[DAY] || interval === timeUnits[WEEK])) {
684
+ time = makeTime(minYear, minMonth, minDateDate +
685
+ i * count * (interval === timeUnits[DAY] ? 1 : 7));
686
+
687
+ // else, the interval is fixed and we use simple addition
688
+ } else {
689
+ time += interval * count;
690
+
691
+ // mark new days if the time is dividable by day
692
+ if (interval <= timeUnits[HOUR] && time % timeUnits[DAY] === timezoneOffset) {
693
+ higherRanks[time] = DAY;
694
+ }
690
695
  }
696
+
697
+ i++;
691
698
  }
692
-
693
- i++;
694
- }
695
699
 
696
- // push the last time
697
- tickPositions.push(time);
700
+ // push the last time
701
+ tickPositions.push(time);
702
+ }
698
703
 
699
704
  // record information on the chosen unit - for dynamic label formatter
700
705
  tickPositions.info = extend(normalizedInterval, {
@@ -875,7 +880,7 @@ timeUnits = hash(
875
880
  HOUR, 3600000,
876
881
  DAY, 24 * 3600000,
877
882
  WEEK, 7 * 24 * 3600000,
878
- MONTH, 30 * 24 * 3600000,
883
+ MONTH, 31 * 24 * 3600000,
879
884
  YEAR, 31556952000
880
885
  );
881
886
  /*jslint white: false*/
@@ -1362,8 +1367,8 @@ defaultOptions = {
1362
1367
  },
1363
1368
  global: {
1364
1369
  useUTC: true,
1365
- canvasToolsURL: 'http://code.highcharts.com/2.3.3/modules/canvas-tools.js',
1366
- VMLRadialGradientURL: 'http://code.highcharts.com/2.3.3/gfx/vml-radial-gradient.png'
1370
+ canvasToolsURL: 'http://code.highcharts.com/2.3.5/modules/canvas-tools.js',
1371
+ VMLRadialGradientURL: 'http://code.highcharts.com/2.3.5/gfx/vml-radial-gradient.png'
1367
1372
  },
1368
1373
  chart: {
1369
1374
  //animation: true,
@@ -1615,7 +1620,7 @@ defaultOptions = {
1615
1620
  pointFormat: '<span style="color:{series.color}">{series.name}</span>: <b>{point.y}</b><br/>',
1616
1621
  shadow: true,
1617
1622
  shared: useCanVG,
1618
- snap: hasTouch ? 25 : 10,
1623
+ snap: isTouchDevice ? 25 : 10,
1619
1624
  style: {
1620
1625
  color: '#333333',
1621
1626
  fontSize: '12px',
@@ -2021,8 +2026,8 @@ SVGElement.prototype = {
2021
2026
  key = 'stroke-width';
2022
2027
  }
2023
2028
 
2024
- // Chrome/Win < 6 bug (http://code.google.com/p/chromium/issues/detail?id=15461)
2025
- if (isWebKit && key === 'stroke-width' && value === 0) {
2029
+ // Chrome/Win < 6 bug (http://code.google.com/p/chromium/issues/detail?id=15461), #1369
2030
+ if (key === 'stroke-width' && value === 0 && (isWebKit || renderer.forExport)) {
2026
2031
  value = 0.000001;
2027
2032
  }
2028
2033
 
@@ -2213,17 +2218,15 @@ SVGElement.prototype = {
2213
2218
  * @param {Function} handler
2214
2219
  */
2215
2220
  on: function (eventType, handler) {
2216
- var fn = handler;
2217
2221
  // touch
2218
2222
  if (hasTouch && eventType === 'click') {
2219
- eventType = 'touchstart';
2220
- fn = function (e) {
2223
+ this.element.ontouchstart = function (e) {
2221
2224
  e.preventDefault();
2222
2225
  handler();
2223
2226
  };
2224
2227
  }
2225
2228
  // simplest possible event model for internal use
2226
- this.element['on' + eventType] = fn;
2229
+ this.element['on' + eventType] = handler;
2227
2230
  return this;
2228
2231
  },
2229
2232
 
@@ -2404,7 +2407,7 @@ SVGElement.prototype = {
2404
2407
  height = pick(wrapper.elemHeight, elem.offsetHeight);
2405
2408
 
2406
2409
  // update textWidth
2407
- if (width > textWidth && /[ \-]/.test(elem.innerText)) { // #983
2410
+ if (width > textWidth && /[ \-]/.test(elem.textContent || elem.innerText)) { // #983, #1254
2408
2411
  css(elem, {
2409
2412
  width: textWidth + PX,
2410
2413
  display: 'block',
@@ -2444,6 +2447,11 @@ SVGElement.prototype = {
2444
2447
  left: (x + xCorr) + PX,
2445
2448
  top: (y + yCorr) + PX
2446
2449
  });
2450
+
2451
+ // force reflow in webkit to apply the left and top on useHTML element (#1249)
2452
+ if (isWebKit) {
2453
+ height = elem.offsetHeight; // assigned to height for JSLint purpose
2454
+ }
2447
2455
 
2448
2456
  // record current text transform
2449
2457
  wrapper.cTT = currentTextTransform;
@@ -2563,7 +2571,7 @@ SVGElement.prototype = {
2563
2571
  element = wrapper.element,
2564
2572
  styles = wrapper.styles,
2565
2573
  rad = rotation * deg2rad;
2566
-
2574
+
2567
2575
  if (!bBox) {
2568
2576
  // SVG elements
2569
2577
  if (element.namespaceURI === SVG_NS || renderer.forExport) {
@@ -2599,7 +2607,12 @@ SVGElement.prototype = {
2599
2607
  if (renderer.isSVG) {
2600
2608
  width = bBox.width;
2601
2609
  height = bBox.height;
2602
-
2610
+
2611
+ // Workaround for wrong bounding box in IE9 and IE10 (#1101)
2612
+ if (isIE && styles && styles.fontSize === '11px' && height === 22.700000762939453) {
2613
+ bBox.height = height = 14;
2614
+ }
2615
+
2603
2616
  // Adjust for rotated text
2604
2617
  if (rotation) {
2605
2618
  bBox.width = mathAbs(height * mathSin(rad)) + mathAbs(width * mathCos(rad));
@@ -2607,11 +2620,6 @@ SVGElement.prototype = {
2607
2620
  }
2608
2621
  }
2609
2622
 
2610
- // Workaround for wrong bounding box in IE9 and IE10 (#1101)
2611
- if (isIE && styles && styles.fontSize === '11px' && height === 22.700000762939453) {
2612
- bBox.height = 14;
2613
- }
2614
-
2615
2623
  wrapper.bBox = bBox;
2616
2624
  }
2617
2625
  return bBox;
@@ -3430,6 +3438,7 @@ SVGRenderer.prototype = {
3430
3438
  options
3431
3439
  ),
3432
3440
 
3441
+ imageElement,
3433
3442
  imageRegex = /^url\((.*?)\)$/,
3434
3443
  imageSrc,
3435
3444
  imageSize,
@@ -3456,23 +3465,25 @@ SVGRenderer.prototype = {
3456
3465
 
3457
3466
  // On image load, set the size and position
3458
3467
  centerImage = function (img, size) {
3459
- img.attr({
3460
- width: size[0],
3461
- height: size[1]
3462
- });
3468
+ if (img.element) { // it may be destroyed in the meantime (#1390)
3469
+ img.attr({
3470
+ width: size[0],
3471
+ height: size[1]
3472
+ });
3463
3473
 
3464
- if (!img.alignByTranslate) { // #185
3465
- img.translate(
3466
- -mathRound(size[0] / 2),
3467
- -mathRound(size[1] / 2)
3468
- );
3474
+ if (!img.alignByTranslate) { // #185
3475
+ img.translate(
3476
+ mathRound((width - size[0]) / 2), // #1378
3477
+ mathRound((height - size[1]) / 2)
3478
+ );
3479
+ }
3469
3480
  }
3470
3481
  };
3471
3482
 
3472
3483
  imageSrc = symbol.match(imageRegex)[1];
3473
3484
  imageSize = symbolSizes[imageSrc];
3474
3485
 
3475
- // create the image synchronously, add attribs async
3486
+ // Ireate the image synchronously, add attribs async
3476
3487
  obj = this.image(imageSrc)
3477
3488
  .attr({
3478
3489
  x: x,
@@ -3482,15 +3493,15 @@ SVGRenderer.prototype = {
3482
3493
  if (imageSize) {
3483
3494
  centerImage(obj, imageSize);
3484
3495
  } else {
3485
- // initialize image to be 0 size so export will still function if there's no cached sizes
3496
+ // Initialize image to be 0 size so export will still function if there's no cached sizes.
3497
+ //
3486
3498
  obj.attr({ width: 0, height: 0 });
3487
3499
 
3488
- // create a dummy JavaScript image to get the width and height
3489
- createElement('img', {
3500
+ // Create a dummy JavaScript image to get the width and height. Due to a bug in IE < 8,
3501
+ // the created element must be assigned to a variable in order to load (#292).
3502
+ imageElement = createElement('img', {
3490
3503
  onload: function () {
3491
- var img = this;
3492
-
3493
- centerImage(obj, symbolSizes[imageSrc] = [img.width, img.height]);
3504
+ centerImage(obj, symbolSizes[imageSrc] = [this.width, this.height]);
3494
3505
  },
3495
3506
  src: imageSrc
3496
3507
  });
@@ -3629,7 +3640,17 @@ SVGRenderer.prototype = {
3629
3640
  var renderer = this,
3630
3641
  colorObject,
3631
3642
  regexRgba = /^rgba/,
3632
- gradName;
3643
+ gradName,
3644
+ gradAttr,
3645
+ gradients,
3646
+ gradientObject,
3647
+ stops,
3648
+ stopColor,
3649
+ stopOpacity,
3650
+ radialReference,
3651
+ n,
3652
+ id,
3653
+ key = [];
3633
3654
 
3634
3655
  // Apply linear or radial gradients
3635
3656
  if (color && color.linearGradient) {
@@ -3639,47 +3660,59 @@ SVGRenderer.prototype = {
3639
3660
  }
3640
3661
 
3641
3662
  if (gradName) {
3642
- var gradAttr = color[gradName],
3643
- gradients = renderer.gradients,
3644
- gradientObject,
3645
- stopColor,
3646
- stopOpacity,
3647
- radialReference = elem.radialReference;
3663
+ gradAttr = color[gradName];
3664
+ gradients = renderer.gradients;
3665
+ stops = color.stops;
3666
+ radialReference = elem.radialReference;
3648
3667
 
3649
- // Check if a gradient object with the same config object is created within this renderer
3650
- if (!gradAttr.id || !gradients[gradAttr.id]) {
3651
-
3652
- // Keep < 2.2 kompatibility
3653
- if (isArray(gradAttr)) {
3654
- color[gradName] = gradAttr = {
3655
- x1: gradAttr[0],
3656
- y1: gradAttr[1],
3657
- x2: gradAttr[2],
3658
- y2: gradAttr[3],
3659
- gradientUnits: 'userSpaceOnUse'
3660
- };
3668
+ // Keep < 2.2 kompatibility
3669
+ if (isArray(gradAttr)) {
3670
+ color[gradName] = gradAttr = {
3671
+ x1: gradAttr[0],
3672
+ y1: gradAttr[1],
3673
+ x2: gradAttr[2],
3674
+ y2: gradAttr[3],
3675
+ gradientUnits: 'userSpaceOnUse'
3676
+ };
3677
+ }
3678
+
3679
+ // Correct the radial gradient for the radial reference system
3680
+ if (gradName === 'radialGradient' && radialReference && !defined(gradAttr.gradientUnits)) {
3681
+ extend(gradAttr, {
3682
+ cx: (radialReference[0] - radialReference[2] / 2) + gradAttr.cx * radialReference[2],
3683
+ cy: (radialReference[1] - radialReference[2] / 2) + gradAttr.cy * radialReference[2],
3684
+ r: gradAttr.r * radialReference[2],
3685
+ gradientUnits: 'userSpaceOnUse'
3686
+ });
3687
+ }
3688
+
3689
+ // Build the unique key to detect whether we need to create a new element (#1282)
3690
+ for (n in gradAttr) {
3691
+ if (n !== 'id') {
3692
+ key.push(n, gradAttr[n]);
3661
3693
  }
3694
+ }
3695
+ for (n in stops) {
3696
+ key.push(stops[n]);
3697
+ }
3698
+ key = key.join(',');
3699
+
3700
+ // Check if a gradient object with the same config object is created within this renderer
3701
+ if (gradients[key]) {
3702
+ id = gradients[key].id;
3662
3703
 
3663
- // Correct the radial gradient for the radial reference system
3664
- if (gradName === 'radialGradient' && radialReference && !defined(gradAttr.gradientUnits)) {
3665
- extend(gradAttr, {
3666
- cx: (radialReference[0] - radialReference[2] / 2) + gradAttr.cx * radialReference[2],
3667
- cy: (radialReference[1] - radialReference[2] / 2) + gradAttr.cy * radialReference[2],
3668
- r: gradAttr.r * radialReference[2],
3669
- gradientUnits: 'userSpaceOnUse'
3670
- });
3671
- }
3704
+ } else {
3672
3705
 
3673
3706
  // Set the id and create the element
3674
- gradAttr.id = PREFIX + idCounter++;
3675
- gradients[gradAttr.id] = gradientObject = renderer.createElement(gradName)
3707
+ gradAttr.id = id = PREFIX + idCounter++;
3708
+ gradients[key] = gradientObject = renderer.createElement(gradName)
3676
3709
  .attr(gradAttr)
3677
3710
  .add(renderer.defs);
3678
3711
 
3679
3712
 
3680
3713
  // The gradient needs to keep a list of stops to be able to destroy them
3681
3714
  gradientObject.stops = [];
3682
- each(color.stops, function (stop) {
3715
+ each(stops, function (stop) {
3683
3716
  var stopObject;
3684
3717
  if (regexRgba.test(stop[1])) {
3685
3718
  colorObject = Color(stop[1]);
@@ -3701,7 +3734,7 @@ SVGRenderer.prototype = {
3701
3734
  }
3702
3735
 
3703
3736
  // Return the reference to the gradient object
3704
- return 'url(' + renderer.url + '#' + gradAttr.id + ')';
3737
+ return 'url(' + renderer.url + '#' + id + ')';
3705
3738
 
3706
3739
  // Webkit and Batik can't show rgba.
3707
3740
  } else if (regexRgba.test(color)) {
@@ -3943,7 +3976,8 @@ SVGRenderer.prototype = {
3943
3976
  crispAdjust = 0,
3944
3977
  deferredAttr = {},
3945
3978
  baselineOffset,
3946
- attrSetters = wrapper.attrSetters;
3979
+ attrSetters = wrapper.attrSetters,
3980
+ needsBox;
3947
3981
 
3948
3982
  /**
3949
3983
  * This function runs after the label is added to the DOM (when the bounding box is
@@ -3961,24 +3995,26 @@ SVGRenderer.prototype = {
3961
3995
 
3962
3996
  // update the label-scoped y offset
3963
3997
  baselineOffset = padding + renderer.fontMetrics(style && style.fontSize).b;
3964
-
3965
-
3966
- // create the border box if it is not already present
3967
- if (!box) {
3968
- boxY = baseline ? -baselineOffset : 0;
3969
-
3970
- wrapper.box = box = shape ?
3971
- renderer.symbol(shape, -alignFactor * padding, boxY, wrapper.width, wrapper.height) :
3972
- renderer.rect(-alignFactor * padding, boxY, wrapper.width, wrapper.height, 0, deferredAttr[STROKE_WIDTH]);
3973
- box.add(wrapper);
3998
+
3999
+ if (needsBox) {
4000
+
4001
+ // create the border box if it is not already present
4002
+ if (!box) {
4003
+ boxY = baseline ? -baselineOffset : 0;
4004
+
4005
+ wrapper.box = box = shape ?
4006
+ renderer.symbol(shape, -alignFactor * padding, boxY, wrapper.width, wrapper.height) :
4007
+ renderer.rect(-alignFactor * padding, boxY, wrapper.width, wrapper.height, 0, deferredAttr[STROKE_WIDTH]);
4008
+ box.add(wrapper);
4009
+ }
4010
+
4011
+ // apply the box attributes
4012
+ box.attr(merge({
4013
+ width: wrapper.width,
4014
+ height: wrapper.height
4015
+ }, deferredAttr));
4016
+ deferredAttr = null;
3974
4017
  }
3975
-
3976
- // apply the box attributes
3977
- box.attr(merge({
3978
- width: wrapper.width,
3979
- height: wrapper.height
3980
- }, deferredAttr));
3981
- deferredAttr = null;
3982
4018
  }
3983
4019
 
3984
4020
  /**
@@ -4032,7 +4068,7 @@ SVGRenderer.prototype = {
4032
4068
  y: y
4033
4069
  });
4034
4070
 
4035
- if (defined(anchorX)) {
4071
+ if (box && defined(anchorX)) {
4036
4072
  wrapper.attr({
4037
4073
  anchorX: anchorX,
4038
4074
  anchorY: anchorY
@@ -4084,11 +4120,15 @@ SVGRenderer.prototype = {
4084
4120
 
4085
4121
  // apply these to the box but not to the text
4086
4122
  attrSetters[STROKE_WIDTH] = function (value, key) {
4123
+ needsBox = true;
4087
4124
  crispAdjust = value % 2 / 2;
4088
4125
  boxAttr(key, value);
4089
4126
  return false;
4090
4127
  };
4091
4128
  attrSetters.stroke = attrSetters.fill = attrSetters.r = function (value, key) {
4129
+ if (key === 'fill') {
4130
+ needsBox = true;
4131
+ }
4092
4132
  boxAttr(key, value);
4093
4133
  return false;
4094
4134
  };
@@ -4142,13 +4182,20 @@ SVGRenderer.prototype = {
4142
4182
  * Return the bounding box of the box, not the group
4143
4183
  */
4144
4184
  getBBox: function () {
4145
- return box.getBBox();
4185
+ return {
4186
+ width: bBox.width + 2 * padding,
4187
+ height: bBox.height + 2 * padding,
4188
+ x: bBox.x - padding,
4189
+ y: bBox.y - padding
4190
+ };
4146
4191
  },
4147
4192
  /**
4148
4193
  * Apply the shadow to the box
4149
4194
  */
4150
4195
  shadow: function (b) {
4151
- box.shadow(b);
4196
+ if (box) {
4197
+ box.shadow(b);
4198
+ }
4152
4199
  return wrapper;
4153
4200
  },
4154
4201
  /**
@@ -4169,6 +4216,9 @@ SVGRenderer.prototype = {
4169
4216
  }
4170
4217
  // Call base implementation to destroy the rest
4171
4218
  SVGElement.prototype.destroy.call(wrapper);
4219
+
4220
+ // Release local pointers (#1298)
4221
+ wrapper = renderer = updateBoxSize = updateTextPadding = boxAttr = getSizeAfterAdd = null;
4172
4222
  }
4173
4223
  });
4174
4224
  }
@@ -4459,7 +4509,7 @@ var VMLElement = {
4459
4509
 
4460
4510
  if (nodeName === 'SPAN') { // text color
4461
4511
  elemStyle.color = value;
4462
- } else {
4512
+ } else if (nodeName !== 'IMG') { // #1336
4463
4513
  element.filled = value !== NONE ? true : false;
4464
4514
 
4465
4515
  value = renderer.color(value, element, key, wrapper);
@@ -4516,6 +4566,7 @@ var VMLElement = {
4516
4566
 
4517
4567
  if (clipRect) {
4518
4568
  clipMembers = clipRect.members;
4569
+ erase(clipMembers, wrapper); // Ensure unique list of elements (#1258)
4519
4570
  clipMembers.push(wrapper);
4520
4571
  wrapper.destroyClip = function () {
4521
4572
  erase(clipMembers, wrapper);
@@ -5722,10 +5773,9 @@ Tick.prototype = {
5722
5773
  // Set the new position, and show or hide
5723
5774
  if (show) {
5724
5775
  label[tick.isNew ? 'attr' : 'animate'](xy);
5725
- label.show();
5726
5776
  tick.isNew = false;
5727
5777
  } else {
5728
- label.hide();
5778
+ label.attr('y', -9999); // #1338
5729
5779
  }
5730
5780
  }
5731
5781
  },
@@ -6543,11 +6593,14 @@ Axis.prototype = {
6543
6593
  pointStack = isNegative ? negPointStack : posPointStack;
6544
6594
  key = isNegative ? negKey : stackKey;
6545
6595
 
6546
- y = pointStack[x] =
6547
- defined(pointStack[x]) ?
6548
- correctFloat(pointStack[x] + y) :
6549
- y;
6596
+ // Set the stack value and y for extremes
6597
+ if (defined(pointStack[x])) { // we're adding to the stack
6598
+ pointStack[x] = correctFloat(pointStack[x] + y);
6599
+ y = [y, pointStack[x]]; // consider both the actual value and the stack (#1376)
6550
6600
 
6601
+ } else { // it's the first point in the stack
6602
+ pointStack[x] = y;
6603
+ }
6551
6604
 
6552
6605
  // add the series
6553
6606
  if (!stacks[key]) {
@@ -6559,7 +6612,7 @@ Axis.prototype = {
6559
6612
  if (!stacks[key][x]) {
6560
6613
  stacks[key][x] = new StackItem(axis, axis.options.stackLabels, isNegative, x, stackOption, stacking);
6561
6614
  }
6562
- stacks[key][x].setTotal(y);
6615
+ stacks[key][x].setTotal(pointStack[x]);
6563
6616
  }
6564
6617
 
6565
6618
  // Handle non null values
@@ -6588,12 +6641,6 @@ Axis.prototype = {
6588
6641
  }
6589
6642
  }
6590
6643
 
6591
- // record the least unit distance
6592
- /*if (findPointRange) {
6593
- series.pointRange = pointRange || 1;
6594
- }
6595
- series.closestPointRange = pointRange;*/
6596
-
6597
6644
  // Get the dataMin and dataMax so far. If percentage is used, the min and max are
6598
6645
  // always 0 and 100. If the length of activeYData is 0, continue with null values.
6599
6646
  if (!axis.usePercentage && activeYData.length) {
@@ -6871,6 +6918,7 @@ Axis.prototype = {
6871
6918
  */
6872
6919
  getMinorTickPositions: function () {
6873
6920
  var axis = this,
6921
+ options = axis.options,
6874
6922
  tickPositions = axis.tickPositions,
6875
6923
  minorTickInterval = axis.minorTickInterval;
6876
6924
 
@@ -6886,13 +6934,20 @@ Axis.prototype = {
6886
6934
  axis.getLogTickPositions(minorTickInterval, tickPositions[i - 1], tickPositions[i], true)
6887
6935
  );
6888
6936
  }
6889
-
6937
+ } else if (axis.isDatetimeAxis && options.minorTickInterval === 'auto') { // #1314
6938
+ minorTickPositions = minorTickPositions.concat(
6939
+ getTimeTicks(
6940
+ normalizeTimeTickInterval(minorTickInterval),
6941
+ axis.min,
6942
+ axis.max,
6943
+ options.startOfWeek
6944
+ )
6945
+ );
6890
6946
  } else {
6891
6947
  for (pos = axis.min + (tickPositions[0] - axis.min) % minorTickInterval; pos <= axis.max; pos += minorTickInterval) {
6892
6948
  minorTickPositions.push(pos);
6893
6949
  }
6894
6950
  }
6895
-
6896
6951
  return minorTickPositions;
6897
6952
  },
6898
6953
 
@@ -7036,7 +7091,7 @@ Axis.prototype = {
7036
7091
  // is some other value
7037
7092
  axis.closestPointRange = closestPointRange;
7038
7093
  }
7039
-
7094
+
7040
7095
  // secondary values
7041
7096
  axis.oldTransA = transA;
7042
7097
  //axis.translationSlope = axis.transA = transA = axis.len / ((range + (2 * minPointOffset)) || 1);
@@ -7102,15 +7157,18 @@ Axis.prototype = {
7102
7157
 
7103
7158
  // adjust min and max for the minimum range
7104
7159
  axis.adjustForMinRange();
7105
-
7106
- // pad the values to get clear of the chart's edges
7160
+
7161
+ // Pad the values to get clear of the chart's edges. To avoid tickInterval taking the padding
7162
+ // into account, we do this after computing tick interval (#1337).
7107
7163
  if (!categories && !axis.usePercentage && !isLinked && defined(axis.min) && defined(axis.max)) {
7108
- length = (axis.max - axis.min) || 1;
7109
- if (!defined(options.min) && !defined(axis.userMin) && minPadding && (axis.dataMin < 0 || !axis.ignoreMinPadding)) {
7110
- axis.min -= length * minPadding;
7111
- }
7112
- if (!defined(options.max) && !defined(axis.userMax) && maxPadding && (axis.dataMax > 0 || !axis.ignoreMaxPadding)) {
7113
- axis.max += length * maxPadding;
7164
+ length = axis.max - axis.min;
7165
+ if (length) {
7166
+ if (!defined(options.min) && !defined(axis.userMin) && minPadding && (axis.dataMin < 0 || !axis.ignoreMinPadding)) {
7167
+ axis.min -= length * minPadding;
7168
+ }
7169
+ if (!defined(options.max) && !defined(axis.userMax) && maxPadding && (axis.dataMax > 0 || !axis.ignoreMaxPadding)) {
7170
+ axis.max += length * maxPadding;
7171
+ }
7114
7172
  }
7115
7173
  }
7116
7174
 
@@ -7193,7 +7251,8 @@ Axis.prototype = {
7193
7251
  // reset min/max or remove extremes based on start/end on tick
7194
7252
  var roundedMin = tickPositions[0],
7195
7253
  roundedMax = tickPositions[tickPositions.length - 1],
7196
- minPointOffset = axis.minPointOffset || 0;
7254
+ minPointOffset = axis.minPointOffset || 0,
7255
+ singlePad;
7197
7256
 
7198
7257
  if (options.startOnTick) {
7199
7258
  axis.min = roundedMin;
@@ -7207,6 +7266,14 @@ Axis.prototype = {
7207
7266
  tickPositions.pop();
7208
7267
  }
7209
7268
 
7269
+ // When there is only one point, or all points have the same value on this axis, then min
7270
+ // and max are equal and tickPositions.length is 1. In this case, add some padding
7271
+ // in order to center the point, but leave it with one tick. #1337.
7272
+ if (tickPositions.length === 1) {
7273
+ singlePad = 1e-9; // The lowest possible number to avoid extra padding on columns
7274
+ axis.min -= singlePad;
7275
+ axis.max += singlePad;
7276
+ }
7210
7277
  }
7211
7278
  },
7212
7279
 
@@ -7694,28 +7761,30 @@ Axis.prototype = {
7694
7761
 
7695
7762
  // Major ticks. Pull out the first item and render it last so that
7696
7763
  // we can get the position of the neighbour label. #808.
7697
- each(tickPositions.slice(1).concat([tickPositions[0]]), function (pos, i) {
7698
-
7699
- // Reorganize the indices
7700
- i = (i === tickPositions.length - 1) ? 0 : i + 1;
7701
-
7702
- // linked axes need an extra check to find out if
7703
- if (!isLinked || (pos >= axis.min && pos <= axis.max)) {
7704
-
7705
- if (!ticks[pos]) {
7706
- ticks[pos] = new Tick(axis, pos);
7707
- }
7708
-
7709
- // render new ticks in old position
7710
- if (slideInTicks && ticks[pos].isNew) {
7711
- ticks[pos].render(i, true);
7764
+ if (tickPositions.length) { // #1300
7765
+ each(tickPositions.slice(1).concat([tickPositions[0]]), function (pos, i) {
7766
+
7767
+ // Reorganize the indices
7768
+ i = (i === tickPositions.length - 1) ? 0 : i + 1;
7769
+
7770
+ // linked axes need an extra check to find out if
7771
+ if (!isLinked || (pos >= axis.min && pos <= axis.max)) {
7772
+
7773
+ if (!ticks[pos]) {
7774
+ ticks[pos] = new Tick(axis, pos);
7775
+ }
7776
+
7777
+ // render new ticks in old position
7778
+ if (slideInTicks && ticks[pos].isNew) {
7779
+ ticks[pos].render(i, true);
7780
+ }
7781
+
7782
+ ticks[pos].isActive = true;
7783
+ ticks[pos].render(i);
7712
7784
  }
7713
-
7714
- ticks[pos].isActive = true;
7715
- ticks[pos].render(i);
7716
- }
7717
-
7718
- });
7785
+
7786
+ });
7787
+ }
7719
7788
 
7720
7789
  // alternate grid color
7721
7790
  if (alternateGridColor) {
@@ -8494,7 +8563,7 @@ MouseTracker.prototype = {
8494
8563
  for (j = 0; j < i; j++) {
8495
8564
  if (series[j].visible &&
8496
8565
  series[j].options.enableMouseTracking !== false &&
8497
- !series[j].noSharedTooltip && series[j].tooltipPoints.length) {
8566
+ !series[j].noSharedTooltip && series[j].tooltipPoints && series[j].tooltipPoints.length) {
8498
8567
  point = series[j].tooltipPoints[index];
8499
8568
  point._dist = mathAbs(index - point[series[j].xAxis.tooltipPosName || 'plotX']);
8500
8569
  distance = mathMin(distance, point._dist);
@@ -8657,7 +8726,10 @@ MouseTracker.prototype = {
8657
8726
  chart.mouseIsDown = hasDragged = false;
8658
8727
  }
8659
8728
 
8660
- removeEvent(doc, hasTouch ? 'touchend' : 'mouseup', drop);
8729
+ removeEvent(doc, 'mouseup', drop);
8730
+ if (hasTouch) {
8731
+ removeEvent(doc, 'touchend', drop);
8732
+ }
8661
8733
  }
8662
8734
 
8663
8735
  /**
@@ -8692,7 +8764,7 @@ MouseTracker.prototype = {
8692
8764
  e = mouseTracker.normalizeMouseEvent(e);
8693
8765
 
8694
8766
  // issue #295, dragging not always working in Firefox
8695
- if (!hasTouch && e.preventDefault) {
8767
+ if (e.type.indexOf('touch') === -1 && e.preventDefault) {
8696
8768
  e.preventDefault();
8697
8769
  }
8698
8770
 
@@ -8702,11 +8774,15 @@ MouseTracker.prototype = {
8702
8774
  chart.mouseDownX = mouseTracker.mouseDownX = e.chartX;
8703
8775
  mouseTracker.mouseDownY = e.chartY;
8704
8776
 
8705
- addEvent(doc, hasTouch ? 'touchend' : 'mouseup', drop);
8777
+ addEvent(doc, 'mouseup', drop);
8778
+ if (hasTouch) {
8779
+ addEvent(doc, 'touchend', drop);
8780
+ }
8706
8781
  };
8707
8782
 
8708
8783
  // The mousemove, touchmove and touchstart event handler
8709
8784
  var mouseMove = function (e) {
8785
+
8710
8786
  // let the system handle multitouch operations like two finger scroll
8711
8787
  // and pinching
8712
8788
  if (e && e.touches && e.touches.length > 1) {
@@ -8715,16 +8791,19 @@ MouseTracker.prototype = {
8715
8791
 
8716
8792
  // normalize
8717
8793
  e = mouseTracker.normalizeMouseEvent(e);
8718
- if (!hasTouch) { // not for touch devices
8719
- e.returnValue = false;
8720
- }
8721
8794
 
8722
- var chartX = e.chartX,
8795
+ var type = e.type,
8796
+ chartX = e.chartX,
8723
8797
  chartY = e.chartY,
8724
8798
  isOutsidePlot = !chart.isInsidePlot(chartX - chart.plotLeft, chartY - chart.plotTop);
8799
+
8800
+
8801
+ if (type.indexOf('touch') === -1) { // not for touch actions
8802
+ e.returnValue = false;
8803
+ }
8725
8804
 
8726
8805
  // on touch devices, only trigger click if a handler is defined
8727
- if (hasTouch && e.type === 'touchstart') {
8806
+ if (type === 'touchstart') {
8728
8807
  if (attr(e.target, 'isTracker')) {
8729
8808
  if (!chart.runTrackerClick) {
8730
8809
  e.preventDefault();
@@ -8757,7 +8836,7 @@ MouseTracker.prototype = {
8757
8836
  }
8758
8837
  }
8759
8838
 
8760
- if (chart.mouseIsDown && e.type !== 'touchstart') { // make selection
8839
+ if (chart.mouseIsDown && type !== 'touchstart') { // make selection
8761
8840
 
8762
8841
  // determine if the mouse has moved more than 10px
8763
8842
  hasDragged = Math.sqrt(
@@ -8821,10 +8900,10 @@ MouseTracker.prototype = {
8821
8900
  return isOutsidePlot || !chart.hasCartesianSeries;
8822
8901
  };
8823
8902
 
8824
- /*
8825
- * When the mouse enters the container, run mouseMove
8826
- */
8827
- container.onmousemove = mouseMove;
8903
+ // When the mouse enters the container, run mouseMove
8904
+ if (!/Android 4\.0/.test(userAgent)) { // This hurts. Best effort for #1385.
8905
+ container.onmousemove = mouseMove;
8906
+ }
8828
8907
 
8829
8908
  /*
8830
8909
  * When the mouse leaves the container, hide the tracking (tooltip).
@@ -8834,7 +8913,9 @@ MouseTracker.prototype = {
8834
8913
  // issue #149 workaround
8835
8914
  // The mouseleave event above does not always fire. Whenever the mouse is moving
8836
8915
  // outside the plotarea, hide the tooltip
8837
- addEvent(doc, 'mousemove', mouseTracker.hideTooltipOnMouseMove);
8916
+ if (!hasTouch) { // #1385
8917
+ addEvent(doc, 'mousemove', mouseTracker.hideTooltipOnMouseMove);
8918
+ }
8838
8919
 
8839
8920
  container.ontouchstart = function (e) {
8840
8921
  // For touch devices, use touchmove to zoom
@@ -9124,19 +9205,27 @@ Legend.prototype = {
9124
9205
  /**
9125
9206
  * Position the checkboxes after the width is determined
9126
9207
  */
9127
- positionCheckboxes: function () {
9128
- var legend = this;
9208
+ positionCheckboxes: function (scrollOffset) {
9209
+ var alignAttr = this.group.alignAttr,
9210
+ translateY,
9211
+ clipHeight = this.clipHeight || this.legendHeight;
9129
9212
 
9130
- each(legend.allItems, function (item) {
9131
- var checkbox = item.checkbox,
9132
- alignAttr = legend.group.alignAttr;
9133
- if (checkbox) {
9134
- css(checkbox, {
9135
- left: (alignAttr.translateX + item.legendItemWidth + checkbox.x - 20) + PX,
9136
- top: (alignAttr.translateY + checkbox.y + 3) + PX
9137
- });
9138
- }
9139
- });
9213
+ if (alignAttr) {
9214
+ translateY = alignAttr.translateY;
9215
+ each(this.allItems, function (item) {
9216
+ var checkbox = item.checkbox,
9217
+ top;
9218
+
9219
+ if (checkbox) {
9220
+ top = (translateY + checkbox.y + (scrollOffset || 0) + 3);
9221
+ css(checkbox, {
9222
+ left: (alignAttr.translateX + item.legendItemWidth + checkbox.x - 20) + PX,
9223
+ top: top + PX,
9224
+ display: top > translateY - 6 && top < translateY + clipHeight - 6 ? '' : NONE
9225
+ });
9226
+ }
9227
+ });
9228
+ }
9140
9229
  },
9141
9230
 
9142
9231
  /**
@@ -9165,7 +9254,8 @@ Legend.prototype = {
9165
9254
  li = item.legendItem,
9166
9255
  series = item.series || item,
9167
9256
  itemOptions = series.options,
9168
- showCheckbox = itemOptions.showCheckbox;
9257
+ showCheckbox = itemOptions.showCheckbox,
9258
+ useHTML = options.useHTML;
9169
9259
 
9170
9260
  if (!li) { // generate it once, later move it
9171
9261
 
@@ -9183,7 +9273,7 @@ Legend.prototype = {
9183
9273
  options.labelFormatter.call(item),
9184
9274
  ltr ? symbolWidth + symbolPadding : -symbolPadding,
9185
9275
  legend.baseline,
9186
- options.useHTML
9276
+ useHTML
9187
9277
  )
9188
9278
  .css(merge(item.visible ? itemStyle : itemHiddenStyle)) // merge to prevent modifying original (#1021)
9189
9279
  .attr({
@@ -9192,8 +9282,8 @@ Legend.prototype = {
9192
9282
  })
9193
9283
  .add(item.legendGroup);
9194
9284
 
9195
- // Set the events on the item group
9196
- item.legendGroup.on('mouseover', function () {
9285
+ // Set the events on the item group, or in case of useHTML, the item itself (#1249)
9286
+ (useHTML ? li : item.legendGroup).on('mouseover', function () {
9197
9287
  item.setState(HOVER_STATE);
9198
9288
  li.css(legend.options.itemHoverStyle);
9199
9289
  })
@@ -9512,6 +9602,7 @@ Legend.prototype = {
9512
9602
  this.scrollGroup.attr({
9513
9603
  translateY: 1
9514
9604
  });
9605
+ this.clipHeight = 0; // #1379
9515
9606
  }
9516
9607
 
9517
9608
  return legendHeight;
@@ -9530,7 +9621,8 @@ Legend.prototype = {
9530
9621
  activeColor = navOptions.activeColor,
9531
9622
  inactiveColor = navOptions.inactiveColor,
9532
9623
  pager = this.pager,
9533
- padding = this.padding;
9624
+ padding = this.padding,
9625
+ scrollOffset;
9534
9626
 
9535
9627
  // When resizing while looking at the last page
9536
9628
  if (currentPage > pageCount) {
@@ -9565,8 +9657,9 @@ Legend.prototype = {
9565
9657
  cursor: currentPage === pageCount ? 'default' : 'pointer'
9566
9658
  });
9567
9659
 
9660
+ scrollOffset = -mathMin(clipHeight * (currentPage - 1), this.fullHeight - clipHeight + padding) + 1;
9568
9661
  this.scrollGroup.animate({
9569
- translateY: -mathMin(clipHeight * (currentPage - 1), this.fullHeight - clipHeight + padding) + 1
9662
+ translateY: scrollOffset
9570
9663
  });
9571
9664
  pager.attr({
9572
9665
  text: currentPage + '/' + pageCount
@@ -9574,6 +9667,7 @@ Legend.prototype = {
9574
9667
 
9575
9668
 
9576
9669
  this.currentPage = currentPage;
9670
+ this.positionCheckboxes(scrollOffset);
9577
9671
  }
9578
9672
 
9579
9673
  }
@@ -9586,72 +9680,108 @@ Legend.prototype = {
9586
9680
  * @param {Object} options
9587
9681
  * @param {Function} callback Function to run when the chart has loaded
9588
9682
  */
9589
- function Chart(userOptions, callback) {
9590
- // Handle regular options
9591
- var options,
9592
- seriesOptions = userOptions.series; // skip merging data points to increase performance
9593
- userOptions.series = null;
9594
- options = merge(defaultOptions, userOptions); // do the merge
9595
- options.series = userOptions.series = seriesOptions; // set back the series data
9596
-
9597
- var optionsChart = options.chart,
9598
- optionsMargin = optionsChart.margin,
9599
- margin = isObject(optionsMargin) ?
9600
- optionsMargin :
9601
- [optionsMargin, optionsMargin, optionsMargin, optionsMargin];
9602
-
9603
- this.optionsMarginTop = pick(optionsChart.marginTop, margin[0]);
9604
- this.optionsMarginRight = pick(optionsChart.marginRight, margin[1]);
9605
- this.optionsMarginBottom = pick(optionsChart.marginBottom, margin[2]);
9606
- this.optionsMarginLeft = pick(optionsChart.marginLeft, margin[3]);
9607
-
9608
- var chartEvents = optionsChart.events;
9609
-
9610
- this.runChartClick = chartEvents && !!chartEvents.click;
9611
- this.callback = callback;
9612
- this.isResizing = 0;
9613
- this.options = options;
9614
- //chartTitleOptions = UNDEFINED;
9615
- //chartSubtitleOptions = UNDEFINED;
9616
-
9617
- this.axes = [];
9618
- this.series = [];
9619
- this.hasCartesianSeries = optionsChart.showAxes;
9620
- //this.axisOffset = UNDEFINED;
9621
- //this.maxTicks = UNDEFINED; // handle the greatest amount of ticks on grouped axes
9622
- //this.inverted = UNDEFINED;
9623
- //this.loadingShown = UNDEFINED;
9624
- //this.container = UNDEFINED;
9625
- //this.chartWidth = UNDEFINED;
9626
- //this.chartHeight = UNDEFINED;
9627
- //this.marginRight = UNDEFINED;
9628
- //this.marginBottom = UNDEFINED;
9629
- //this.containerWidth = UNDEFINED;
9630
- //this.containerHeight = UNDEFINED;
9631
- //this.oldChartWidth = UNDEFINED;
9632
- //this.oldChartHeight = UNDEFINED;
9633
-
9634
- //this.renderTo = UNDEFINED;
9635
- //this.renderToClone = UNDEFINED;
9636
- //this.tracker = UNDEFINED;
9637
-
9638
- //this.spacingBox = UNDEFINED
9639
-
9640
- //this.legend = UNDEFINED;
9641
-
9642
- // Elements
9643
- //this.chartBackground = UNDEFINED;
9644
- //this.plotBackground = UNDEFINED;
9645
- //this.plotBGImage = UNDEFINED;
9646
- //this.plotBorder = UNDEFINED;
9647
- //this.loadingDiv = UNDEFINED;
9648
- //this.loadingSpan = UNDEFINED;
9649
-
9650
- this.init(chartEvents);
9683
+ function Chart() {
9684
+ this.init.apply(this, arguments);
9651
9685
  }
9652
9686
 
9653
9687
  Chart.prototype = {
9654
9688
 
9689
+ /**
9690
+ * Initialize the chart
9691
+ */
9692
+ init: function (userOptions, callback) {
9693
+
9694
+ // Handle regular options
9695
+ var options,
9696
+ seriesOptions = userOptions.series; // skip merging data points to increase performance
9697
+
9698
+ userOptions.series = null;
9699
+ options = merge(defaultOptions, userOptions); // do the merge
9700
+ options.series = userOptions.series = seriesOptions; // set back the series data
9701
+
9702
+ var optionsChart = options.chart,
9703
+ optionsMargin = optionsChart.margin,
9704
+ margin = isObject(optionsMargin) ?
9705
+ optionsMargin :
9706
+ [optionsMargin, optionsMargin, optionsMargin, optionsMargin];
9707
+
9708
+ this.optionsMarginTop = pick(optionsChart.marginTop, margin[0]);
9709
+ this.optionsMarginRight = pick(optionsChart.marginRight, margin[1]);
9710
+ this.optionsMarginBottom = pick(optionsChart.marginBottom, margin[2]);
9711
+ this.optionsMarginLeft = pick(optionsChart.marginLeft, margin[3]);
9712
+
9713
+ var chartEvents = optionsChart.events;
9714
+
9715
+ this.runChartClick = chartEvents && !!chartEvents.click;
9716
+ this.callback = callback;
9717
+ this.isResizing = 0;
9718
+ this.options = options;
9719
+ //chartTitleOptions = UNDEFINED;
9720
+ //chartSubtitleOptions = UNDEFINED;
9721
+
9722
+ this.axes = [];
9723
+ this.series = [];
9724
+ this.hasCartesianSeries = optionsChart.showAxes;
9725
+ //this.axisOffset = UNDEFINED;
9726
+ //this.maxTicks = UNDEFINED; // handle the greatest amount of ticks on grouped axes
9727
+ //this.inverted = UNDEFINED;
9728
+ //this.loadingShown = UNDEFINED;
9729
+ //this.container = UNDEFINED;
9730
+ //this.chartWidth = UNDEFINED;
9731
+ //this.chartHeight = UNDEFINED;
9732
+ //this.marginRight = UNDEFINED;
9733
+ //this.marginBottom = UNDEFINED;
9734
+ //this.containerWidth = UNDEFINED;
9735
+ //this.containerHeight = UNDEFINED;
9736
+ //this.oldChartWidth = UNDEFINED;
9737
+ //this.oldChartHeight = UNDEFINED;
9738
+
9739
+ //this.renderTo = UNDEFINED;
9740
+ //this.renderToClone = UNDEFINED;
9741
+ //this.tracker = UNDEFINED;
9742
+
9743
+ //this.spacingBox = UNDEFINED
9744
+
9745
+ //this.legend = UNDEFINED;
9746
+
9747
+ // Elements
9748
+ //this.chartBackground = UNDEFINED;
9749
+ //this.plotBackground = UNDEFINED;
9750
+ //this.plotBGImage = UNDEFINED;
9751
+ //this.plotBorder = UNDEFINED;
9752
+ //this.loadingDiv = UNDEFINED;
9753
+ //this.loadingSpan = UNDEFINED;
9754
+
9755
+ var chart = this,
9756
+ eventType;
9757
+
9758
+ // Add the chart to the global lookup
9759
+ chart.index = charts.length;
9760
+ charts.push(chart);
9761
+
9762
+ // Set up auto resize
9763
+ if (optionsChart.reflow !== false) {
9764
+ addEvent(chart, 'load', chart.initReflow);
9765
+ }
9766
+
9767
+ // Chart event handlers
9768
+ if (chartEvents) {
9769
+ for (eventType in chartEvents) {
9770
+ addEvent(chart, eventType, chartEvents[eventType]);
9771
+ }
9772
+ }
9773
+
9774
+ chart.xAxis = [];
9775
+ chart.yAxis = [];
9776
+
9777
+ // Expose methods and variables
9778
+ chart.animation = useCanVG ? false : pick(optionsChart.animation, true);
9779
+ chart.pointCount = 0;
9780
+ chart.counters = new ChartCounters();
9781
+
9782
+ chart.firstRender();
9783
+ },
9784
+
9655
9785
  /**
9656
9786
  * Initialize an individual series, called internally before render time
9657
9787
  */
@@ -9822,13 +9952,13 @@ Chart.prototype = {
9822
9952
 
9823
9953
 
9824
9954
  }
9825
-
9826
9955
  // the plot areas size has changed
9827
9956
  if (isDirtyBox) {
9828
9957
  chart.drawChartBox();
9829
9958
  }
9830
9959
 
9831
9960
 
9961
+
9832
9962
  // redraw affected series
9833
9963
  each(series, function (serie) {
9834
9964
  if (serie.isDirty && serie.visible &&
@@ -9837,7 +9967,6 @@ Chart.prototype = {
9837
9967
  }
9838
9968
  });
9839
9969
 
9840
-
9841
9970
  // move tooltip or reset
9842
9971
  if (tracker && tracker.resetTracker) {
9843
9972
  tracker.resetTracker(true);
@@ -10191,10 +10320,10 @@ Chart.prototype = {
10191
10320
  chart.containerWidth = adapterRun(renderTo, 'width');
10192
10321
  chart.containerHeight = adapterRun(renderTo, 'height');
10193
10322
 
10194
- chart.chartWidth = optionsChart.width || chart.containerWidth || 600;
10195
- chart.chartHeight = optionsChart.height ||
10323
+ chart.chartWidth = mathMax(0, pick(optionsChart.width, chart.containerWidth, 600));
10324
+ chart.chartHeight = mathMax(0, pick(optionsChart.height,
10196
10325
  // the offsetHeight of an empty container is 0 in standard browsers, but 19 in IE7:
10197
- (chart.containerHeight > 19 ? chart.containerHeight : 400);
10326
+ chart.containerHeight > 19 ? chart.containerHeight : 400));
10198
10327
  },
10199
10328
 
10200
10329
  /**
@@ -10242,6 +10371,8 @@ Chart.prototype = {
10242
10371
  chartWidth,
10243
10372
  chartHeight,
10244
10373
  renderTo,
10374
+ indexAttrName = 'data-highcharts-chart',
10375
+ oldChartIndex,
10245
10376
  containerId;
10246
10377
 
10247
10378
  chart.renderTo = renderTo = optionsChart.renderTo;
@@ -10255,6 +10386,15 @@ Chart.prototype = {
10255
10386
  if (!renderTo) {
10256
10387
  error(13, true);
10257
10388
  }
10389
+
10390
+ // If the container already holds a chart, destroy it
10391
+ oldChartIndex = pInt(attr(renderTo, indexAttrName));
10392
+ if (!isNaN(oldChartIndex) && charts[oldChartIndex]) {
10393
+ charts[oldChartIndex].destroy();
10394
+ }
10395
+
10396
+ // Make a reference to the chart from the div
10397
+ attr(renderTo, indexAttrName, chart.index);
10258
10398
 
10259
10399
  // remove previous chart
10260
10400
  renderTo.innerHTML = '';
@@ -10427,13 +10567,14 @@ Chart.prototype = {
10427
10567
 
10428
10568
  // Width and height checks for display:none. Target is doc in IE8 and Opera,
10429
10569
  // win in Firefox, Chrome and IE9.
10430
- if (width && height && (target === win || target === doc)) {
10570
+ if (!chart.hasUserSize && width && height && (target === win || target === doc)) {
10431
10571
 
10432
10572
  if (width !== chart.containerWidth || height !== chart.containerHeight) {
10433
10573
  clearTimeout(reflowTimeout);
10434
10574
  chart.reflowTimeout = reflowTimeout = setTimeout(function () {
10435
10575
  if (chart.container) { // It may have been destroyed in the meantime (#1257)
10436
- chart.resize(width, height, false);
10576
+ chart.setSize(width, height, false);
10577
+ chart.hasUserSize = null;
10437
10578
  }
10438
10579
  }, 100);
10439
10580
  }
@@ -10453,8 +10594,7 @@ Chart.prototype = {
10453
10594
  * @param {Number} height
10454
10595
  * @param {Object|Boolean} animation
10455
10596
  */
10456
- // TODO: This method is called setSize in the api
10457
- resize: function (width, height, animation) {
10597
+ setSize: function (width, height, animation) {
10458
10598
  var chart = this,
10459
10599
  chartWidth,
10460
10600
  chartHeight,
@@ -10480,10 +10620,11 @@ Chart.prototype = {
10480
10620
  chart.oldChartHeight = chart.chartHeight;
10481
10621
  chart.oldChartWidth = chart.chartWidth;
10482
10622
  if (defined(width)) {
10483
- chart.chartWidth = chartWidth = mathRound(width);
10623
+ chart.chartWidth = chartWidth = mathMax(0, mathRound(width));
10624
+ chart.hasUserSize = !!chartWidth;
10484
10625
  }
10485
10626
  if (defined(height)) {
10486
- chart.chartHeight = chartHeight = mathRound(height);
10627
+ chart.chartHeight = chartHeight = mathMax(0, mathRound(height));
10487
10628
  }
10488
10629
 
10489
10630
  css(chart.container, {
@@ -10564,8 +10705,8 @@ Chart.prototype = {
10564
10705
 
10565
10706
  chart.plotLeft = plotLeft = mathRound(chart.plotLeft);
10566
10707
  chart.plotTop = plotTop = mathRound(chart.plotTop);
10567
- chart.plotWidth = plotWidth = mathRound(chartWidth - plotLeft - chart.marginRight);
10568
- chart.plotHeight = plotHeight = mathRound(chartHeight - plotTop - chart.marginBottom);
10708
+ chart.plotWidth = plotWidth = mathMax(0, mathRound(chartWidth - plotLeft - chart.marginRight));
10709
+ chart.plotHeight = plotHeight = mathMax(0, mathRound(chartHeight - plotTop - chart.marginBottom));
10569
10710
 
10570
10711
  chart.plotSizeX = inverted ? plotHeight : plotWidth;
10571
10712
  chart.plotSizeY = inverted ? plotWidth : plotHeight;
@@ -10884,9 +11025,13 @@ Chart.prototype = {
10884
11025
  container = chart.container,
10885
11026
  i,
10886
11027
  parentNode = container && container.parentNode;
10887
-
11028
+
10888
11029
  // fire the chart.destoy event
10889
11030
  fireEvent(chart, 'destroy');
11031
+
11032
+ // Delete the chart from charts lookup array
11033
+ charts[chart.index] = UNDEFINED;
11034
+ chart.renderTo.removeAttribute('data-highcharts-chart');
10890
11035
 
10891
11036
  // remove events
10892
11037
  removeEvent(chart);
@@ -10905,7 +11050,9 @@ Chart.prototype = {
10905
11050
  }
10906
11051
 
10907
11052
  // ==== Destroy chart properties:
10908
- each(['title', 'subtitle', 'chartBackground', 'plotBackground', 'plotBGImage', 'plotBorder', 'seriesGroup', 'clipRect', 'credits', 'tracker', 'scroller', 'rangeSelector', 'legend', 'resetZoomButton', 'tooltip', 'renderer'], function (name) {
11053
+ each(['title', 'subtitle', 'chartBackground', 'plotBackground', 'plotBGImage',
11054
+ 'plotBorder', 'seriesGroup', 'clipRect', 'credits', 'tracker', 'scroller',
11055
+ 'rangeSelector', 'legend', 'resetZoomButton', 'tooltip', 'renderer'], function (name) {
10909
11056
  var prop = chart[name];
10910
11057
 
10911
11058
  if (prop && prop.destroy) {
@@ -10930,37 +11077,48 @@ Chart.prototype = {
10930
11077
 
10931
11078
  },
10932
11079
 
11080
+
10933
11081
  /**
10934
- * Prepare for first rendering after all data are loaded
11082
+ * VML namespaces can't be added until after complete. Listening
11083
+ * for Perini's doScroll hack is not enough.
10935
11084
  */
10936
- firstRender: function () {
10937
- var chart = this,
10938
- options = chart.options,
10939
- callback = chart.callback;
11085
+ isReadyToRender: function () {
11086
+ var chart = this;
10940
11087
 
10941
- // VML namespaces can't be added until after complete. Listening
10942
- // for Perini's doScroll hack is not enough.
10943
- var ONREADYSTATECHANGE = 'onreadystatechange',
10944
- COMPLETE = 'complete';
10945
11088
  // Note: in spite of JSLint's complaints, win == win.top is required
10946
11089
  /*jslint eqeq: true*/
10947
- if ((!hasSVG && (win == win.top && doc.readyState !== COMPLETE)) || (useCanVG && !win.canvg)) {
11090
+ if ((!hasSVG && (win == win.top && doc.readyState !== 'complete')) || (useCanVG && !win.canvg)) {
10948
11091
  /*jslint eqeq: false*/
10949
11092
  if (useCanVG) {
10950
11093
  // Delay rendering until canvg library is downloaded and ready
10951
- CanVGController.push(function () { chart.firstRender(); }, options.global.canvasToolsURL);
11094
+ CanVGController.push(function () { chart.firstRender(); }, chart.options.global.canvasToolsURL);
10952
11095
  } else {
10953
- doc.attachEvent(ONREADYSTATECHANGE, function () {
10954
- doc.detachEvent(ONREADYSTATECHANGE, chart.firstRender);
10955
- if (doc.readyState === COMPLETE) {
11096
+ doc.attachEvent('onreadystatechange', function () {
11097
+ doc.detachEvent('onreadystatechange', chart.firstRender);
11098
+ if (doc.readyState === 'complete') {
10956
11099
  chart.firstRender();
10957
11100
  }
10958
11101
  });
10959
11102
  }
11103
+ return false;
11104
+ }
11105
+ return true;
11106
+ },
11107
+
11108
+ /**
11109
+ * Prepare for first rendering after all data are loaded
11110
+ */
11111
+ firstRender: function () {
11112
+ var chart = this,
11113
+ options = chart.options,
11114
+ callback = chart.callback;
11115
+
11116
+ // Check whether the chart is ready to render
11117
+ if (!chart.isReadyToRender()) {
10960
11118
  return;
10961
11119
  }
10962
11120
 
10963
- // create the container
11121
+ // Create the container
10964
11122
  chart.getContainer();
10965
11123
 
10966
11124
  // Run an early event after the container and renderer are established
@@ -11014,73 +11172,6 @@ Chart.prototype = {
11014
11172
 
11015
11173
  fireEvent(chart, 'load');
11016
11174
 
11017
- },
11018
-
11019
- init: function (chartEvents) {
11020
- var chart = this,
11021
- optionsChart = chart.options.chart,
11022
- eventType;
11023
-
11024
- // Run chart
11025
-
11026
- // Set up auto resize
11027
- if (optionsChart.reflow !== false) {
11028
- addEvent(chart, 'load', chart.initReflow);
11029
- }
11030
-
11031
- // Chart event handlers
11032
- if (chartEvents) {
11033
- for (eventType in chartEvents) {
11034
- addEvent(chart, eventType, chartEvents[eventType]);
11035
- }
11036
- }
11037
-
11038
- chart.xAxis = [];
11039
- chart.yAxis = [];
11040
-
11041
- // Expose methods and variables
11042
- chart.animation = useCanVG ? false : pick(optionsChart.animation, true);
11043
- chart.setSize = chart.resize;
11044
- chart.pointCount = 0;
11045
- chart.counters = new ChartCounters();
11046
- /*
11047
- if ($) $(function () {
11048
- $container = $('#container');
11049
- var origChartWidth,
11050
- origChartHeight;
11051
- if ($container) {
11052
- $('<button>+</button>')
11053
- .insertBefore($container)
11054
- .click(function () {
11055
- if (origChartWidth === UNDEFINED) {
11056
- origChartWidth = chartWidth;
11057
- origChartHeight = chartHeight;
11058
- }
11059
- chart.resize(chartWidth *= 1.1, chartHeight *= 1.1);
11060
- });
11061
- $('<button>-</button>')
11062
- .insertBefore($container)
11063
- .click(function () {
11064
- if (origChartWidth === UNDEFINED) {
11065
- origChartWidth = chartWidth;
11066
- origChartHeight = chartHeight;
11067
- }
11068
- chart.resize(chartWidth *= 0.9, chartHeight *= 0.9);
11069
- });
11070
- $('<button>1:1</button>')
11071
- .insertBefore($container)
11072
- .click(function () {
11073
- if (origChartWidth === UNDEFINED) {
11074
- origChartWidth = chartWidth;
11075
- origChartHeight = chartHeight;
11076
- }
11077
- chart.resize(origChartWidth, origChartHeight);
11078
- });
11079
- }
11080
- })
11081
- */
11082
-
11083
- chart.firstRender();
11084
11175
  }
11085
11176
  }; // end Chart
11086
11177
 
@@ -11108,7 +11199,6 @@ Point.prototype = {
11108
11199
  if (series.options.colorByPoint) {
11109
11200
  defaultColors = series.chart.options.colors;
11110
11201
  point.color = point.color || defaultColors[counters.color++];
11111
-
11112
11202
  // loop back to zero
11113
11203
  counters.wrapColor(defaultColors.length);
11114
11204
  }
@@ -11212,7 +11302,7 @@ Point.prototype = {
11212
11302
  */
11213
11303
  destroyElements: function () {
11214
11304
  var point = this,
11215
- props = ['graphic', 'tracker', 'dataLabel', 'group', 'connector', 'shadowGroup'],
11305
+ props = ['graphic', 'tracker', 'dataLabel', 'dataLabelUpper', 'group', 'connector', 'shadowGroup'],
11216
11306
  prop,
11217
11307
  i = 6;
11218
11308
  while (i--) {
@@ -11337,7 +11427,7 @@ Point.prototype = {
11337
11427
 
11338
11428
  // Backwards compatibility to y naming in early Highstock
11339
11429
  seriesTooltipOptions.valuePrefix = seriesTooltipOptions.valuePrefix || seriesTooltipOptions.yPrefix;
11340
- seriesTooltipOptions.valueDecimals = seriesTooltipOptions.valueDecimals || seriesTooltipOptions.yDecimals;
11430
+ seriesTooltipOptions.valueDecimals = pick(seriesTooltipOptions.valueDecimals, seriesTooltipOptions.yDecimals); // #1248
11341
11431
  seriesTooltipOptions.valueSuffix = seriesTooltipOptions.valueSuffix || seriesTooltipOptions.ySuffix;
11342
11432
 
11343
11433
  // loop over the variables defined on the form {series.name}, {point.y} etc
@@ -11406,7 +11496,7 @@ Point.prototype = {
11406
11496
  for (i = 0; i < dataLength; i++) {
11407
11497
  if (data[i] === point) {
11408
11498
  series.xData[i] = point.x;
11409
- series.yData[i] = point.y;
11499
+ series.yData[i] = point.toYData ? point.toYData() : point.y;
11410
11500
  series.options.data[i] = options;
11411
11501
  break;
11412
11502
  }
@@ -11624,6 +11714,7 @@ Series.prototype = {
11624
11714
  type: 'line',
11625
11715
  pointClass: Point,
11626
11716
  sorted: true, // requires the data to be sorted
11717
+ requireSorting: true,
11627
11718
  pointAttrToOptions: { // mapping between SVG attributes and the corresponding options
11628
11719
  stroke: 'lineColor',
11629
11720
  'stroke-width': 'lineWidth',
@@ -11918,6 +12009,7 @@ Series.prototype = {
11918
12009
  */
11919
12010
  addPoint: function (options, redraw, shift, animation) {
11920
12011
  var series = this,
12012
+ seriesOptions = series.options,
11921
12013
  data = series.data,
11922
12014
  graph = series.graph,
11923
12015
  area = series.area,
@@ -11925,7 +12017,7 @@ Series.prototype = {
11925
12017
  xData = series.xData,
11926
12018
  yData = series.yData,
11927
12019
  currentShift = (graph && graph.shift) || 0,
11928
- dataOptions = series.options.data,
12020
+ dataOptions = seriesOptions.data,
11929
12021
  point,
11930
12022
  proto = series.pointClass.prototype;
11931
12023
 
@@ -11953,6 +12045,10 @@ Series.prototype = {
11953
12045
  yData.push(proto.toYData ? proto.toYData.call(point) : point.y);
11954
12046
  dataOptions.push(options);
11955
12047
 
12048
+ // Generate points to be added to the legend (#1329)
12049
+ if (seriesOptions.legendType === 'point') {
12050
+ series.generatePoints();
12051
+ }
11956
12052
 
11957
12053
  // Shift the first point off the parallel arrays
11958
12054
  // todo: consider series.removePoint(i) method
@@ -12058,6 +12154,12 @@ Series.prototype = {
12058
12154
  yData[i] = pointProto.toYData ? pointProto.toYData.call(pt) : pt.y;
12059
12155
  }
12060
12156
  }
12157
+
12158
+ // Unsorted data is not supported by the line tooltip as well as data grouping and
12159
+ // navigation in Stock charts (#725)
12160
+ if (series.requireSorting && xData.length > 1 && xData[1] < xData[0]) {
12161
+ error(15);
12162
+ }
12061
12163
 
12062
12164
  // Forgetting to cast strings to numbers is a common caveat when handling CSV or JSON
12063
12165
  if (isString(yData[0])) {
@@ -12461,10 +12563,6 @@ Series.prototype = {
12461
12563
  chart = series.chart,
12462
12564
  hoverSeries = chart.hoverSeries;
12463
12565
 
12464
- /*if (!hasTouch && chart.mouseIsDown) {
12465
- return;
12466
- }*/
12467
-
12468
12566
  // set normal state to previous series
12469
12567
  if (hoverSeries && hoverSeries !== series) {
12470
12568
  hoverSeries.onMouseOut();
@@ -12584,10 +12682,14 @@ Series.prototype = {
12584
12682
  afterAnimate: function () {
12585
12683
  var chart = this.chart,
12586
12684
  sharedClipKey = this.sharedClipKey,
12587
- group = this.group;
12685
+ group = this.group,
12686
+ trackerGroup = this.trackerGroup;
12588
12687
 
12589
12688
  if (group && this.options.clip !== false) {
12590
12689
  group.clip(chart.clipRect);
12690
+ if (trackerGroup) {
12691
+ trackerGroup.clip(chart.clipRect); // #484
12692
+ }
12591
12693
  this.markerGroup.clip(); // no clip
12592
12694
  }
12593
12695
 
@@ -12928,7 +13030,7 @@ Series.prototype = {
12928
13030
  'dataLabelsGroup',
12929
13031
  'data-labels',
12930
13032
  series.visible ? VISIBLE : HIDDEN,
12931
- 6
13033
+ options.zIndex || 6
12932
13034
  );
12933
13035
 
12934
13036
  // Make the labels for each point
@@ -13061,7 +13163,7 @@ Series.prototype = {
13061
13163
  // Show or hide based on the final aligned position
13062
13164
  dataLabel.attr({
13063
13165
  visibility: options.crop === false || chart.isInsidePlot(alignAttr.x, alignAttr.y) || chart.isInsidePlot(plotX, plotY, inverted) ?
13064
- (hasSVG ? 'inherit' : VISIBLE) :
13166
+ (chart.renderer.isSVG ? 'inherit' : VISIBLE) :
13065
13167
  HIDDEN
13066
13168
  });
13067
13169
 
@@ -13072,10 +13174,15 @@ Series.prototype = {
13072
13174
  */
13073
13175
  getSegmentPath: function (segment) {
13074
13176
  var series = this,
13075
- segmentPath = [];
13076
-
13177
+ segmentPath = [],
13178
+ step = series.options.step;
13179
+
13077
13180
  // build the segment line
13078
13181
  each(segment, function (point, i) {
13182
+
13183
+ var plotX = point.plotX,
13184
+ plotY = point.plotY,
13185
+ lastPoint;
13079
13186
 
13080
13187
  if (series.getPointSpline) { // generate the spline as defined in the SplineSeries object
13081
13188
  segmentPath.push.apply(segmentPath, series.getPointSpline(segment, point, i));
@@ -13086,12 +13193,28 @@ Series.prototype = {
13086
13193
  segmentPath.push(i ? L : M);
13087
13194
 
13088
13195
  // step line?
13089
- if (i && series.options.step) {
13090
- var lastPoint = segment[i - 1];
13091
- segmentPath.push(
13092
- point.plotX,
13093
- lastPoint.plotY
13094
- );
13196
+ if (step && i) {
13197
+ lastPoint = segment[i - 1];
13198
+ if (step === 'right') {
13199
+ segmentPath.push(
13200
+ lastPoint.plotX,
13201
+ plotY
13202
+ );
13203
+
13204
+ } else if (step === 'center') {
13205
+ segmentPath.push(
13206
+ (lastPoint.plotX + plotX) / 2,
13207
+ lastPoint.plotY,
13208
+ (lastPoint.plotX + plotX) / 2,
13209
+ plotY
13210
+ );
13211
+
13212
+ } else {
13213
+ segmentPath.push(
13214
+ plotX,
13215
+ lastPoint.plotY
13216
+ );
13217
+ }
13095
13218
  }
13096
13219
 
13097
13220
  // normal line to next point
@@ -13427,6 +13550,11 @@ Series.prototype = {
13427
13550
  }
13428
13551
  }
13429
13552
  }
13553
+
13554
+ // hide tooltip (#1361)
13555
+ if (chart.hoverSeries === series) {
13556
+ series.onMouseOut();
13557
+ }
13430
13558
 
13431
13559
 
13432
13560
  if (dataLabelsGroup) {
@@ -13513,7 +13641,17 @@ Series.prototype = {
13513
13641
  singlePoints = series.singlePoints,
13514
13642
  trackerGroup = this.isCartesian && this.plotGroup('trackerGroup', null, VISIBLE, options.zIndex || 1, chart.trackerGroup),
13515
13643
  singlePoint,
13516
- i;
13644
+ i,
13645
+ onMouseOver = function () {
13646
+ if (chart.hoverSeries !== series) {
13647
+ series.onMouseOver();
13648
+ }
13649
+ },
13650
+ onMouseOut = function () {
13651
+ if (!options.stickyTracking) {
13652
+ series.onMouseOut();
13653
+ }
13654
+ };
13517
13655
 
13518
13656
  // Extend end points. A better way would be to use round linecaps,
13519
13657
  // but those are not clickable in VML.
@@ -13544,27 +13682,23 @@ Series.prototype = {
13544
13682
 
13545
13683
  } else { // create
13546
13684
 
13547
- series.tracker = renderer.path(trackerPath)
13685
+ series.tracker = tracker = renderer.path(trackerPath)
13548
13686
  .attr({
13549
13687
  isTracker: true,
13550
- 'stroke-linejoin': 'bevel',
13688
+ 'stroke-linejoin': 'round', // #1225
13551
13689
  visibility: series.visible ? VISIBLE : HIDDEN,
13552
13690
  stroke: TRACKER_FILL,
13553
13691
  fill: trackByArea ? TRACKER_FILL : NONE,
13554
13692
  'stroke-width' : options.lineWidth + (trackByArea ? 0 : 2 * snap)
13555
13693
  })
13556
- .on(hasTouch ? 'touchstart' : 'mouseover', function () {
13557
- if (chart.hoverSeries !== series) {
13558
- series.onMouseOver();
13559
- }
13560
- })
13561
- .on('mouseout', function () {
13562
- if (!options.stickyTracking) {
13563
- series.onMouseOut();
13564
- }
13565
- })
13694
+ .on('mouseover', onMouseOver)
13695
+ .on('mouseout', onMouseOut)
13566
13696
  .css(css)
13567
13697
  .add(trackerGroup);
13698
+
13699
+ if (hasTouch) {
13700
+ tracker.on('touchstart', onMouseOver);
13701
+ }
13568
13702
  }
13569
13703
 
13570
13704
  }
@@ -13928,6 +14062,7 @@ var ColumnSeries = extendClass(Series, {
13928
14062
  borderWidth = options.borderWidth,
13929
14063
  columnCount = 0,
13930
14064
  xAxis = series.xAxis,
14065
+ yAxis = series.yAxis,
13931
14066
  reversedXAxis = xAxis.reversed,
13932
14067
  stackGroups = {},
13933
14068
  stackKey,
@@ -13940,7 +14075,6 @@ var ColumnSeries = extendClass(Series, {
13940
14075
  // chart.orderStacks() function and call it on init, addSeries and removeSeries
13941
14076
  if (options.grouping === false) {
13942
14077
  columnCount = 1;
13943
-
13944
14078
  } else {
13945
14079
  each(chart.series, function (otherSeries) {
13946
14080
  var otherOptions = otherSeries.options;
@@ -13973,23 +14107,24 @@ var ColumnSeries = extendClass(Series, {
13973
14107
  pointOffsetWidth * options.pointPadding,
13974
14108
  pointWidth = pick(optionPointWidth, pointOffsetWidth - 2 * pointPadding), // exact point width, used in polar charts
13975
14109
  barW = mathCeil(mathMax(pointWidth, 1 + 2 * borderWidth)), // rounded and postprocessed for border width
13976
- colIndex = (reversedXAxis ? columnCount -
13977
- series.columnIndex : series.columnIndex) || 0,
14110
+ colIndex = (reversedXAxis ?
14111
+ columnCount - (series.columnIndex || 0) : // #1251
14112
+ series.columnIndex) || 0,
13978
14113
  pointXOffset = pointPadding + (groupPadding + colIndex *
13979
14114
  pointOffsetWidth - (categoryWidth / 2)) *
13980
14115
  (reversedXAxis ? -1 : 1),
13981
14116
  threshold = options.threshold,
13982
- translatedThreshold = series.translatedThreshold = series.yAxis.getThreshold(threshold),
14117
+ translatedThreshold = series.translatedThreshold = yAxis.getThreshold(threshold),
13983
14118
  minPointLength = pick(options.minPointLength, 5);
13984
14119
 
13985
14120
  // record the new values
13986
14121
  each(points, function (point) {
13987
- var plotY = point.plotY,
14122
+ var plotY = mathMin(mathMax(-999, point.plotY), yAxis.len + 999), // Don't draw too far outside plot area (#1303)
13988
14123
  yBottom = pick(point.yBottom, translatedThreshold),
13989
14124
  barX = point.plotX + pointXOffset,
13990
14125
  barY = mathCeil(mathMin(plotY, yBottom)),
13991
14126
  barH = mathCeil(mathMax(plotY, yBottom) - barY),
13992
- stack = series.yAxis.stacks[(point.y < 0 ? '-' : '') + series.stackKey],
14127
+ stack = yAxis.stacks[(point.y < 0 ? '-' : '') + series.stackKey],
13993
14128
  shapeArgs;
13994
14129
 
13995
14130
  // Record the offset'ed position and width of the bar to be able to align the stacking total correctly
@@ -14078,7 +14213,7 @@ var ColumnSeries = extendClass(Series, {
14078
14213
  },
14079
14214
  /**
14080
14215
  * Draw the individual tracker elements.
14081
- * This method is inherited by scatter and pie charts too.
14216
+ * This method is inherited by pie charts too.
14082
14217
  */
14083
14218
  drawTracker: function () {
14084
14219
  var series = this,
@@ -14093,9 +14228,28 @@ var ColumnSeries = extendClass(Series, {
14093
14228
  trackerGroup = series.isCartesian && series.plotGroup('trackerGroup', null, VISIBLE, options.zIndex || 1, chart.trackerGroup),
14094
14229
  rel,
14095
14230
  plotY,
14096
- validPlotY;
14231
+ validPlotY,
14232
+ points = series.points,
14233
+ point,
14234
+ i = points.length,
14235
+ onMouseOver = function (event) {
14236
+ rel = event.relatedTarget || event.fromElement;
14237
+ if (chart.hoverSeries !== series && attr(rel, 'isTracker') !== trackerLabel) {
14238
+ series.onMouseOver();
14239
+ }
14240
+ points[event.target._i].onMouseOver();
14241
+ },
14242
+ onMouseOut = function (event) {
14243
+ if (!options.stickyTracking) {
14244
+ rel = event.relatedTarget || event.toElement;
14245
+ if (attr(rel, 'isTracker') !== trackerLabel) {
14246
+ series.onMouseOut();
14247
+ }
14248
+ }
14249
+ };
14097
14250
 
14098
- each(series.points, function (point) {
14251
+ while (i--) {
14252
+ point = points[i];
14099
14253
  tracker = point.tracker;
14100
14254
  shapeArgs = point.trackerArgs || point.shapeArgs;
14101
14255
  plotY = point.plotY;
@@ -14106,34 +14260,25 @@ var ColumnSeries = extendClass(Series, {
14106
14260
  tracker.attr(shapeArgs);
14107
14261
 
14108
14262
  } else {
14109
- point.tracker =
14263
+ point.tracker = tracker =
14110
14264
  renderer[point.shapeType](shapeArgs)
14111
14265
  .attr({
14112
14266
  isTracker: trackerLabel,
14113
14267
  fill: TRACKER_FILL,
14114
14268
  visibility: series.visible ? VISIBLE : HIDDEN
14115
14269
  })
14116
- .on(hasTouch ? 'touchstart' : 'mouseover', function (event) {
14117
- rel = event.relatedTarget || event.fromElement;
14118
- if (chart.hoverSeries !== series && attr(rel, 'isTracker') !== trackerLabel) {
14119
- series.onMouseOver();
14120
- }
14121
- point.onMouseOver();
14122
-
14123
- })
14124
- .on('mouseout', function (event) {
14125
- if (!options.stickyTracking) {
14126
- rel = event.relatedTarget || event.toElement;
14127
- if (attr(rel, 'isTracker') !== trackerLabel) {
14128
- series.onMouseOut();
14129
- }
14130
- }
14131
- })
14270
+ .on('mouseover', onMouseOver)
14271
+ .on('mouseout', onMouseOut)
14132
14272
  .css(css)
14133
14273
  .add(point.group || trackerGroup); // pies have point group - see issue #118
14274
+
14275
+ if (hasTouch) {
14276
+ tracker.on('touchstart', onMouseOver);
14277
+ }
14134
14278
  }
14279
+ tracker.element._i = i;
14135
14280
  }
14136
- });
14281
+ }
14137
14282
  },
14138
14283
 
14139
14284
  /**
@@ -14142,7 +14287,7 @@ var ColumnSeries = extendClass(Series, {
14142
14287
  alignDataLabel: function (point, dataLabel, options, alignTo, isNew) {
14143
14288
  var chart = this.chart,
14144
14289
  inverted = chart.inverted,
14145
- below = point.below || (point.plotY > (this.translatedThreshold || chart.plotSizeY)),
14290
+ below = point.below || (point.plotY > pick(this.translatedThreshold, chart.plotSizeY)),
14146
14291
  inside = (this.options.stacking || options.inside); // draw it inside the box?
14147
14292
 
14148
14293
  // Align to the column itself, or the top of it
@@ -14232,6 +14377,7 @@ var ColumnSeries = extendClass(Series, {
14232
14377
  }
14233
14378
 
14234
14379
  },
14380
+
14235
14381
  /**
14236
14382
  * Remove this series from the chart
14237
14383
  */
@@ -14288,6 +14434,7 @@ defaultPlotOptions.scatter = merge(defaultSeriesOptions, {
14288
14434
  var ScatterSeries = extendClass(Series, {
14289
14435
  type: 'scatter',
14290
14436
  sorted: false,
14437
+ requireSorting: false,
14291
14438
  /**
14292
14439
  * Extend the base Series' translate method by adding shape type and
14293
14440
  * arguments for the point trackers
@@ -14317,7 +14464,19 @@ var ScatterSeries = extendClass(Series, {
14317
14464
  css = cursor && { cursor: cursor },
14318
14465
  points = series.points,
14319
14466
  i = points.length,
14320
- graphic;
14467
+ graphic,
14468
+ markerGroup = series.markerGroup,
14469
+ onMouseOver = function (e) {
14470
+ series.onMouseOver();
14471
+ if (e.target._i !== UNDEFINED) { // undefined on graph in scatterchart
14472
+ points[e.target._i].onMouseOver();
14473
+ }
14474
+ },
14475
+ onMouseOut = function () {
14476
+ if (!series.options.stickyTracking) {
14477
+ series.onMouseOut();
14478
+ }
14479
+ };
14321
14480
 
14322
14481
  // Set an expando property for the point index, used below
14323
14482
  while (i--) {
@@ -14329,26 +14488,23 @@ var ScatterSeries = extendClass(Series, {
14329
14488
 
14330
14489
  // Add the event listeners, we need to do this only once
14331
14490
  if (!series._hasTracking) {
14332
- series.markerGroup
14491
+ markerGroup
14333
14492
  .attr({
14334
14493
  isTracker: true
14335
14494
  })
14336
- .on(hasTouch ? 'touchstart' : 'mouseover', function (e) {
14337
- series.onMouseOver();
14338
- if (e.target._i !== UNDEFINED) { // undefined on graph in scatterchart
14339
- points[e.target._i].onMouseOver();
14340
- }
14341
- })
14342
- .on('mouseout', function () {
14343
- if (!series.options.stickyTracking) {
14344
- series.onMouseOut();
14345
- }
14346
- })
14495
+ .on('mouseover', onMouseOver)
14496
+ .on('mouseout', onMouseOut)
14347
14497
  .css(css);
14498
+ if (hasTouch) {
14499
+ markerGroup.on('touchstart', onMouseOver);
14500
+ }
14501
+
14348
14502
  } else {
14349
14503
  series._hasTracking = true;
14350
14504
  }
14351
- }
14505
+ },
14506
+
14507
+ setTooltipPoints: noop
14352
14508
  });
14353
14509
  seriesTypes.scatter = ScatterSeries;
14354
14510
 
@@ -14500,6 +14656,7 @@ var PieSeries = {
14500
14656
  type: 'pie',
14501
14657
  isCartesian: false,
14502
14658
  pointClass: PiePoint,
14659
+ requireSorting: false,
14503
14660
  pointAttrToOptions: { // mapping between SVG attributes and the corresponding options
14504
14661
  stroke: 'borderColor',
14505
14662
  'stroke-width': 'borderWidth',
@@ -14519,19 +14676,19 @@ var PieSeries = {
14519
14676
  */
14520
14677
  animate: function () {
14521
14678
  var series = this,
14522
- points = series.points;
14679
+ points = series.points,
14680
+ startAngleRad = series.startAngleRad;
14523
14681
 
14524
14682
  each(points, function (point) {
14525
14683
  var graphic = point.graphic,
14526
- args = point.shapeArgs,
14527
- up = -mathPI / 2;
14684
+ args = point.shapeArgs;
14528
14685
 
14529
14686
  if (graphic) {
14530
14687
  // start values
14531
14688
  graphic.attr({
14532
- r: 0,
14533
- start: up,
14534
- end: up
14689
+ r: series.center[3] / 2, // animate from inner radius (#779)
14690
+ start: startAngleRad,
14691
+ end: startAngleRad
14535
14692
  });
14536
14693
 
14537
14694
  // animate
@@ -14597,7 +14754,7 @@ var PieSeries = {
14597
14754
 
14598
14755
  var total = 0,
14599
14756
  series = this,
14600
- cumulative = -0.25, // start at top
14757
+ cumulative = 0,
14601
14758
  precision = 1000, // issue #172
14602
14759
  options = series.options,
14603
14760
  slicedOffset = options.slicedOffset,
@@ -14607,6 +14764,7 @@ var PieSeries = {
14607
14764
  start,
14608
14765
  end,
14609
14766
  angle,
14767
+ startAngleRad = series.startAngleRad = mathPI / 180 * ((options.startAngle || 0) % 360 - 90),
14610
14768
  points = series.points,
14611
14769
  circ = 2 * mathPI,
14612
14770
  fraction,
@@ -14644,11 +14802,11 @@ var PieSeries = {
14644
14802
 
14645
14803
  // set start and end angle
14646
14804
  fraction = total ? point.y / total : 0;
14647
- start = mathRound(cumulative * circ * precision) / precision;
14805
+ start = mathRound((startAngleRad + (cumulative * circ)) * precision) / precision;
14648
14806
  if (!ignoreHiddenPoint || point.visible) {
14649
14807
  cumulative += fraction;
14650
14808
  }
14651
- end = mathRound(cumulative * circ * precision) / precision;
14809
+ end = mathRound((startAngleRad + (cumulative * circ)) * precision) / precision;
14652
14810
 
14653
14811
  // set the shape
14654
14812
  point.shapeType = 'arc';
@@ -14663,6 +14821,9 @@ var PieSeries = {
14663
14821
 
14664
14822
  // center for the sliced out slice
14665
14823
  angle = (end + start) / 2;
14824
+ if (angle > 0.75 * circ) {
14825
+ angle -= 2 * mathPI;
14826
+ }
14666
14827
  point.slicedTranslation = map([
14667
14828
  mathCos(angle) * slicedOffset + chart.plotLeft,
14668
14829
  mathSin(angle) * slicedOffset + chart.plotTop
@@ -14675,6 +14836,9 @@ var PieSeries = {
14675
14836
  positions[0] + radiusX * 0.7,
14676
14837
  positions[1] + radiusY * 0.7
14677
14838
  ];
14839
+
14840
+ point.half = angle < circ / 4 ? 0 : 1;
14841
+ point.angle = angle;
14678
14842
 
14679
14843
  // set the anchor point for data labels
14680
14844
  point.labelPos = [
@@ -14686,7 +14850,7 @@ var PieSeries = {
14686
14850
  positions[1] + radiusY, // a/a
14687
14851
  labelDistance < 0 ? // alignment
14688
14852
  'center' :
14689
- angle < circ / 4 ? 'left' : 'right', // alignment
14853
+ point.half ? 'right' : 'left', // alignment
14690
14854
  angle // center angle
14691
14855
  ];
14692
14856
 
@@ -14823,9 +14987,16 @@ var PieSeries = {
14823
14987
  y,
14824
14988
  visibility,
14825
14989
  rankArr,
14826
- sort,
14827
14990
  i = 2,
14828
- j;
14991
+ j,
14992
+ sort = function (a, b) {
14993
+ return b.y - a.y;
14994
+ },
14995
+ sortByAngle = function (points, sign) {
14996
+ points.sort(function (a, b) {
14997
+ return (b.angle - a.angle) * sign;
14998
+ });
14999
+ };
14829
15000
 
14830
15001
  // get out if not enabled
14831
15002
  if (!options.enabled && !series._hasPointLabels) {
@@ -14838,17 +15009,9 @@ var PieSeries = {
14838
15009
  // arrange points for detection collision
14839
15010
  each(data, function (point) {
14840
15011
  if (point.dataLabel) { // it may have been cancelled in the base method (#407)
14841
- halves[
14842
- point.labelPos[7] < mathPI / 2 ? 0 : 1
14843
- ].push(point);
15012
+ halves[point.half].push(point);
14844
15013
  }
14845
15014
  });
14846
- halves[1].reverse();
14847
-
14848
- // define the sorting algorithm
14849
- sort = function (a, b) {
14850
- return b.y - a.y;
14851
- };
14852
15015
 
14853
15016
  // assume equal label heights
14854
15017
  labelHeight = halves[0][0] && halves[0][0].dataLabel && (halves[0][0].dataLabel.getBBox().height || 21); // 21 is for #968
@@ -14865,6 +15028,9 @@ var PieSeries = {
14865
15028
  pos,
14866
15029
  length = points.length,
14867
15030
  slotIndex;
15031
+
15032
+ // Sort by angle
15033
+ sortByAngle(points, i - 0.5);
14868
15034
 
14869
15035
  // Only do anti-collision when we are outside the pie and have connectors (#856)
14870
15036
  if (distanceOption > 0) {
@@ -15081,10 +15247,14 @@ extend(Highcharts, {
15081
15247
  VMLRenderer: VMLRenderer,
15082
15248
 
15083
15249
  // Various
15250
+ arrayMin: arrayMin,
15251
+ arrayMax: arrayMax,
15252
+ charts: charts,
15084
15253
  dateFormat: dateFormat,
15085
15254
  pathAnim: pathAnim,
15086
15255
  getOptions: getOptions,
15087
15256
  hasBidiBug: hasBidiBug,
15257
+ isTouchDevice: isTouchDevice,
15088
15258
  numberFormat: numberFormat,
15089
15259
  seriesTypes: seriesTypes,
15090
15260
  setOptions: setOptions,
@@ -15106,6 +15276,6 @@ extend(Highcharts, {
15106
15276
  canvas: useCanVG,
15107
15277
  vml: !hasSVG && !useCanVG,
15108
15278
  product: 'Highcharts',
15109
- version: '2.3.3'
15279
+ version: '2.3.5'
15110
15280
  });
15111
15281
  }());