highcharts-js-rails 0.1.11 → 0.2.0

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