highcharts-js-rails 0.1.9 → 0.1.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,7 +2,7 @@
2
2
  // @compilation_level SIMPLE_OPTIMIZATIONS
3
3
 
4
4
  /**
5
- * @license Highcharts JS v2.2.5 (2012-06-08)
5
+ * @license Highcharts JS v2.3.2 (2012-08-31)
6
6
  *
7
7
  * (c) 2009-2011 Torstein Hønsi
8
8
  *
@@ -32,7 +32,8 @@ var UNDEFINED,
32
32
 
33
33
  // some variables
34
34
  userAgent = navigator.userAgent,
35
- isIE = /msie/i.test(userAgent) && !win.opera,
35
+ isOpera = win.opera,
36
+ isIE = /msie/i.test(userAgent) && !isOpera,
36
37
  docMode8 = doc.documentMode === 8,
37
38
  isWebKit = /AppleWebKit/.test(userAgent),
38
39
  isFirefox = /Firefox/.test(userAgent),
@@ -375,6 +376,23 @@ function pad(number, length) {
375
376
  return new Array((length || 2) + 1 - String(number).length).join(0) + number;
376
377
  }
377
378
 
379
+ /**
380
+ * Wrap a method with extended functionality, preserving the original function
381
+ * @param {Object} obj The context object that the method belongs to
382
+ * @param {String} method The name of the method to extend
383
+ * @param {Function} func A wrapper function callback. This function is called with the same arguments
384
+ * as the original function, except that the original function is unshifted and passed as the first
385
+ * argument.
386
+ */
387
+ function wrap(obj, method, func) {
388
+ var proceed = obj[method];
389
+ obj[method] = function () {
390
+ var args = Array.prototype.slice.call(arguments);
391
+ args.unshift(proceed);
392
+ return func.apply(this, args);
393
+ };
394
+ }
395
+
378
396
  /**
379
397
  * Based on http://www.php.net/manual/en/function.strftime.php
380
398
  * @param {String} format
@@ -836,6 +854,16 @@ function correctFloat(num) {
836
854
  );
837
855
  }
838
856
 
857
+ /**
858
+ * Set the global animation to either a given value, or fall back to the
859
+ * given chart's animation option
860
+ * @param {Object} animation
861
+ * @param {Object} chart
862
+ */
863
+ function setAnimation(animation, chart) {
864
+ globalAnimation = pick(animation, chart.animation);
865
+ }
866
+
839
867
  /**
840
868
  * The time unit lookup
841
869
  */
@@ -947,28 +975,338 @@ pathAnim = {
947
975
  }
948
976
  };
949
977
 
950
-
951
- /**
952
- * Set the global animation to either a given value, or fall back to the
953
- * given chart's animation option
954
- * @param {Object} animation
955
- * @param {Object} chart
956
- */
957
- function setAnimation(animation, chart) {
958
- globalAnimation = pick(animation, chart.animation);
959
- }
960
-
978
+ (function ($) {
979
+ /**
980
+ * The default HighchartsAdapter for jQuery
981
+ */
982
+ win.HighchartsAdapter = win.HighchartsAdapter || ($ && {
983
+
984
+ /**
985
+ * Initialize the adapter by applying some extensions to jQuery
986
+ */
987
+ init: function (pathAnim) {
988
+
989
+ // extend the animate function to allow SVG animations
990
+ var Fx = $.fx,
991
+ Step = Fx.step,
992
+ dSetter,
993
+ Tween = $.Tween,
994
+ propHooks = Tween && Tween.propHooks;
995
+
996
+ /*jslint unparam: true*//* allow unused param x in this function */
997
+ $.extend($.easing, {
998
+ easeOutQuad: function (x, t, b, c, d) {
999
+ return -c * (t /= d) * (t - 2) + b;
1000
+ }
1001
+ });
1002
+ /*jslint unparam: false*/
1003
+
1004
+
1005
+ // extend some methods to check for elem.attr, which means it is a Highcharts SVG object
1006
+ $.each(['cur', '_default', 'width', 'height'], function (i, fn) {
1007
+ var obj = Step,
1008
+ base,
1009
+ elem;
1010
+
1011
+ // Handle different parent objects
1012
+ if (fn === 'cur') {
1013
+ obj = Fx.prototype; // 'cur', the getter, relates to Fx.prototype
1014
+
1015
+ } else if (fn === '_default' && Tween) { // jQuery 1.8 model
1016
+ obj = propHooks[fn];
1017
+ fn = 'set';
1018
+ }
1019
+
1020
+ // Overwrite the method
1021
+ base = obj[fn];
1022
+ if (base) { // step.width and step.height don't exist in jQuery < 1.7
1023
+
1024
+ // create the extended function replacement
1025
+ obj[fn] = function (fx) {
1026
+
1027
+ // Fx.prototype.cur does not use fx argument
1028
+ fx = i ? fx : this;
1029
+
1030
+ // shortcut
1031
+ elem = fx.elem;
1032
+
1033
+ // Fx.prototype.cur returns the current value. The other ones are setters
1034
+ // and returning a value has no effect.
1035
+ return elem.attr ? // is SVG element wrapper
1036
+ elem.attr(fx.prop, fn === 'cur' ? UNDEFINED : fx.now) : // apply the SVG wrapper's method
1037
+ base.apply(this, arguments); // use jQuery's built-in method
1038
+ };
1039
+ }
1040
+ });
1041
+
1042
+
1043
+ // Define the setter function for d (path definitions)
1044
+ dSetter = function (fx) {
1045
+ var elem = fx.elem,
1046
+ ends;
1047
+
1048
+ // Normally start and end should be set in state == 0, but sometimes,
1049
+ // for reasons unknown, this doesn't happen. Perhaps state == 0 is skipped
1050
+ // in these cases
1051
+ if (!fx.started) {
1052
+ ends = pathAnim.init(elem, elem.d, elem.toD);
1053
+ fx.start = ends[0];
1054
+ fx.end = ends[1];
1055
+ fx.started = true;
1056
+ }
1057
+
1058
+
1059
+ // interpolate each value of the path
1060
+ elem.attr('d', pathAnim.step(fx.start, fx.end, fx.pos, elem.toD));
1061
+ };
1062
+
1063
+ // jQuery 1.8 style
1064
+ if (Tween) {
1065
+ propHooks.d = {
1066
+ set: dSetter
1067
+ };
1068
+ // pre 1.8
1069
+ } else {
1070
+ // animate paths
1071
+ Step.d = dSetter;
1072
+ }
1073
+
1074
+ /**
1075
+ * Utility for iterating over an array. Parameters are reversed compared to jQuery.
1076
+ * @param {Array} arr
1077
+ * @param {Function} fn
1078
+ */
1079
+ this.each = Array.prototype.forEach ?
1080
+ function (arr, fn) { // modern browsers
1081
+ return Array.prototype.forEach.call(arr, fn);
1082
+
1083
+ } :
1084
+ function (arr, fn) { // legacy
1085
+ var i = 0,
1086
+ len = arr.length;
1087
+ for (; i < len; i++) {
1088
+ if (fn.call(arr[i], arr[i], i, arr) === false) {
1089
+ return i;
1090
+ }
1091
+ }
1092
+ };
1093
+
1094
+ // Register Highcharts as a jQuery plugin
1095
+ // TODO: MooTools and prototype as well?
1096
+ // TODO: StockChart
1097
+ /*$.fn.highcharts = function(options, callback) {
1098
+ options.chart = merge(options.chart, { renderTo: this[0] });
1099
+ this.chart = new Chart(options, callback);
1100
+ return this;
1101
+ };*/
1102
+ },
1103
+
1104
+ /**
1105
+ * Downloads a script and executes a callback when done.
1106
+ * @param {String} scriptLocation
1107
+ * @param {Function} callback
1108
+ */
1109
+ getScript: $.getScript,
1110
+
1111
+ /**
1112
+ * Return the index of an item in an array, or -1 if not found
1113
+ */
1114
+ inArray: $.inArray,
1115
+
1116
+ /**
1117
+ * A direct link to jQuery methods. MooTools and Prototype adapters must be implemented for each case of method.
1118
+ * @param {Object} elem The HTML element
1119
+ * @param {String} method Which method to run on the wrapped element
1120
+ */
1121
+ adapterRun: function (elem, method) {
1122
+ return $(elem)[method]();
1123
+ },
1124
+
1125
+ /**
1126
+ * Filter an array
1127
+ */
1128
+ grep: $.grep,
1129
+
1130
+ /**
1131
+ * Map an array
1132
+ * @param {Array} arr
1133
+ * @param {Function} fn
1134
+ */
1135
+ map: function (arr, fn) {
1136
+ //return jQuery.map(arr, fn);
1137
+ var results = [],
1138
+ i = 0,
1139
+ len = arr.length;
1140
+ for (; i < len; i++) {
1141
+ results[i] = fn.call(arr[i], arr[i], i, arr);
1142
+ }
1143
+ return results;
1144
+
1145
+ },
1146
+
1147
+ /**
1148
+ * Deep merge two objects and return a third object
1149
+ */
1150
+ merge: function () {
1151
+ var args = arguments;
1152
+ return $.extend(true, null, args[0], args[1], args[2], args[3]);
1153
+ },
1154
+
1155
+ /**
1156
+ * Get the position of an element relative to the top left of the page
1157
+ */
1158
+ offset: function (el) {
1159
+ return $(el).offset();
1160
+ },
1161
+
1162
+ /**
1163
+ * Add an event listener
1164
+ * @param {Object} el A HTML element or custom object
1165
+ * @param {String} event The event type
1166
+ * @param {Function} fn The event handler
1167
+ */
1168
+ addEvent: function (el, event, fn) {
1169
+ $(el).bind(event, fn);
1170
+ },
1171
+
1172
+ /**
1173
+ * Remove event added with addEvent
1174
+ * @param {Object} el The object
1175
+ * @param {String} eventType The event type. Leave blank to remove all events.
1176
+ * @param {Function} handler The function to remove
1177
+ */
1178
+ removeEvent: function (el, eventType, handler) {
1179
+ // workaround for jQuery issue with unbinding custom events:
1180
+ // http://forum.jQuery.com/topic/javascript-error-when-unbinding-a-custom-event-using-jQuery-1-4-2
1181
+ var func = doc.removeEventListener ? 'removeEventListener' : 'detachEvent';
1182
+ if (doc[func] && !el[func]) {
1183
+ el[func] = function () {};
1184
+ }
1185
+
1186
+ $(el).unbind(eventType, handler);
1187
+ },
1188
+
1189
+ /**
1190
+ * Fire an event on a custom object
1191
+ * @param {Object} el
1192
+ * @param {String} type
1193
+ * @param {Object} eventArguments
1194
+ * @param {Function} defaultFunction
1195
+ */
1196
+ fireEvent: function (el, type, eventArguments, defaultFunction) {
1197
+ var event = $.Event(type),
1198
+ detachedType = 'detached' + type,
1199
+ defaultPrevented;
1200
+
1201
+ // Remove warnings in Chrome when accessing layerX and layerY. Although Highcharts
1202
+ // never uses these properties, Chrome includes them in the default click event and
1203
+ // raises the warning when they are copied over in the extend statement below.
1204
+ //
1205
+ // To avoid problems in IE (see #1010) where we cannot delete the properties and avoid
1206
+ // testing if they are there (warning in chrome) the only option is to test if running IE.
1207
+ if (!isIE && eventArguments) {
1208
+ delete eventArguments.layerX;
1209
+ delete eventArguments.layerY;
1210
+ }
1211
+
1212
+ extend(event, eventArguments);
1213
+
1214
+ // Prevent jQuery from triggering the object method that is named the
1215
+ // same as the event. For example, if the event is 'select', jQuery
1216
+ // attempts calling el.select and it goes into a loop.
1217
+ if (el[type]) {
1218
+ el[detachedType] = el[type];
1219
+ el[type] = null;
1220
+ }
1221
+
1222
+ // Wrap preventDefault and stopPropagation in try/catch blocks in
1223
+ // order to prevent JS errors when cancelling events on non-DOM
1224
+ // objects. #615.
1225
+ /*jslint unparam: true*/
1226
+ $.each(['preventDefault', 'stopPropagation'], function (i, fn) {
1227
+ var base = event[fn];
1228
+ event[fn] = function () {
1229
+ try {
1230
+ base.call(event);
1231
+ } catch (e) {
1232
+ if (fn === 'preventDefault') {
1233
+ defaultPrevented = true;
1234
+ }
1235
+ }
1236
+ };
1237
+ });
1238
+ /*jslint unparam: false*/
1239
+
1240
+ // trigger it
1241
+ $(el).trigger(event);
1242
+
1243
+ // attach the method
1244
+ if (el[detachedType]) {
1245
+ el[type] = el[detachedType];
1246
+ el[detachedType] = null;
1247
+ }
1248
+
1249
+ if (defaultFunction && !event.isDefaultPrevented() && !defaultPrevented) {
1250
+ defaultFunction(event);
1251
+ }
1252
+ },
1253
+
1254
+ /**
1255
+ * Extension method needed for MooTools
1256
+ */
1257
+ washMouseEvent: function (e) {
1258
+ var ret = e.originalEvent || e;
1259
+
1260
+ // computed by jQuery, needed by IE8
1261
+ ret.pageX = e.pageX;
1262
+ ret.pageY = e.pageY;
1263
+
1264
+ return ret;
1265
+ },
1266
+
1267
+ /**
1268
+ * Animate a HTML element or SVG element wrapper
1269
+ * @param {Object} el
1270
+ * @param {Object} params
1271
+ * @param {Object} options jQuery-like animation options: duration, easing, callback
1272
+ */
1273
+ animate: function (el, params, options) {
1274
+ var $el = $(el);
1275
+ if (params.d) {
1276
+ el.toD = params.d; // keep the array form for paths, used in $.fx.step.d
1277
+ params.d = 1; // because in jQuery, animating to an array has a different meaning
1278
+ }
1279
+
1280
+ $el.stop();
1281
+ $el.animate(params, options);
1282
+
1283
+ },
1284
+ /**
1285
+ * Stop running animation
1286
+ */
1287
+ stop: function (el) {
1288
+ $(el).stop();
1289
+ }
1290
+ });
1291
+ }(win.jQuery));
961
1292
 
962
1293
 
963
1294
  // check for a custom HighchartsAdapter defined prior to this file
964
1295
  var globalAdapter = win.HighchartsAdapter,
965
- adapter = globalAdapter || {},
1296
+ adapter = globalAdapter || {};
1297
+
1298
+ // Initialize the adapter
1299
+ if (globalAdapter) {
1300
+ globalAdapter.init.call(globalAdapter, pathAnim);
1301
+ }
1302
+
966
1303
 
967
1304
  // Utility functions. If the HighchartsAdapter is not defined, adapter is an empty object
968
1305
  // and all the utility functions will be null. In that case they are populated by the
969
1306
  // default adapters below.
970
- adapterRun = adapter.adapterRun,
1307
+ var adapterRun = adapter.adapterRun,
971
1308
  getScript = adapter.getScript,
1309
+ inArray = adapter.inArray,
972
1310
  each = adapter.each,
973
1311
  grep = adapter.grep,
974
1312
  offset = adapter.offset,
@@ -981,281 +1319,8 @@ var globalAdapter = win.HighchartsAdapter,
981
1319
  animate = adapter.animate,
982
1320
  stop = adapter.stop;
983
1321
 
984
- /*
985
- * Define the adapter for frameworks. If an external adapter is not defined,
986
- * Highcharts reverts to the built-in jQuery adapter.
987
- */
988
- if (globalAdapter && globalAdapter.init) {
989
- // Initialize the adapter with the pathAnim object that takes care
990
- // of path animations.
991
- globalAdapter.init(pathAnim);
992
- }
993
- if (!globalAdapter && win.jQuery) {
994
- var jQ = jQuery;
995
-
996
- /**
997
- * Downloads a script and executes a callback when done.
998
- * @param {String} scriptLocation
999
- * @param {Function} callback
1000
- */
1001
- getScript = jQ.getScript;
1002
-
1003
- /**
1004
- * A direct link to jQuery methods. MooTools and Prototype adapters must be implemented for each case of method.
1005
- * @param {Object} elem The HTML element
1006
- * @param {String} method Which method to run on the wrapped element
1007
- */
1008
- adapterRun = function (elem, method) {
1009
- return jQ(elem)[method]();
1010
- };
1011
-
1012
- /**
1013
- * Utility for iterating over an array. Parameters are reversed compared to jQuery.
1014
- * @param {Array} arr
1015
- * @param {Function} fn
1016
- */
1017
- each = function (arr, fn) {
1018
- var i = 0,
1019
- len = arr.length;
1020
- for (; i < len; i++) {
1021
- if (fn.call(arr[i], arr[i], i, arr) === false) {
1022
- return i;
1023
- }
1024
- }
1025
- };
1026
-
1027
- /**
1028
- * Filter an array
1029
- */
1030
- grep = jQ.grep;
1031
-
1032
- /**
1033
- * Map an array
1034
- * @param {Array} arr
1035
- * @param {Function} fn
1036
- */
1037
- map = function (arr, fn) {
1038
- //return jQuery.map(arr, fn);
1039
- var results = [],
1040
- i = 0,
1041
- len = arr.length;
1042
- for (; i < len; i++) {
1043
- results[i] = fn.call(arr[i], arr[i], i, arr);
1044
- }
1045
- return results;
1046
-
1047
- };
1048
-
1049
- /**
1050
- * Deep merge two objects and return a third object
1051
- */
1052
- merge = function () {
1053
- var args = arguments;
1054
- return jQ.extend(true, null, args[0], args[1], args[2], args[3]);
1055
- };
1056
-
1057
- /**
1058
- * Get the position of an element relative to the top left of the page
1059
- */
1060
- offset = function (el) {
1061
- return jQ(el).offset();
1062
- };
1063
-
1064
- /**
1065
- * Add an event listener
1066
- * @param {Object} el A HTML element or custom object
1067
- * @param {String} event The event type
1068
- * @param {Function} fn The event handler
1069
- */
1070
- addEvent = function (el, event, fn) {
1071
- jQ(el).bind(event, fn);
1072
- };
1073
-
1074
- /**
1075
- * Remove event added with addEvent
1076
- * @param {Object} el The object
1077
- * @param {String} eventType The event type. Leave blank to remove all events.
1078
- * @param {Function} handler The function to remove
1079
- */
1080
- removeEvent = function (el, eventType, handler) {
1081
- // workaround for jQuery issue with unbinding custom events:
1082
- // http://forum.jquery.com/topic/javascript-error-when-unbinding-a-custom-event-using-jquery-1-4-2
1083
- var func = doc.removeEventListener ? 'removeEventListener' : 'detachEvent';
1084
- if (doc[func] && !el[func]) {
1085
- el[func] = function () {};
1086
- }
1087
-
1088
- jQ(el).unbind(eventType, handler);
1089
- };
1090
-
1091
- /**
1092
- * Fire an event on a custom object
1093
- * @param {Object} el
1094
- * @param {String} type
1095
- * @param {Object} eventArguments
1096
- * @param {Function} defaultFunction
1097
- */
1098
- fireEvent = function (el, type, eventArguments, defaultFunction) {
1099
- var event = jQ.Event(type),
1100
- detachedType = 'detached' + type,
1101
- defaultPrevented;
1102
-
1103
- // Remove warnings in Chrome when accessing layerX and layerY. Although Highcharts
1104
- // never uses these properties, Chrome includes them in the default click event and
1105
- // raises the warning when they are copied over in the extend statement below.
1106
- //
1107
- // To avoid problems in IE (see #1010) where we cannot delete the properties and avoid
1108
- // testing if they are there (warning in chrome) the only option is to test if running IE.
1109
- if (!isIE && eventArguments) {
1110
- delete eventArguments.layerX;
1111
- delete eventArguments.layerY;
1112
- }
1113
-
1114
- extend(event, eventArguments);
1115
-
1116
- // Prevent jQuery from triggering the object method that is named the
1117
- // same as the event. For example, if the event is 'select', jQuery
1118
- // attempts calling el.select and it goes into a loop.
1119
- if (el[type]) {
1120
- el[detachedType] = el[type];
1121
- el[type] = null;
1122
- }
1123
-
1124
- // Wrap preventDefault and stopPropagation in try/catch blocks in
1125
- // order to prevent JS errors when cancelling events on non-DOM
1126
- // objects. #615.
1127
- each(['preventDefault', 'stopPropagation'], function (fn) {
1128
- var base = event[fn];
1129
- event[fn] = function () {
1130
- try {
1131
- base.call(event);
1132
- } catch (e) {
1133
- if (fn === 'preventDefault') {
1134
- defaultPrevented = true;
1135
- }
1136
- }
1137
- };
1138
- });
1139
-
1140
- // trigger it
1141
- jQ(el).trigger(event);
1142
-
1143
- // attach the method
1144
- if (el[detachedType]) {
1145
- el[type] = el[detachedType];
1146
- el[detachedType] = null;
1147
- }
1148
-
1149
- if (defaultFunction && !event.isDefaultPrevented() && !defaultPrevented) {
1150
- defaultFunction(event);
1151
- }
1152
- };
1153
-
1154
- /**
1155
- * Extension method needed for MooTools
1156
- */
1157
- washMouseEvent = function (e) {
1158
- return e;
1159
- };
1160
-
1161
- /**
1162
- * Animate a HTML element or SVG element wrapper
1163
- * @param {Object} el
1164
- * @param {Object} params
1165
- * @param {Object} options jQuery-like animation options: duration, easing, callback
1166
- */
1167
- animate = function (el, params, options) {
1168
- var $el = jQ(el);
1169
- if (params.d) {
1170
- el.toD = params.d; // keep the array form for paths, used in jQ.fx.step.d
1171
- params.d = 1; // because in jQuery, animating to an array has a different meaning
1172
- }
1173
-
1174
- $el.stop();
1175
- $el.animate(params, options);
1176
-
1177
- };
1178
- /**
1179
- * Stop running animation
1180
- */
1181
- stop = function (el) {
1182
- jQ(el).stop();
1183
- };
1184
-
1185
-
1186
- //=== Extend jQuery on init
1187
-
1188
- /*jslint unparam: true*//* allow unused param x in this function */
1189
- jQ.extend(jQ.easing, {
1190
- easeOutQuad: function (x, t, b, c, d) {
1191
- return -c * (t /= d) * (t - 2) + b;
1192
- }
1193
- });
1194
- /*jslint unparam: false*/
1195
-
1196
- // extend the animate function to allow SVG animations
1197
- var jFx = jQ.fx,
1198
- jStep = jFx.step;
1199
-
1200
- // extend some methods to check for elem.attr, which means it is a Highcharts SVG object
1201
- each(['cur', '_default', 'width', 'height'], function (fn, i) {
1202
- var obj = jStep,
1203
- base,
1204
- elem;
1205
-
1206
- // Handle different parent objects
1207
- if (fn === 'cur') {
1208
- obj = jFx.prototype; // 'cur', the getter, relates to jFx.prototype
1209
-
1210
- } else if (fn === '_default' && jQ.Tween) { // jQuery 1.8 model
1211
- obj = jQ.Tween.propHooks[fn];
1212
- fn = 'set';
1213
- }
1214
-
1215
- // Overwrite the method
1216
- base = obj[fn];
1217
- if (base) { // step.width and step.height don't exist in jQuery < 1.7
1218
-
1219
- // create the extended function replacement
1220
- obj[fn] = function (fx) {
1221
-
1222
- // jFx.prototype.cur does not use fx argument
1223
- fx = i ? fx : this;
1224
-
1225
- // shortcut
1226
- elem = fx.elem;
1227
-
1228
- // jFX.prototype.cur returns the current value. The other ones are setters
1229
- // and returning a value has no effect.
1230
- return elem.attr ? // is SVG element wrapper
1231
- elem.attr(fx.prop, fn === 'cur' ? UNDEFINED : fx.now) : // apply the SVG wrapper's method
1232
- base.apply(this, arguments); // use jQuery's built-in method
1233
- };
1234
- }
1235
- });
1236
-
1237
- // animate paths
1238
- jStep.d = function (fx) {
1239
- var elem = fx.elem;
1240
1322
 
1241
1323
 
1242
- // Normally start and end should be set in state == 0, but sometimes,
1243
- // for reasons unknown, this doesn't happen. Perhaps state == 0 is skipped
1244
- // in these cases
1245
- if (!fx.started) {
1246
- var ends = pathAnim.init(elem, elem.d, elem.toD);
1247
- fx.start = ends[0];
1248
- fx.end = ends[1];
1249
- fx.started = true;
1250
- }
1251
-
1252
-
1253
- // interpolate each value of the path
1254
- elem.attr('d', pathAnim.step(fx.start, fx.end, fx.pos, elem.toD));
1255
-
1256
- };
1257
- }
1258
-
1259
1324
  /* ****************************************************************************
1260
1325
  * Handle the options *
1261
1326
  *****************************************************************************/
@@ -1288,13 +1353,15 @@ defaultOptions = {
1288
1353
  shortMonths: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
1289
1354
  weekdays: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
1290
1355
  decimalPoint: '.',
1356
+ numericSymbols: ['k', 'M', 'G', 'T', 'P', 'E'], // SI prefixes used in axis labels
1291
1357
  resetZoom: 'Reset zoom',
1292
1358
  resetZoomTitle: 'Reset zoom level 1:1',
1293
1359
  thousandsSep: ','
1294
1360
  },
1295
1361
  global: {
1296
1362
  useUTC: true,
1297
- canvasToolsURL: 'http://code.highcharts.com/2.2.5/modules/canvas-tools.js'
1363
+ canvasToolsURL: 'http://code.highcharts.com/2.3.2/modules/canvas-tools.js',
1364
+ VMLRadialGradientURL: 'http://code.highcharts.com/2.3.2/gfx/vml-radial-gradient.png'
1298
1365
  },
1299
1366
  chart: {
1300
1367
  //animation: true,
@@ -1393,6 +1460,7 @@ defaultOptions = {
1393
1460
  //fillColor: null,
1394
1461
  states: { // states for a single point
1395
1462
  hover: {
1463
+ enabled: true
1396
1464
  //radius: base + 2
1397
1465
  },
1398
1466
  select: {
@@ -1838,7 +1906,7 @@ SVGElement.prototype = {
1838
1906
  value = hash[key];
1839
1907
 
1840
1908
  // check for a specific attribute setter
1841
- result = attrSetters[key] && attrSetters[key](value, key);
1909
+ result = attrSetters[key] && attrSetters[key].call(wrapper, value, key);
1842
1910
 
1843
1911
  if (result !== false) {
1844
1912
  if (result !== UNDEFINED) {
@@ -1995,7 +2063,10 @@ SVGElement.prototype = {
1995
2063
 
1996
2064
 
1997
2065
  if (key === 'text') {
1998
- // only one node allowed
2066
+ // Delete bBox memo when the text changes
2067
+ if (value !== wrapper.textStr) {
2068
+ delete wrapper.bBox;
2069
+ }
1999
2070
  wrapper.textStr = value;
2000
2071
  if (wrapper.added) {
2001
2072
  renderer.buildText(wrapper);
@@ -2010,25 +2081,6 @@ SVGElement.prototype = {
2010
2081
 
2011
2082
  }
2012
2083
 
2013
- // Workaround for our #732, WebKit's issue https://bugs.webkit.org/show_bug.cgi?id=78385
2014
- // TODO: If the WebKit team fix this bug before the final release of Chrome 18, remove the workaround.
2015
- if (isWebKit && /Chrome\/(18|19)/.test(userAgent)) {
2016
- if (nodeName === 'text' && (hash.x !== UNDEFINED || hash.y !== UNDEFINED)) {
2017
- var parent = element.parentNode,
2018
- next = element.nextSibling;
2019
-
2020
- if (parent) {
2021
- parent.removeChild(element);
2022
- if (next) {
2023
- parent.insertBefore(element, next);
2024
- } else {
2025
- parent.appendChild(element);
2026
- }
2027
- }
2028
- }
2029
- }
2030
- // End of workaround for #732
2031
-
2032
2084
  return ret;
2033
2085
  },
2034
2086
 
@@ -2055,7 +2107,7 @@ SVGElement.prototype = {
2055
2107
  * @param {String} id
2056
2108
  */
2057
2109
  clip: function (clipRect) {
2058
- return this.attr('clip-path', 'url(' + this.renderer.url + '#' + clipRect.id + ')');
2110
+ return this.attr('clip-path', clipRect ? 'url(' + this.renderer.url + '#' + clipRect.id + ')' : NONE);
2059
2111
  },
2060
2112
 
2061
2113
  /**
@@ -2121,7 +2173,7 @@ SVGElement.prototype = {
2121
2173
 
2122
2174
  // store object
2123
2175
  elemWrapper.styles = styles;
2124
-
2176
+
2125
2177
  // serialize and set style attribute
2126
2178
  if (isIE && !hasSVG) { // legacy IE doesn't support setting style attribute
2127
2179
  if (textWidth) {
@@ -2229,14 +2281,14 @@ SVGElement.prototype = {
2229
2281
  * @return {Object} A hash containing values for x, y, width and height
2230
2282
  */
2231
2283
 
2232
- htmlGetBBox: function (refresh) {
2284
+ htmlGetBBox: function () {
2233
2285
  var wrapper = this,
2234
2286
  element = wrapper.element,
2235
2287
  bBox = wrapper.bBox;
2236
2288
 
2237
2289
  // faking getBBox in exported SVG in legacy IE
2238
- if (!bBox || refresh) {
2239
- // faking getBBox in exported SVG in legacy IE
2290
+ if (!bBox) {
2291
+ // faking getBBox in exported SVG in legacy IE (is this a duplicate of the fix for #1079?)
2240
2292
  if (element.nodeName === 'text') {
2241
2293
  element.style.position = ABSOLUTE;
2242
2294
  }
@@ -2310,25 +2362,34 @@ SVGElement.prototype = {
2310
2362
  textWidth = pInt(wrapper.textWidth),
2311
2363
  xCorr = wrapper.xCorr || 0,
2312
2364
  yCorr = wrapper.yCorr || 0,
2313
- currentTextTransform = [rotation, align, elem.innerHTML, wrapper.textWidth].join(',');
2365
+ currentTextTransform = [rotation, align, elem.innerHTML, wrapper.textWidth].join(','),
2366
+ rotationStyle = {},
2367
+ prefix;
2314
2368
 
2315
2369
  if (currentTextTransform !== wrapper.cTT) { // do the calculations and DOM access only if properties changed
2316
2370
 
2317
2371
  if (defined(rotation)) {
2318
- radians = rotation * deg2rad; // deg to rad
2319
- costheta = mathCos(radians);
2320
- sintheta = mathSin(radians);
2321
-
2322
- // Adjust for alignment and rotation. Rotation of useHTML content is not yet implemented
2323
- // but it can probably be implemented for Firefox 3.5+ on user request. FF3.5+
2324
- // has support for CSS3 transform. The getBBox method also needs to be updated
2325
- // to compensate for the rotation, like it currently does for SVG.
2326
- // Test case: http://highcharts.com/tests/?file=text-rotation
2327
- css(elem, {
2328
- filter: rotation ? ['progid:DXImageTransform.Microsoft.Matrix(M11=', costheta,
2329
- ', M12=', -sintheta, ', M21=', sintheta, ', M22=', costheta,
2330
- ', sizingMethod=\'auto expand\')'].join('') : NONE
2331
- });
2372
+
2373
+ if (renderer.isSVG) { // #916
2374
+ prefix = isIE ? '-ms' : isWebKit ? '-webkit' : isFirefox ? '-moz' : isOpera ? '-o' : '';
2375
+ rotationStyle[prefix + '-transform'] = rotationStyle.transform = 'rotate(' + rotation + 'deg)';
2376
+
2377
+ } else {
2378
+ radians = rotation * deg2rad; // deg to rad
2379
+ costheta = mathCos(radians);
2380
+ sintheta = mathSin(radians);
2381
+
2382
+ // Adjust for alignment and rotation. Rotation of useHTML content is not yet implemented
2383
+ // but it can probably be implemented for Firefox 3.5+ on user request. FF3.5+
2384
+ // has support for CSS3 transform. The getBBox method also needs to be updated
2385
+ // to compensate for the rotation, like it currently does for SVG.
2386
+ // Test case: http://highcharts.com/tests/?file=text-rotation
2387
+ rotationStyle.filter = rotation ? ['progid:DXImageTransform.Microsoft.Matrix(M11=', costheta,
2388
+ ', M12=', -sintheta, ', M21=', sintheta, ', M22=', costheta,
2389
+ ', sizingMethod=\'auto expand\')'].join('') : NONE;
2390
+ }
2391
+
2392
+ css(elem, rotationStyle);
2332
2393
  }
2333
2394
 
2334
2395
  width = pick(wrapper.elemWidth, elem.offsetWidth);
@@ -2484,50 +2545,61 @@ SVGElement.prototype = {
2484
2545
  /**
2485
2546
  * Get the bounding box (width, height, x and y) for the element
2486
2547
  */
2487
- getBBox: function (refresh) {
2548
+ getBBox: function () {
2488
2549
  var wrapper = this,
2489
- bBox,
2550
+ bBox = wrapper.bBox,
2551
+ renderer = wrapper.renderer,
2490
2552
  width,
2491
2553
  height,
2492
2554
  rotation = wrapper.rotation,
2493
2555
  element = wrapper.element,
2494
2556
  rad = rotation * deg2rad;
2495
2557
 
2496
- // SVG elements
2497
- if (element.namespaceURI === SVG_NS || wrapper.renderer.forExport) {
2498
- try { // Fails in Firefox if the container has display: none.
2558
+ if (!bBox) {
2559
+ // SVG elements
2560
+ if (element.namespaceURI === SVG_NS || renderer.forExport) {
2561
+ try { // Fails in Firefox if the container has display: none.
2562
+
2563
+ bBox = element.getBBox ?
2564
+ // SVG: use extend because IE9 is not allowed to change width and height in case
2565
+ // of rotation (below)
2566
+ extend({}, element.getBBox()) :
2567
+ // Canvas renderer and legacy IE in export mode
2568
+ {
2569
+ width: element.offsetWidth,
2570
+ height: element.offsetHeight
2571
+ };
2572
+ } catch (e) {}
2573
+
2574
+ // If the bBox is not set, the try-catch block above failed. The other condition
2575
+ // is for Opera that returns a width of -Infinity on hidden elements.
2576
+ if (!bBox || bBox.width < 0) {
2577
+ bBox = { width: 0, height: 0 };
2578
+ }
2579
+
2580
+
2581
+ // VML Renderer or useHTML within SVG
2582
+ } else {
2583
+
2584
+ bBox = wrapper.htmlGetBBox();
2499
2585
 
2500
- bBox = element.getBBox ?
2501
- // SVG: use extend because IE9 is not allowed to change width and height in case
2502
- // of rotation (below)
2503
- extend({}, element.getBBox()) :
2504
- // Canvas renderer and legacy IE in export mode
2505
- {
2506
- width: element.offsetWidth,
2507
- height: element.offsetHeight
2508
- };
2509
- } catch (e) {}
2510
-
2511
- // If the bBox is not set, the try-catch block above failed. The other condition
2512
- // is for Opera that returns a width of -Infinity on hidden elements.
2513
- if (!bBox || bBox.width < 0) {
2514
- bBox = { width: 0, height: 0 };
2515
2586
  }
2516
2587
 
2517
- width = bBox.width;
2518
- height = bBox.height;
2519
-
2520
- // adjust for rotated text
2521
- if (rotation) {
2522
- bBox.width = mathAbs(height * mathSin(rad)) + mathAbs(width * mathCos(rad));
2523
- bBox.height = mathAbs(height * mathCos(rad)) + mathAbs(width * mathSin(rad));
2588
+ // True SVG elements as well as HTML elements in modern browsers using the .useHTML option
2589
+ // need to compensated for rotation
2590
+ if (renderer.isSVG) {
2591
+ width = bBox.width;
2592
+ height = bBox.height;
2593
+
2594
+ // Adjust for rotated text
2595
+ if (rotation) {
2596
+ bBox.width = mathAbs(height * mathSin(rad)) + mathAbs(width * mathCos(rad));
2597
+ bBox.height = mathAbs(height * mathCos(rad)) + mathAbs(width * mathSin(rad));
2598
+ }
2524
2599
  }
2525
-
2526
- // VML Renderer or useHTML within SVG
2527
- } else {
2528
- bBox = wrapper.htmlGetBBox(refresh);
2600
+
2601
+ wrapper.bBox = bBox;
2529
2602
  }
2530
-
2531
2603
  return bBox;
2532
2604
  },
2533
2605
 
@@ -2544,7 +2616,7 @@ SVGElement.prototype = {
2544
2616
  hide: function () {
2545
2617
  return this.attr({ visibility: HIDDEN });
2546
2618
  },
2547
-
2619
+
2548
2620
  /**
2549
2621
  * Add the element
2550
2622
  * @param {Object|Undefined} parent Can be an element, an element wrapper or undefined
@@ -2562,6 +2634,10 @@ SVGElement.prototype = {
2562
2634
  otherZIndex,
2563
2635
  i,
2564
2636
  inserted;
2637
+
2638
+ if (parent) {
2639
+ this.parentGroup = parent;
2640
+ }
2565
2641
 
2566
2642
  // mark as inverted
2567
2643
  this.parentInverted = parent && parent.inverted;
@@ -2688,27 +2764,34 @@ SVGElement.prototype = {
2688
2764
 
2689
2765
  /**
2690
2766
  * Add a shadow to the element. Must be done after the element is added to the DOM
2691
- * @param {Boolean} apply
2767
+ * @param {Boolean|Object} shadowOptions
2692
2768
  */
2693
- shadow: function (apply, group, cutOff) {
2769
+ shadow: function (shadowOptions, group, cutOff) {
2694
2770
  var shadows = [],
2695
2771
  i,
2696
2772
  shadow,
2697
2773
  element = this.element,
2698
2774
  strokeWidth,
2775
+ shadowWidth,
2776
+ shadowElementOpacity,
2699
2777
 
2700
2778
  // compensate for inverted plot area
2701
- transform = this.parentInverted ? '(-1,-1)' : '(1,1)';
2779
+ transform;
2702
2780
 
2703
2781
 
2704
- if (apply) {
2705
- for (i = 1; i <= 3; i++) {
2782
+ if (shadowOptions) {
2783
+ shadowWidth = pick(shadowOptions.width, 3);
2784
+ shadowElementOpacity = (shadowOptions.opacity || 0.15) / shadowWidth;
2785
+ transform = this.parentInverted ?
2786
+ '(-1,-1)' :
2787
+ '(' + (shadowOptions.offsetX || 1) + ', ' + (shadowOptions.offsetY || 1) + ')';
2788
+ for (i = 1; i <= shadowWidth; i++) {
2706
2789
  shadow = element.cloneNode(0);
2707
- strokeWidth = 7 - 2 * i;
2790
+ strokeWidth = (shadowWidth * 2) + 1 - (2 * i);
2708
2791
  attr(shadow, {
2709
2792
  'isShadow': 'true',
2710
- 'stroke': 'rgb(0, 0, 0)',
2711
- 'stroke-opacity': 0.05 * i,
2793
+ 'stroke': shadowOptions.color || 'black',
2794
+ 'stroke-opacity': shadowElementOpacity * i,
2712
2795
  'stroke-width': strokeWidth,
2713
2796
  'transform': 'translate' + transform,
2714
2797
  'fill': NONE
@@ -2768,8 +2851,15 @@ SVGRenderer.prototype = {
2768
2851
  renderer.box = boxWrapper.element;
2769
2852
  renderer.boxWrapper = boxWrapper;
2770
2853
  renderer.alignedObjects = [];
2771
- renderer.url = isIE ? '' : loc.href.replace(/#.*?$/, '')
2772
- .replace(/([\('\)])/g, '\\$1'); // Page url used for internal references. #24, #672.
2854
+
2855
+ // Page url used for internal references. #24, #672, #1070
2856
+ renderer.url = (isFirefox || isWebKit) && doc.getElementsByTagName('base').length ?
2857
+ loc.href
2858
+ .replace(/#.*?$/, '') // remove the hash
2859
+ .replace(/([\('\)])/g, '\\$1') // escape parantheses and quotes
2860
+ .replace(/ /g, '%20') : // replace spaces (needed for Safari only)
2861
+ '';
2862
+
2773
2863
  renderer.defs = this.createElement('defs').add();
2774
2864
  renderer.forExport = forExport;
2775
2865
  renderer.gradients = {}; // Object where gradient SvgElements are stored
@@ -2884,7 +2974,9 @@ SVGRenderer.prototype = {
2884
2974
 
2885
2975
  // Needed in IE9 because it doesn't report tspan's offsetHeight (#893)
2886
2976
  function getLineHeightByBBox(lineNo) {
2887
- linePositions[lineNo] = textNode.getBBox().height;
2977
+ linePositions[lineNo] = textNode.getBBox ?
2978
+ textNode.getBBox().height :
2979
+ wrapper.renderer.fontMetrics(textNode.style.fontSize).h; // #990
2888
2980
  return mathRound(linePositions[lineNo] - (linePositions[lineNo - 1] || 0));
2889
2981
  }
2890
2982
 
@@ -2987,6 +3079,7 @@ SVGRenderer.prototype = {
2987
3079
  rest = [];
2988
3080
 
2989
3081
  while (words.length || rest.length) {
3082
+ delete wrapper.bBox; // delete cache
2990
3083
  actualWidth = wrapper.getBBox().width;
2991
3084
  tooLong = actualWidth > width;
2992
3085
  if (!tooLong || words.length === 1) { // new line needed
@@ -3131,7 +3224,8 @@ SVGRenderer.prototype = {
3131
3224
  // points format: [M, 0, 0, L, 100, 0]
3132
3225
  // normalize to a crisp line
3133
3226
  if (points[1] === points[4]) {
3134
- points[1] = points[4] = mathRound(points[1]) + (width % 2 / 2);
3227
+ // Substract due to #1129. Now bottom and left axis gridlines behave the same.
3228
+ points[1] = points[4] = mathRound(points[1]) - (width % 2 / 2);
3135
3229
  }
3136
3230
  if (points[2] === points[5]) {
3137
3231
  points[2] = points[5] = mathRound(points[2]) + (width % 2 / 2);
@@ -3649,6 +3743,13 @@ SVGRenderer.prototype = {
3649
3743
  fontFamily: defaultChartStyle.fontFamily,
3650
3744
  fontSize: defaultChartStyle.fontSize
3651
3745
  });
3746
+
3747
+ // Prevent wrapping from creating false offsetWidths in export in legacy IE (#1079, #1063)
3748
+ if (!hasSVG && renderer.forExport) {
3749
+ wrapper.css({
3750
+ position: ABSOLUTE
3751
+ });
3752
+ }
3652
3753
 
3653
3754
  wrapper.x = x;
3654
3755
  wrapper.y = y;
@@ -3673,6 +3774,9 @@ SVGRenderer.prototype = {
3673
3774
 
3674
3775
  // Text setter
3675
3776
  attrSetters.text = function (value) {
3777
+ if (value !== element.innerHTML) {
3778
+ delete this.bBox;
3779
+ }
3676
3780
  element.innerHTML = value;
3677
3781
  return false;
3678
3782
  };
@@ -3708,33 +3812,55 @@ SVGRenderer.prototype = {
3708
3812
  wrapper.add = function (svgGroupWrapper) {
3709
3813
 
3710
3814
  var htmlGroup,
3711
- htmlGroupStyle,
3712
- container = renderer.box.parentNode;
3815
+ container = renderer.box.parentNode,
3816
+ parentGroup,
3817
+ parents = [];
3713
3818
 
3714
3819
  // Create a mock group to hold the HTML elements
3715
3820
  if (svgGroupWrapper) {
3716
3821
  htmlGroup = svgGroupWrapper.div;
3717
3822
  if (!htmlGroup) {
3718
- htmlGroup = svgGroupWrapper.div = createElement(DIV, {
3719
- className: attr(svgGroupWrapper.element, 'class')
3720
- }, {
3721
- position: ABSOLUTE,
3722
- left: svgGroupWrapper.attr('translateX') + PX,
3723
- top: svgGroupWrapper.attr('translateY') + PX
3724
- }, container);
3725
-
3726
- // Ensure dynamic updating position
3727
- htmlGroupStyle = htmlGroup.style;
3728
- extend(svgGroupWrapper.attrSetters, {
3729
- translateX: function (value) {
3730
- htmlGroupStyle.left = value + PX;
3731
- },
3732
- translateY: function (value) {
3733
- htmlGroupStyle.top = value + PX;
3734
- },
3735
- visibility: function (value, key) {
3736
- htmlGroupStyle[key] = value;
3737
- }
3823
+
3824
+ // Read the parent chain into an array and read from top down
3825
+ parentGroup = svgGroupWrapper;
3826
+ while (parentGroup) {
3827
+
3828
+ parents.push(parentGroup);
3829
+
3830
+ // Move up to the next parent group
3831
+ parentGroup = parentGroup.parentGroup;
3832
+ }
3833
+
3834
+ // Ensure dynamically updating position when any parent is translated
3835
+ each(parents.reverse(), function (parentGroup) {
3836
+ var htmlGroupStyle;
3837
+
3838
+ // Create a HTML div and append it to the parent div to emulate
3839
+ // the SVG group structure
3840
+ htmlGroup = parentGroup.div = parentGroup.div || createElement(DIV, {
3841
+ className: attr(parentGroup.element, 'class')
3842
+ }, {
3843
+ position: ABSOLUTE,
3844
+ left: (parentGroup.translateX || 0) + PX,
3845
+ top: (parentGroup.translateY || 0) + PX
3846
+ }, htmlGroup || container); // the top group is appended to container
3847
+
3848
+ // Shortcut
3849
+ htmlGroupStyle = htmlGroup.style;
3850
+
3851
+ // Set listeners to update the HTML div's position whenever the SVG group
3852
+ // position is changed
3853
+ extend(parentGroup.attrSetters, {
3854
+ translateX: function (value) {
3855
+ htmlGroupStyle.left = value + PX;
3856
+ },
3857
+ translateY: function (value) {
3858
+ htmlGroupStyle.top = value + PX;
3859
+ },
3860
+ visibility: function (value, key) {
3861
+ htmlGroupStyle[key] = value;
3862
+ }
3863
+ });
3738
3864
  });
3739
3865
 
3740
3866
  }
@@ -3794,8 +3920,8 @@ SVGRenderer.prototype = {
3794
3920
  text = renderer.text('', 0, 0, useHTML)
3795
3921
  .attr({
3796
3922
  zIndex: 1
3797
- })
3798
- .add(wrapper),
3923
+ }),
3924
+ //.add(wrapper),
3799
3925
  box,
3800
3926
  bBox,
3801
3927
  alignFactor = 0,
@@ -3819,7 +3945,7 @@ SVGRenderer.prototype = {
3819
3945
  style = text.element.style;
3820
3946
 
3821
3947
  bBox = (width === undefined || height === undefined || wrapper.styles.textAlign) &&
3822
- text.getBBox(true);
3948
+ text.getBBox();
3823
3949
  wrapper.width = (width || bBox.width || 0) + 2 * padding;
3824
3950
  wrapper.height = (height || bBox.height || 0) + 2 * padding;
3825
3951
 
@@ -3889,6 +4015,7 @@ SVGRenderer.prototype = {
3889
4015
  }
3890
4016
 
3891
4017
  function getSizeAfterAdd() {
4018
+ text.add(wrapper);
3892
4019
  wrapper.attr({
3893
4020
  text: str, // alignment is available now
3894
4021
  x: x,
@@ -4115,11 +4242,6 @@ var VMLElement = {
4115
4242
  renderer.invertChild(element, parentNode);
4116
4243
  }
4117
4244
 
4118
- // issue #140 workaround - related to #61 and #74
4119
- if (docMode8 && parentNode.gVis === HIDDEN) {
4120
- css(element, { visibility: HIDDEN });
4121
- }
4122
-
4123
4245
  // append it
4124
4246
  parentNode.appendChild(element);
4125
4247
 
@@ -4135,26 +4257,6 @@ var VMLElement = {
4135
4257
  return wrapper;
4136
4258
  },
4137
4259
 
4138
- /**
4139
- * In IE8 documentMode 8, we need to recursively set the visibility down in the DOM
4140
- * tree for nested groups. Related to #61, #586.
4141
- */
4142
- toggleChildren: function (element, visibility) {
4143
- var childNodes = element.childNodes,
4144
- i = childNodes.length;
4145
-
4146
- while (i--) {
4147
-
4148
- // apply the visibility
4149
- css(childNodes[i], { visibility: visibility });
4150
-
4151
- // we have a nested group, apply it to its children again
4152
- if (childNodes[i].nodeName === 'DIV') {
4153
- this.toggleChildren(childNodes[i], visibility);
4154
- }
4155
- }
4156
- },
4157
-
4158
4260
  /**
4159
4261
  * VML always uses htmlUpdateTransform
4160
4262
  */
@@ -4203,7 +4305,7 @@ var VMLElement = {
4203
4305
  skipAttr = false;
4204
4306
 
4205
4307
  // check for a specific attribute setter
4206
- result = attrSetters[key] && attrSetters[key](value, key);
4308
+ result = attrSetters[key] && attrSetters[key].call(wrapper, value, key);
4207
4309
 
4208
4310
  if (result !== false && value !== null) { // #620
4209
4311
 
@@ -4258,24 +4360,33 @@ var VMLElement = {
4258
4360
  }
4259
4361
  skipAttr = true;
4260
4362
 
4261
- // directly mapped to css
4262
- } else if (key === 'zIndex' || key === 'visibility') {
4263
-
4264
- // workaround for #61 and #586
4265
- if (docMode8 && key === 'visibility' && nodeName === 'DIV') {
4266
- element.gVis = value;
4267
- wrapper.toggleChildren(element, value);
4268
- if (value === VISIBLE) { // #74
4269
- value = null;
4363
+ // handle visibility
4364
+ } else if (key === 'visibility') {
4365
+
4366
+ // let the shadow follow the main element
4367
+ if (shadows) {
4368
+ i = shadows.length;
4369
+ while (i--) {
4370
+ shadows[i].style[key] = value;
4270
4371
  }
4271
4372
  }
4373
+
4374
+ // Instead of toggling the visibility CSS property, move the div out of the viewport.
4375
+ // This works around #61 and #586
4376
+ if (nodeName === 'DIV') {
4377
+ value = value === HIDDEN ? '-999em' : 0;
4378
+ key = 'top';
4379
+ }
4380
+
4381
+ elemStyle[key] = value;
4382
+ skipAttr = true;
4383
+
4384
+ // directly mapped to css
4385
+ } else if (key === 'zIndex') {
4272
4386
 
4273
4387
  if (value) {
4274
4388
  elemStyle[key] = value;
4275
4389
  }
4276
-
4277
-
4278
-
4279
4390
  skipAttr = true;
4280
4391
 
4281
4392
  // width and height
@@ -4298,7 +4409,6 @@ var VMLElement = {
4298
4409
 
4299
4410
  // x and y
4300
4411
  } else if (key === 'x' || key === 'y') {
4301
-
4302
4412
  wrapper[key] = value; // used in getter
4303
4413
  elemStyle[{ x: 'left', y: 'top' }[key]] = value;
4304
4414
 
@@ -4340,7 +4450,7 @@ var VMLElement = {
4340
4450
  } else {
4341
4451
  element.filled = value !== NONE ? true : false;
4342
4452
 
4343
- value = renderer.color(value, element, key);
4453
+ value = renderer.color(value, element, key, wrapper);
4344
4454
 
4345
4455
  key = 'fillcolor';
4346
4456
  }
@@ -4348,6 +4458,9 @@ var VMLElement = {
4348
4458
  // rotation on VML elements
4349
4459
  } else if (nodeName === 'shape' && key === 'rotation') {
4350
4460
  wrapper[key] = value;
4461
+ // Correction for the 1x1 size of the shape container. Used in gauge needles.
4462
+ element.style.left = -mathRound(mathSin(value * deg2rad) + 1) + PX;
4463
+ element.style.top = mathRound(mathCos(value * deg2rad)) + PX;
4351
4464
 
4352
4465
  // translation for animation
4353
4466
  } else if (key === 'translateX' || key === 'translateY' || key === 'rotation') {
@@ -4363,16 +4476,6 @@ var VMLElement = {
4363
4476
  skipAttr = true;
4364
4477
  }
4365
4478
 
4366
- // let the shadow follow the main element
4367
- if (shadows && key === 'visibility') {
4368
- i = shadows.length;
4369
- while (i--) {
4370
- shadows[i].style[key] = value;
4371
- }
4372
- }
4373
-
4374
-
4375
-
4376
4479
  if (!skipAttr) {
4377
4480
  if (docMode8) { // IE8 setAttribute bug
4378
4481
  element[key] = value;
@@ -4394,21 +4497,32 @@ var VMLElement = {
4394
4497
  */
4395
4498
  clip: function (clipRect) {
4396
4499
  var wrapper = this,
4397
- clipMembers = clipRect.members,
4500
+ clipMembers,
4398
4501
  element = wrapper.element,
4399
- parentNode = element.parentNode;
4400
-
4401
- clipMembers.push(wrapper);
4402
- wrapper.destroyClip = function () {
4403
- erase(clipMembers, wrapper);
4404
- };
4405
-
4406
- // Issue #863 workaround - related to #140, #61, #74
4407
- if (parentNode && parentNode.className === 'highcharts-tracker' && !docMode8) {
4408
- css(element, { visibility: HIDDEN });
4502
+ parentNode = element.parentNode,
4503
+ cssRet;
4504
+
4505
+ if (clipRect) {
4506
+ clipMembers = clipRect.members;
4507
+ clipMembers.push(wrapper);
4508
+ wrapper.destroyClip = function () {
4509
+ erase(clipMembers, wrapper);
4510
+ };
4511
+ // Issue #863 workaround - related to #140, #61, #74
4512
+ if (parentNode && parentNode.className === 'highcharts-tracker' && !docMode8) {
4513
+ css(element, { visibility: HIDDEN });
4514
+ }
4515
+ cssRet = clipRect.getCSS(wrapper);
4516
+
4517
+ } else {
4518
+ if (wrapper.destroyClip) {
4519
+ wrapper.destroyClip();
4520
+ }
4521
+ cssRet = { clip: docMode8 ? 'inherit' : 'rect(auto)' }; // #1214
4409
4522
  }
4410
4523
 
4411
- return wrapper.css(clipRect.getCSS(wrapper));
4524
+ return wrapper.css(cssRet);
4525
+
4412
4526
  },
4413
4527
 
4414
4528
  /**
@@ -4424,8 +4538,7 @@ var VMLElement = {
4424
4538
  safeRemoveChild: function (element) {
4425
4539
  // discardElement will detach the node from its parent before attaching it
4426
4540
  // to the garbage bin. Therefore it is important that the node is attached and have parent.
4427
- var parentNode = element.parentNode;
4428
- if (parentNode) {
4541
+ if (element.parentNode) {
4429
4542
  discardElement(element);
4430
4543
  }
4431
4544
  },
@@ -4434,13 +4547,11 @@ var VMLElement = {
4434
4547
  * Extend element.destroy by removing it from the clip members array
4435
4548
  */
4436
4549
  destroy: function () {
4437
- var wrapper = this;
4438
-
4439
- if (wrapper.destroyClip) {
4440
- wrapper.destroyClip();
4550
+ if (this.destroyClip) {
4551
+ this.destroyClip();
4441
4552
  }
4442
4553
 
4443
- return SVGElement.prototype.destroy.apply(wrapper);
4554
+ return SVGElement.prototype.destroy.apply(this);
4444
4555
  },
4445
4556
 
4446
4557
  /**
@@ -4491,9 +4602,9 @@ var VMLElement = {
4491
4602
 
4492
4603
  /**
4493
4604
  * Apply a drop shadow by copying elements and giving them different strokes
4494
- * @param {Boolean} apply
4605
+ * @param {Boolean|Object} shadowOptions
4495
4606
  */
4496
- shadow: function (apply, group, cutOff) {
4607
+ shadow: function (shadowOptions, group, cutOff) {
4497
4608
  var shadows = [],
4498
4609
  i,
4499
4610
  element = this.element,
@@ -4503,7 +4614,9 @@ var VMLElement = {
4503
4614
  markup,
4504
4615
  path = element.path,
4505
4616
  strokeWidth,
4506
- modifiedPath;
4617
+ modifiedPath,
4618
+ shadowWidth,
4619
+ shadowElementOpacity;
4507
4620
 
4508
4621
  // some times empty paths are not strings
4509
4622
  if (path && typeof path.value !== 'string') {
@@ -4511,24 +4624,26 @@ var VMLElement = {
4511
4624
  }
4512
4625
  modifiedPath = path;
4513
4626
 
4514
- if (apply) {
4627
+ if (shadowOptions) {
4628
+ shadowWidth = pick(shadowOptions.width, 3);
4629
+ shadowElementOpacity = (shadowOptions.opacity || 0.15) / shadowWidth;
4515
4630
  for (i = 1; i <= 3; i++) {
4516
4631
 
4517
- strokeWidth = 7 - 2 * i;
4632
+ strokeWidth = (shadowWidth * 2) + 1 - (2 * i);
4518
4633
 
4519
4634
  // Cut off shadows for stacked column items
4520
4635
  if (cutOff) {
4521
4636
  modifiedPath = this.cutOffPath(path.value, strokeWidth + 0.5);
4522
4637
  }
4523
4638
 
4524
- markup = ['<shape isShadow="true" strokeweight="', (7 - 2 * i),
4639
+ markup = ['<shape isShadow="true" strokeweight="', strokeWidth,
4525
4640
  '" filled="false" path="', modifiedPath,
4526
4641
  '" coordsize="10 10" style="', element.style.cssText, '" />'];
4527
4642
 
4528
4643
  shadow = createElement(renderer.prepVML(markup),
4529
4644
  null, {
4530
- left: pInt(elemStyle.left) + 1,
4531
- top: pInt(elemStyle.top) + 1
4645
+ left: pInt(elemStyle.left) + (shadowOptions.offsetX || 1),
4646
+ top: pInt(elemStyle.top) + (shadowOptions.offsetY || 1)
4532
4647
  }
4533
4648
  );
4534
4649
  if (cutOff) {
@@ -4536,7 +4651,7 @@ var VMLElement = {
4536
4651
  }
4537
4652
 
4538
4653
  // apply the opacity
4539
- markup = ['<stroke color="black" opacity="', (0.05 * i), '"/>'];
4654
+ markup = ['<stroke color="', shadowOptions.color || 'black', '" opacity="', shadowElementOpacity * i, '"/>'];
4540
4655
  createElement(renderer.prepVML(markup), null, null, shadow);
4541
4656
 
4542
4657
 
@@ -4631,15 +4746,16 @@ var VMLRendererExtension = { // inherit SVGRenderer
4631
4746
  clipRect: function (x, y, width, height) {
4632
4747
 
4633
4748
  // create a dummy element
4634
- var clipRect = this.createElement();
4635
-
4749
+ var clipRect = this.createElement(),
4750
+ isObj = isObject(x);
4751
+
4636
4752
  // mimic a rectangle with its style object for automatic updating in attr
4637
4753
  return extend(clipRect, {
4638
4754
  members: [],
4639
- left: x,
4640
- top: y,
4641
- width: width,
4642
- height: height,
4755
+ left: isObj ? x.x : x,
4756
+ top: isObj ? x.y : y,
4757
+ width: isObj ? x.width : width,
4758
+ height: isObj ? x.height : height,
4643
4759
  getCSS: function (wrapper) {
4644
4760
  var inverted = wrapper.inverted,
4645
4761
  rect = this,
@@ -4683,8 +4799,9 @@ var VMLRendererExtension = { // inherit SVGRenderer
4683
4799
  *
4684
4800
  * @param {Object} color The color or config object
4685
4801
  */
4686
- color: function (color, elem, prop) {
4687
- var colorObject,
4802
+ color: function (color, elem, prop, wrapper) {
4803
+ var renderer = this,
4804
+ colorObject,
4688
4805
  regexRgba = /^rgba/,
4689
4806
  markup,
4690
4807
  fillType,
@@ -4707,7 +4824,6 @@ var VMLRendererExtension = { // inherit SVGRenderer
4707
4824
  y1,
4708
4825
  x2,
4709
4826
  y2,
4710
- angle,
4711
4827
  opacity1,
4712
4828
  opacity2,
4713
4829
  color1,
@@ -4716,7 +4832,14 @@ var VMLRendererExtension = { // inherit SVGRenderer
4716
4832
  stops = color.stops,
4717
4833
  firstStop,
4718
4834
  lastStop,
4719
- colors = [];
4835
+ colors = [],
4836
+ addFillNode = function () {
4837
+ // Add the fill subnode. When colors attribute is used, the meanings of opacity and o:opacity2
4838
+ // are reversed.
4839
+ markup = ['<fill colors="' + colors.join(',') + '" opacity="', opacity2, '" o:opacity2="', opacity1,
4840
+ '" type="', fillType, '" ', fillAttr, 'focus="100%" method="any" />'];
4841
+ createElement(renderer.prepVML(markup), null, null, elem);
4842
+ };
4720
4843
 
4721
4844
  // Extend from 0 to 1
4722
4845
  firstStop = stops[0];
@@ -4758,65 +4881,67 @@ var VMLRendererExtension = { // inherit SVGRenderer
4758
4881
  }
4759
4882
  });
4760
4883
 
4761
- // Handle linear gradient angle
4762
- if (fillType === 'gradient') {
4763
- x1 = gradient.x1 || gradient[0] || 0;
4764
- y1 = gradient.y1 || gradient[1] || 0;
4765
- x2 = gradient.x2 || gradient[2] || 0;
4766
- y2 = gradient.y2 || gradient[3] || 0;
4767
- angle = 90 - math.atan(
4768
- (y2 - y1) / // y vector
4769
- (x2 - x1) // x vector
4770
- ) * 180 / mathPI;
4771
-
4772
- // Radial (circular) gradient
4773
- } else {
4774
- // pie: http://jsfiddle.net/highcharts/66g8H/
4775
- // reference: http://jsfiddle.net/highcharts/etznJ/
4776
- // http://jsfiddle.net/highcharts/XRbCc/
4777
- // http://jsfiddle.net/highcharts/F3fwR/
4778
- // TODO:
4779
- // - correct for radialRefeence
4780
- // - check whether gradient stops are supported
4781
- // - add global option for gradient image (must relate to version)
4782
- var r = gradient.r,
4783
- size = r * 2,
4784
- cx = gradient.cx,
4785
- cy = gradient.cy;
4786
- //radialReference = elem.radialReference;
4787
-
4788
- //if (radialReference) {
4789
- // Try setting pixel size, or other way to adjust the gradient size to the bounding box
4790
- //}
4791
- fillAttr = 'src="http://code.highcharts.com/gfx/radial-gradient.png" ' +
4792
- 'size="' + size + ',' + size + '" ' +
4793
- 'origin="0.5,0.5" ' +
4794
- 'position="' + cx + ',' + cy + '" ' +
4795
- 'color2="' + color2 + '" ';
4796
-
4797
- // The fill element's color attribute is broken in IE8 standards mode, so we
4798
- // need to set the parent shape's fillcolor attribute instead.
4799
- ret = color1;
4800
- }
4801
-
4802
-
4803
-
4804
4884
  // Apply the gradient to fills only.
4805
4885
  if (prop === 'fill') {
4806
4886
 
4807
- // when colors attribute is used, the meanings of opacity and o:opacity2
4808
- // are reversed.
4809
- markup = ['<fill colors="' + colors.join(',') + '" angle="', angle,
4810
- '" opacity="', opacity2, '" o:opacity2="', opacity1,
4811
- '" type="', fillType, '" ', fillAttr, 'focus="100%" method="any" />'];
4812
- createElement(this.prepVML(markup), null, null, elem);
4887
+ // Handle linear gradient angle
4888
+ if (fillType === 'gradient') {
4889
+ x1 = gradient.x1 || gradient[0] || 0;
4890
+ y1 = gradient.y1 || gradient[1] || 0;
4891
+ x2 = gradient.x2 || gradient[2] || 0;
4892
+ y2 = gradient.y2 || gradient[3] || 0;
4893
+ fillAttr = 'angle="' + (90 - math.atan(
4894
+ (y2 - y1) / // y vector
4895
+ (x2 - x1) // x vector
4896
+ ) * 180 / mathPI) + '"';
4897
+
4898
+ addFillNode();
4899
+
4900
+ // Radial (circular) gradient
4901
+ } else {
4902
+
4903
+ var r = gradient.r,
4904
+ sizex = r * 2,
4905
+ sizey = r * 2,
4906
+ cx = gradient.cx,
4907
+ cy = gradient.cy,
4908
+ radialReference = elem.radialReference,
4909
+ bBox,
4910
+ applyRadialGradient = function () {
4911
+ if (radialReference) {
4912
+ bBox = wrapper.getBBox();
4913
+ cx += (radialReference[0] - bBox.x) / bBox.width - 0.5;
4914
+ cy += (radialReference[1] - bBox.y) / bBox.height - 0.5;
4915
+ sizex *= radialReference[2] / bBox.width;
4916
+ sizey *= radialReference[2] / bBox.height;
4917
+ }
4918
+ fillAttr = 'src="' + defaultOptions.global.VMLRadialGradientURL + '" ' +
4919
+ 'size="' + sizex + ',' + sizey + '" ' +
4920
+ 'origin="0.5,0.5" ' +
4921
+ 'position="' + cx + ',' + cy + '" ' +
4922
+ 'color2="' + color2 + '" ';
4923
+
4924
+ addFillNode();
4925
+ };
4926
+
4927
+ // Apply radial gradient
4928
+ if (wrapper.added) {
4929
+ applyRadialGradient();
4930
+ } else {
4931
+ // We need to know the bounding box to get the size and position right
4932
+ addEvent(wrapper, 'add', applyRadialGradient);
4933
+ }
4934
+
4935
+ // The fill element's color attribute is broken in IE8 standards mode, so we
4936
+ // need to set the parent shape's fillcolor attribute instead.
4937
+ ret = color1;
4938
+ }
4813
4939
 
4814
4940
  // Gradients are not supported for VML stroke, return the first color. #722.
4815
4941
  } else {
4816
4942
  ret = stopColor;
4817
4943
  }
4818
4944
 
4819
-
4820
4945
  // if the color is an rgba color, split it and add a fill node
4821
4946
  // to hold the opacity component
4822
4947
  } else if (regexRgba.test(color) && elem.tagName !== 'IMG') {
@@ -5021,11 +5146,12 @@ var VMLRendererExtension = { // inherit SVGRenderer
5021
5146
  y + radius * sinEnd // end y
5022
5147
  ];
5023
5148
 
5024
- if (options.open) {
5149
+ if (options.open && !innerRadius) {
5025
5150
  ret.push(
5151
+ 'e',
5026
5152
  M,
5027
- x - innerRadius,
5028
- y - innerRadius
5153
+ x,// - innerRadius,
5154
+ y// - innerRadius
5029
5155
  );
5030
5156
  }
5031
5157
 
@@ -5303,7 +5429,7 @@ Tick.prototype = {
5303
5429
  .attr(attr)
5304
5430
  // without position absolute, IE export sometimes is wrong
5305
5431
  .css(css)
5306
- .add(axis.axisGroup) :
5432
+ .add(axis.labelGroup) :
5307
5433
  null;
5308
5434
 
5309
5435
  // update
@@ -5322,7 +5448,7 @@ Tick.prototype = {
5322
5448
  var label = this.label,
5323
5449
  axis = this.axis;
5324
5450
  return label ?
5325
- ((this.labelBBox = label.getBBox(true)))[axis.horiz ? 'height' : 'width'] :
5451
+ ((this.labelBBox = label.getBBox()))[axis.horiz ? 'height' : 'width'] :
5326
5452
  0;
5327
5453
  },
5328
5454
 
@@ -5363,7 +5489,7 @@ Tick.prototype = {
5363
5489
  plotLeft = chart.plotLeft,
5364
5490
  plotRight = plotLeft + axis.len,
5365
5491
  neighbour = axis.ticks[tickPositions[index + (isFirst ? 1 : -1)]],
5366
- neighbourEdge = neighbour && neighbour.label.xy.x + neighbour.getLabelSides()[isFirst ? 0 : 1];
5492
+ neighbourEdge = neighbour && neighbour.label.xy && neighbour.label.xy.x + neighbour.getLabelSides()[isFirst ? 0 : 1];
5367
5493
 
5368
5494
  if ((isFirst && !reversed) || (isLast && reversed)) {
5369
5495
  // Is the label spilling out to the left of the plot area?
@@ -5496,7 +5622,7 @@ Tick.prototype = {
5496
5622
  step = labelOptions.step,
5497
5623
  attribs,
5498
5624
  show = true,
5499
- tickmarkOffset = (options.categories && options.tickmarkPlacement === 'between') ? 0.5 : 0,
5625
+ tickmarkOffset = axis.tickmarkOffset,
5500
5626
  xy = tick.getPosition(horiz, pos, tickmarkOffset, old),
5501
5627
  x = xy.x,
5502
5628
  y = xy.y,
@@ -5534,7 +5660,7 @@ Tick.prototype = {
5534
5660
  }
5535
5661
 
5536
5662
  // create the tick mark
5537
- if (tickWidth) {
5663
+ if (tickWidth && tickLength) {
5538
5664
 
5539
5665
  // negate the length
5540
5666
  if (tickPosition === 'inside') {
@@ -6139,6 +6265,8 @@ Axis.prototype = {
6139
6265
  // Tick intervals
6140
6266
  //axis.tickInterval = UNDEFINED;
6141
6267
  //axis.minorTickInterval = UNDEFINED;
6268
+
6269
+ axis.tickmarkOffset = (options.categories && options.tickmarkPlacement === 'between') ? 0.5 : 0;
6142
6270
 
6143
6271
  // Major ticks
6144
6272
  axis.ticks = {};
@@ -6231,7 +6359,10 @@ Axis.prototype = {
6231
6359
  this.isXAxis ? {} : this.defaultYAxisOptions,
6232
6360
  [this.defaultTopAxisOptions, this.defaultRightAxisOptions,
6233
6361
  this.defaultBottomAxisOptions, this.defaultLeftAxisOptions][this.side],
6234
- userOptions
6362
+ merge(
6363
+ defaultOptions[this.isXAxis ? 'xAxis' : 'yAxis'], // if set in setOptions (#1053)
6364
+ userOptions
6365
+ )
6235
6366
  );
6236
6367
  },
6237
6368
 
@@ -6242,29 +6373,43 @@ Axis.prototype = {
6242
6373
  defaultLabelFormatter: function () {
6243
6374
  var axis = this.axis,
6244
6375
  value = this.value,
6245
- categories = axis.categories,
6246
- tickInterval = axis.tickInterval,
6376
+ categories = axis.categories,
6247
6377
  dateTimeLabelFormat = this.dateTimeLabelFormat,
6248
- ret;
6378
+ numericSymbols = defaultOptions.lang.numericSymbols,
6379
+ i = numericSymbols && numericSymbols.length,
6380
+ multi,
6381
+ ret,
6382
+
6383
+ // make sure the same symbol is added for all labels on a linear axis
6384
+ numericSymbolDetector = axis.isLog ? value : axis.tickInterval;
6249
6385
 
6250
6386
  if (categories) {
6251
6387
  ret = value;
6252
6388
 
6253
6389
  } else if (dateTimeLabelFormat) { // datetime axis
6254
6390
  ret = dateFormat(dateTimeLabelFormat, value);
6391
+
6392
+ } else if (i && numericSymbolDetector >= 1000) {
6393
+ // Decide whether we should add a numeric symbol like k (thousands) or M (millions).
6394
+ // If we are to enable this in tooltip or other places as well, we can move this
6395
+ // logic to the numberFormatter and enable it by a parameter.
6396
+ while (i-- && ret === UNDEFINED) {
6397
+ multi = Math.pow(1000, i + 1);
6398
+ if (numericSymbolDetector >= multi && numericSymbols[i] !== null) {
6399
+ ret = numberFormat(value / multi, -1) + numericSymbols[i];
6400
+ }
6401
+ }
6402
+ }
6403
+
6404
+ if (ret === UNDEFINED) {
6405
+ if (value >= 1000) { // add thousands separators
6406
+ ret = numberFormat(value, 0);
6255
6407
 
6256
- } else if (tickInterval % 1000000 === 0) { // use M abbreviation
6257
- ret = (value / 1000000) + 'M';
6258
-
6259
- } else if (tickInterval % 1000 === 0) { // use k abbreviation
6260
- ret = (value / 1000) + 'k';
6261
-
6262
- } else if (value >= 1000) { // add thousands separators
6263
- ret = numberFormat(value, 0);
6264
-
6265
- } else { // small numbers
6266
- ret = numberFormat(value, -1);
6408
+ } else { // small numbers
6409
+ ret = numberFormat(value, -1);
6410
+ }
6267
6411
  }
6412
+
6268
6413
  return ret;
6269
6414
  },
6270
6415
 
@@ -6278,6 +6423,8 @@ Axis.prototype = {
6278
6423
  posStack = [],
6279
6424
  negStack = [],
6280
6425
  i;
6426
+
6427
+ axis.hasVisibleSeries = false;
6281
6428
 
6282
6429
  // reset dataMin and dataMax in case we're redrawing
6283
6430
  axis.dataMin = axis.dataMax = null;
@@ -6303,6 +6450,8 @@ Axis.prototype = {
6303
6450
  activeYData = [],
6304
6451
  activeCounter = 0;
6305
6452
 
6453
+ axis.hasVisibleSeries = true;
6454
+
6306
6455
  // Validate threshold in logarithmic axes
6307
6456
  if (axis.isLog && threshold <= 0) {
6308
6457
  threshold = seriesOptions.threshold = null;
@@ -6444,13 +6593,14 @@ Axis.prototype = {
6444
6593
  }
6445
6594
  }
6446
6595
  });
6596
+
6447
6597
  },
6448
6598
 
6449
6599
  /**
6450
6600
  * Translate from axis value to pixel position on the chart, or back
6451
6601
  *
6452
6602
  */
6453
- translate: function (val, backwards, cvsCoord, old, handleLog) {
6603
+ translate: function (val, backwards, cvsCoord, old, handleLog, pointPlacementBetween) {
6454
6604
  var axis = this,
6455
6605
  axisLength = axis.len,
6456
6606
  sign = 1,
@@ -6487,7 +6637,8 @@ Axis.prototype = {
6487
6637
  val = axis.val2lin(val);
6488
6638
  }
6489
6639
 
6490
- returnValue = sign * (val - localMin) * localA + cvsOffset + (sign * axis.minPixelPadding);
6640
+ returnValue = sign * (val - localMin) * localA + cvsOffset + (sign * axis.minPixelPadding) +
6641
+ (pointPlacementBetween ? localA * axis.pointRange / 2 : 0);
6491
6642
  }
6492
6643
 
6493
6644
  return returnValue;
@@ -6809,17 +6960,38 @@ Axis.prototype = {
6809
6960
  range = axis.max - axis.min,
6810
6961
  pointRange = 0,
6811
6962
  closestPointRange,
6812
- seriesClosestPointRange,
6963
+ minPointOffset = 0,
6964
+ pointRangePadding = 0,
6813
6965
  transA = axis.transA;
6814
6966
 
6815
6967
  // adjust translation for padding
6816
6968
  if (axis.isXAxis) {
6817
6969
  if (axis.isLinked) {
6818
- pointRange = axis.linkedParent.pointRange;
6970
+ minPointOffset = axis.linkedParent.minPointOffset;
6819
6971
  } else {
6820
6972
  each(axis.series, function (series) {
6821
- pointRange = mathMax(pointRange, series.pointRange);
6822
- seriesClosestPointRange = series.closestPointRange;
6973
+ var seriesPointRange = series.pointRange,
6974
+ pointPlacement = series.options.pointPlacement,
6975
+ seriesClosestPointRange = series.closestPointRange;
6976
+
6977
+ pointRange = mathMax(pointRange, seriesPointRange);
6978
+
6979
+ // minPointOffset is the value padding to the left of the axis in order to make
6980
+ // room for points with a pointRange, typically columns. When the pointPlacement option
6981
+ // is 'between' or 'on', this padding does not apply.
6982
+ minPointOffset = mathMax(
6983
+ minPointOffset,
6984
+ pointPlacement ? 0 : seriesPointRange / 2
6985
+ );
6986
+
6987
+ // Determine the total padding needed to the length of the axis to make room for the
6988
+ // pointRange. If the series' pointPlacement is 'on', no padding is added.
6989
+ pointRangePadding = mathMax(
6990
+ pointRangePadding,
6991
+ pointPlacement === 'on' ? 0 : seriesPointRange
6992
+ );
6993
+
6994
+ // Set the closestPointRange
6823
6995
  if (!series.noSharedTooltip && defined(seriesClosestPointRange)) {
6824
6996
  closestPointRange = defined(closestPointRange) ?
6825
6997
  mathMin(closestPointRange, seriesClosestPointRange) :
@@ -6827,6 +6999,9 @@ Axis.prototype = {
6827
6999
  }
6828
7000
  });
6829
7001
  }
7002
+
7003
+ // Record minPointOffse
7004
+ axis.minPointOffset = minPointOffset;
6830
7005
 
6831
7006
  // pointRange means the width reserved for each point, like in a column chart
6832
7007
  axis.pointRange = pointRange;
@@ -6839,9 +7014,10 @@ Axis.prototype = {
6839
7014
 
6840
7015
  // secondary values
6841
7016
  axis.oldTransA = transA;
6842
- axis.translationSlope = axis.transA = transA = axis.len / ((range + pointRange) || 1);
7017
+ //axis.translationSlope = axis.transA = transA = axis.len / ((range + (2 * minPointOffset)) || 1);
7018
+ axis.translationSlope = axis.transA = transA = axis.len / ((range + pointRangePadding) || 1);
6843
7019
  axis.transB = axis.horiz ? axis.left : axis.bottom; // translation addend
6844
- axis.minPixelPadding = transA * (pointRange / 2);
7020
+ axis.minPixelPadding = transA * minPointOffset;
6845
7021
  },
6846
7022
 
6847
7023
  /**
@@ -6863,6 +7039,7 @@ Axis.prototype = {
6863
7039
  length,
6864
7040
  linkedParentExtremes,
6865
7041
  tickIntervalOption = options.tickInterval,
7042
+ minTickIntervalOption = options.minTickInterval,
6866
7043
  tickPixelIntervalOption = options.tickPixelInterval,
6867
7044
  tickPositions,
6868
7045
  categories = axis.categories;
@@ -6936,9 +7113,9 @@ Axis.prototype = {
6936
7113
  }
6937
7114
 
6938
7115
  // set the translation factor used in translate function
6939
- axis.setAxisTranslation();
7116
+ axis.setAxisTranslation(secondPass);
6940
7117
 
6941
- // hook for ordinal axes. To do: merge with below
7118
+ // hook for ordinal axes and radial axes
6942
7119
  if (axis.beforeSetTickPositions) {
6943
7120
  axis.beforeSetTickPositions();
6944
7121
  }
@@ -6947,11 +7124,16 @@ Axis.prototype = {
6947
7124
  if (axis.postProcessTickInterval) {
6948
7125
  axis.tickInterval = axis.postProcessTickInterval(axis.tickInterval);
6949
7126
  }
7127
+
7128
+ // Before normalizing the tick interval, handle minimum tick interval. This applies only if tickInterval is not defined.
7129
+ if (!tickIntervalOption && axis.tickInterval < minTickIntervalOption) {
7130
+ axis.tickInterval = minTickIntervalOption;
7131
+ }
6950
7132
 
6951
7133
  // for linear axes, get magnitude and normalize the interval
6952
7134
  if (!isDatetimeAxis && !isLog) { // linear
6953
7135
  magnitude = math.pow(10, mathFloor(math.log(axis.tickInterval) / math.LN10));
6954
- if (!defined(options.tickInterval)) {
7136
+ if (!tickIntervalOption) {
6955
7137
  axis.tickInterval = normalizeTickInterval(axis.tickInterval, null, magnitude, options);
6956
7138
  }
6957
7139
  }
@@ -6985,17 +7167,18 @@ Axis.prototype = {
6985
7167
 
6986
7168
  // reset min/max or remove extremes based on start/end on tick
6987
7169
  var roundedMin = tickPositions[0],
6988
- roundedMax = tickPositions[tickPositions.length - 1];
7170
+ roundedMax = tickPositions[tickPositions.length - 1],
7171
+ minPointOffset = axis.minPointOffset || 0;
6989
7172
 
6990
7173
  if (options.startOnTick) {
6991
7174
  axis.min = roundedMin;
6992
- } else if (axis.min > roundedMin) {
7175
+ } else if (axis.min - minPointOffset > roundedMin) {
6993
7176
  tickPositions.shift();
6994
7177
  }
6995
7178
 
6996
7179
  if (options.endOnTick) {
6997
7180
  axis.max = roundedMax;
6998
- } else if (axis.max < roundedMax) {
7181
+ } else if (axis.max + minPointOffset < roundedMax) {
6999
7182
  tickPositions.pop();
7000
7183
  }
7001
7184
 
@@ -7161,6 +7344,15 @@ Axis.prototype = {
7161
7344
  });
7162
7345
  },
7163
7346
 
7347
+ /**
7348
+ * Overridable method for zooming chart. Pulled out in a separate method to allow overriding
7349
+ * in stock charts.
7350
+ */
7351
+ zoom: function (newMin, newMax) {
7352
+ this.setExtremes(newMin, newMax, false, UNDEFINED, { trigger: 'zoom' });
7353
+ return true;
7354
+ },
7355
+
7164
7356
  /**
7165
7357
  * Update the axis metrics
7166
7358
  */
@@ -7257,17 +7449,21 @@ Axis.prototype = {
7257
7449
 
7258
7450
 
7259
7451
  // For reuse in Axis.render
7260
- axis.hasData = hasData = axis.series.length && defined(axis.min) && defined(axis.max);
7452
+ axis.hasData = hasData = (axis.hasVisibleSeries || (defined(axis.min) && defined(axis.max) && !!tickPositions));
7261
7453
  axis.showAxis = showAxis = hasData || pick(options.showEmpty, true);
7262
-
7454
+
7455
+
7263
7456
  // Create the axisGroup and gridGroup elements on first iteration
7264
7457
  if (!axis.axisGroup) {
7265
- axis.axisGroup = renderer.g('axis')
7266
- .attr({ zIndex: options.zIndex || 7 })
7267
- .add();
7268
7458
  axis.gridGroup = renderer.g('grid')
7269
7459
  .attr({ zIndex: options.gridZIndex || 1 })
7270
7460
  .add();
7461
+ axis.axisGroup = renderer.g('axis')
7462
+ .attr({ zIndex: options.zIndex || 2 })
7463
+ .add();
7464
+ axis.labelGroup = renderer.g('axis-labels')
7465
+ .attr({ zIndex: labelOptions.zIndex || 7 })
7466
+ .add();
7271
7467
  }
7272
7468
 
7273
7469
  if (hasData || axis.isLinked) {
@@ -7361,6 +7557,8 @@ Axis.prototype = {
7361
7557
  horiz = this.horiz,
7362
7558
  lineLeft = this.left + (opposite ? this.width : 0) + offset,
7363
7559
  lineTop = chart.chartHeight - this.bottom - (opposite ? this.height : 0) + offset;
7560
+
7561
+ this.lineTop = lineTop; // used by flag series
7364
7562
 
7365
7563
  return chart.renderer.crispLine([
7366
7564
  M,
@@ -7438,6 +7636,7 @@ Axis.prototype = {
7438
7636
  alternateBands = axis.alternateBands,
7439
7637
  stackLabelOptions = options.stackLabels,
7440
7638
  alternateGridColor = options.alternateGridColor,
7639
+ tickmarkOffset = axis.tickmarkOffset,
7441
7640
  lineWidth = options.lineWidth,
7442
7641
  linePath,
7443
7642
  hasRendered = chart.hasRendered,
@@ -7500,8 +7699,8 @@ Axis.prototype = {
7500
7699
  if (!alternateBands[pos]) {
7501
7700
  alternateBands[pos] = new PlotLineOrBand(axis);
7502
7701
  }
7503
- from = pos;
7504
- to = tickPositions[i + 1] !== UNDEFINED ? tickPositions[i + 1] : axis.max;
7702
+ from = pos + tickmarkOffset; // #949
7703
+ to = tickPositions[i + 1] !== UNDEFINED ? tickPositions[i + 1] + tickmarkOffset : axis.max;
7505
7704
  alternateBands[pos].options = {
7506
7705
  from: isLog ? lin2log(from) : from,
7507
7706
  to: isLog ? lin2log(to) : to,
@@ -7549,14 +7748,13 @@ Axis.prototype = {
7549
7748
  'stroke-width': lineWidth,
7550
7749
  zIndex: 7
7551
7750
  })
7552
- .add();
7751
+ .add(axis.axisGroup);
7553
7752
  } else {
7554
7753
  axis.axisLine.animate({ d: linePath });
7555
7754
  }
7556
7755
 
7557
7756
  // show or hide the line depending on options.showEmpty
7558
7757
  axis.axisLine[showAxis ? 'show' : 'hide']();
7559
-
7560
7758
  }
7561
7759
 
7562
7760
  if (axisTitle && showAxis) {
@@ -7618,15 +7816,14 @@ Axis.prototype = {
7618
7816
  * Update the axis title by options
7619
7817
  */
7620
7818
  setTitle: function (newTitleOptions, redraw) {
7621
- var axis = this,
7622
- chart = axis.chart,
7623
- options = axis.options,
7624
- axisTitle;
7819
+ var chart = this.chart,
7820
+ options = this.options,
7821
+ axisTitle = this.axisTitle;
7625
7822
 
7626
7823
  options.title = merge(options.title, newTitleOptions);
7627
7824
 
7628
- axis.axisTitle = axisTitle && axisTitle.destroy(); // #922
7629
- axis.isDirty = true;
7825
+ this.axisTitle = axisTitle && axisTitle.destroy(); // #922
7826
+ this.isDirty = true;
7630
7827
 
7631
7828
  if (pick(redraw, true)) {
7632
7829
  chart.redraw();
@@ -7711,7 +7908,7 @@ Axis.prototype = {
7711
7908
  });
7712
7909
 
7713
7910
  // Destroy local variables
7714
- each(['stackTotalGroup', 'axisLine', 'axisGroup', 'gridGroup', 'axisTitle'], function (prop) {
7911
+ each(['stackTotalGroup', 'axisLine', 'axisGroup', 'gridGroup', 'labelGroup', 'axisTitle'], function (prop) {
7715
7912
  if (axis[prop]) {
7716
7913
  axis[prop] = axis[prop].destroy();
7717
7914
  }
@@ -7729,16 +7926,12 @@ Axis.prototype = {
7729
7926
  function Tooltip(chart, options) {
7730
7927
  var borderWidth = options.borderWidth,
7731
7928
  style = options.style,
7732
- shared = options.shared,
7733
7929
  padding = pInt(style.padding);
7734
7930
 
7735
7931
  // Save the chart and options
7736
7932
  this.chart = chart;
7737
7933
  this.options = options;
7738
7934
 
7739
- // remove padding CSS and apply padding on box instead
7740
- style.padding = 0;
7741
-
7742
7935
  // Keep track of the current series
7743
7936
  //this.currentSeries = UNDEFINED;
7744
7937
 
@@ -7746,17 +7939,16 @@ function Tooltip(chart, options) {
7746
7939
  this.crosshairs = [];
7747
7940
 
7748
7941
  // Current values of x and y when animating
7749
- this.currentX = 0;
7750
- this.currentY = 0;
7942
+ this.now = { x: 0, y: 0 };
7751
7943
 
7752
7944
  // The tooltipTick function, initialized to nothing
7753
7945
  //this.tooltipTick = UNDEFINED;
7754
7946
 
7755
7947
  // The tooltip is initially hidden
7756
- this.tooltipIsHidden = true;
7948
+ this.isHidden = true;
7757
7949
 
7758
7950
  // create the label
7759
- this.label = chart.renderer.label('', 0, 0, null, null, null, options.useHTML, null, 'tooltip')
7951
+ this.label = chart.renderer.label('', 0, 0, options.shape, null, null, options.useHTML, null, 'tooltip')
7760
7952
  .attr({
7761
7953
  padding: padding,
7762
7954
  fill: options.backgroundColor,
@@ -7765,6 +7957,7 @@ function Tooltip(chart, options) {
7765
7957
  zIndex: 8
7766
7958
  })
7767
7959
  .css(style)
7960
+ .css({ padding: 0 }) // Remove it from VML, the padding is applied as an attribute instead (#1117)
7768
7961
  .hide()
7769
7962
  .add();
7770
7963
 
@@ -7775,7 +7968,7 @@ function Tooltip(chart, options) {
7775
7968
  }
7776
7969
 
7777
7970
  // Public property for getting the shared state.
7778
- this.shared = shared;
7971
+ this.shared = options.shared;
7779
7972
  }
7780
7973
 
7781
7974
  Tooltip.prototype = {
@@ -7798,24 +7991,30 @@ Tooltip.prototype = {
7798
7991
  /**
7799
7992
  * Provide a soft movement for the tooltip
7800
7993
  *
7801
- * @param {Number} finalX
7802
- * @param {Number} finalY
7994
+ * @param {Number} x
7995
+ * @param {Number} y
7803
7996
  * @private
7804
7997
  */
7805
- move: function (finalX, finalY) {
7806
- var tooltip = this;
7998
+ move: function (x, y, anchorX, anchorY) {
7999
+ var tooltip = this,
8000
+ now = tooltip.now,
8001
+ animate = tooltip.options.animation !== false && !tooltip.isHidden;
7807
8002
 
7808
8003
  // get intermediate values for animation
7809
- tooltip.currentX = tooltip.tooltipIsHidden ? finalX : (2 * tooltip.currentX + finalX) / 3;
7810
- tooltip.currentY = tooltip.tooltipIsHidden ? finalY : (tooltip.currentY + finalY) / 2;
8004
+ extend(now, {
8005
+ x: animate ? (2 * now.x + x) / 3 : x,
8006
+ y: animate ? (now.y + y) / 2 : y,
8007
+ anchorX: animate ? (2 * now.anchorX + anchorX) / 3 : anchorX,
8008
+ anchorY: animate ? (now.anchorY + anchorY) / 2 : anchorY
8009
+ });
7811
8010
 
7812
8011
  // move to the intermediate value
7813
- tooltip.label.attr({ x: tooltip.currentX, y: tooltip.currentY });
8012
+ tooltip.label.attr(now);
7814
8013
 
7815
8014
  // run on next tick of the mouse tracker
7816
- if (mathAbs(finalX - tooltip.currentX) > 1 || mathAbs(finalY - tooltip.currentY) > 1) {
8015
+ if (animate && (mathAbs(x - now.x) > 1 || mathAbs(y - now.y) > 1)) {
7817
8016
  tooltip.tooltipTick = function () {
7818
- tooltip.move(finalX, finalY);
8017
+ tooltip.move(x, y, anchorX, anchorY);
7819
8018
  };
7820
8019
  } else {
7821
8020
  tooltip.tooltipTick = null;
@@ -7826,7 +8025,7 @@ Tooltip.prototype = {
7826
8025
  * Hide the tooltip
7827
8026
  */
7828
8027
  hide: function () {
7829
- if (!this.tooltipIsHidden) {
8028
+ if (!this.isHidden) {
7830
8029
  var hoverPoints = this.chart.hoverPoints;
7831
8030
 
7832
8031
  this.label.hide();
@@ -7839,7 +8038,7 @@ Tooltip.prototype = {
7839
8038
  }
7840
8039
 
7841
8040
  this.chart.hoverPoints = null;
7842
- this.tooltipIsHidden = true;
8041
+ this.isHidden = true;
7843
8042
  }
7844
8043
  },
7845
8044
 
@@ -7863,7 +8062,8 @@ Tooltip.prototype = {
7863
8062
  chart = this.chart,
7864
8063
  inverted = chart.inverted,
7865
8064
  plotX = 0,
7866
- plotY = 0;
8065
+ plotY = 0,
8066
+ yAxis;
7867
8067
 
7868
8068
  points = splat(points);
7869
8069
 
@@ -7873,8 +8073,10 @@ Tooltip.prototype = {
7873
8073
  // When shared, use the average position
7874
8074
  if (!ret) {
7875
8075
  each(points, function (point) {
8076
+ yAxis = point.series.yAxis;
7876
8077
  plotX += point.plotX;
7877
- plotY += point.plotLow ? (point.plotLow + point.plotHigh) / 2 : point.plotY;
8078
+ plotY += (point.plotLow ? (point.plotLow + point.plotHigh) / 2 : point.plotY) +
8079
+ (!inverted && yAxis ? yAxis.top - chart.plotTop : 0); // #1151
7878
8080
  });
7879
8081
 
7880
8082
  plotX /= points.length;
@@ -8002,12 +8204,13 @@ Tooltip.prototype = {
8002
8204
  if (shared && !(point.series && point.series.noSharedTooltip)) {
8003
8205
 
8004
8206
  // hide previous hoverPoints and set new
8207
+
8208
+ chart.hoverPoints = point;
8005
8209
  if (hoverPoints) {
8006
8210
  each(hoverPoints, function (point) {
8007
8211
  point.setState();
8008
8212
  });
8009
8213
  }
8010
- chart.hoverPoints = point;
8011
8214
 
8012
8215
  each(point, function (item) {
8013
8216
  item.setState(HOVER_STATE);
@@ -8041,7 +8244,7 @@ Tooltip.prototype = {
8041
8244
  } else {
8042
8245
 
8043
8246
  // show it
8044
- if (tooltip.tooltipIsHidden) {
8247
+ if (tooltip.isHidden) {
8045
8248
  label.show();
8046
8249
  }
8047
8250
 
@@ -8064,10 +8267,15 @@ Tooltip.prototype = {
8064
8267
  );
8065
8268
 
8066
8269
  // do the move
8067
- tooltip.move(mathRound(placedTooltipPoint.x), mathRound(placedTooltipPoint.y));
8270
+ tooltip.move(
8271
+ mathRound(placedTooltipPoint.x),
8272
+ mathRound(placedTooltipPoint.y),
8273
+ x + chart.plotLeft,
8274
+ y + chart.plotTop
8275
+ );
8068
8276
 
8069
8277
 
8070
- tooltip.tooltipIsHidden = false;
8278
+ tooltip.isHidden = false;
8071
8279
  }
8072
8280
 
8073
8281
  // crosshairs
@@ -8176,16 +8384,9 @@ MouseTracker.prototype = {
8176
8384
  e.target = e.srcElement;
8177
8385
  }
8178
8386
 
8179
- // jQuery only copies over some properties. IE needs e.x and iOS needs touches.
8180
- if (e.originalEvent) {
8181
- e = e.originalEvent;
8182
- }
8183
-
8184
- // The same for MooTools. It renames e.pageX to e.page.x. #445.
8185
- if (e.event) {
8186
- e = e.event;
8187
- }
8188
-
8387
+ // Framework specific normalizing (#1165)
8388
+ e = washMouseEvent(e);
8389
+
8189
8390
  // iOS
8190
8391
  ePos = e.touches ? e.touches.item(0) : e;
8191
8392
 
@@ -8204,7 +8405,6 @@ MouseTracker.prototype = {
8204
8405
  return extend(e, {
8205
8406
  chartX: mathRound(chartX),
8206
8407
  chartY: mathRound(chartY)
8207
-
8208
8408
  });
8209
8409
  },
8210
8410
 
@@ -8227,15 +8427,26 @@ MouseTracker.prototype = {
8227
8427
  coordinates[isXAxis ? 'xAxis' : 'yAxis'].push({
8228
8428
  axis: axis,
8229
8429
  value: axis.translate(
8230
- isHorizontal ?
8430
+ (isHorizontal ?
8231
8431
  e.chartX - chart.plotLeft :
8232
- chart.plotHeight - e.chartY + chart.plotTop,
8432
+ axis.top + axis.len - e.chartY) - axis.minPixelPadding, // #1051
8233
8433
  true
8234
8434
  )
8235
8435
  });
8236
8436
  });
8237
8437
  return coordinates;
8238
8438
  },
8439
+
8440
+ /**
8441
+ * Return the index in the tooltipPoints array, corresponding to pixel position in
8442
+ * the plot area.
8443
+ */
8444
+ getIndex: function (e) {
8445
+ var chart = this.chart;
8446
+ return chart.inverted ?
8447
+ chart.plotHeight + chart.plotTop - e.chartY :
8448
+ e.chartX - chart.plotLeft;
8449
+ },
8239
8450
 
8240
8451
  /**
8241
8452
  * With line type charts with a single tracker, get the point closest to the mouse
@@ -8244,6 +8455,7 @@ MouseTracker.prototype = {
8244
8455
  var mouseTracker = this,
8245
8456
  chart = mouseTracker.chart,
8246
8457
  series = chart.series,
8458
+ tooltip = chart.tooltip,
8247
8459
  point,
8248
8460
  points,
8249
8461
  hoverPoint = chart.hoverPoint,
@@ -8251,11 +8463,10 @@ MouseTracker.prototype = {
8251
8463
  i,
8252
8464
  j,
8253
8465
  distance = chart.chartWidth,
8254
- // the index in the tooltipPoints array, corresponding to pixel position in plot area
8255
- index = chart.inverted ? chart.plotHeight + chart.plotTop - e.chartY : e.chartX - chart.plotLeft;
8466
+ index = mouseTracker.getIndex(e);
8256
8467
 
8257
8468
  // shared tooltip
8258
- if (chart.tooltip && mouseTracker.options.tooltip.shared && !(hoverSeries && hoverSeries.noSharedTooltip)) {
8469
+ if (tooltip && mouseTracker.options.tooltip.shared && !(hoverSeries && hoverSeries.noSharedTooltip)) {
8259
8470
  points = [];
8260
8471
 
8261
8472
  // loop over all series and find the ones with points closest to the mouse
@@ -8265,7 +8476,7 @@ MouseTracker.prototype = {
8265
8476
  series[j].options.enableMouseTracking !== false &&
8266
8477
  !series[j].noSharedTooltip && series[j].tooltipPoints.length) {
8267
8478
  point = series[j].tooltipPoints[index];
8268
- point._dist = mathAbs(index - point.plotX);
8479
+ point._dist = mathAbs(index - point[series[j].xAxis.tooltipPosName || 'plotX']);
8269
8480
  distance = mathMin(distance, point._dist);
8270
8481
  points.push(point);
8271
8482
  }
@@ -8279,7 +8490,7 @@ MouseTracker.prototype = {
8279
8490
  }
8280
8491
  // refresh the tooltip if necessary
8281
8492
  if (points.length && (points[0].plotX !== mouseTracker.hoverX)) {
8282
- chart.tooltip.refresh(points, e);
8493
+ tooltip.refresh(points, e);
8283
8494
  mouseTracker.hoverX = points[0].plotX;
8284
8495
  }
8285
8496
  }
@@ -8304,14 +8515,16 @@ MouseTracker.prototype = {
8304
8515
 
8305
8516
  /**
8306
8517
  * Reset the tracking by hiding the tooltip, the hover series state and the hover point
8518
+ *
8519
+ * @param allowMove {Boolean} Instead of destroying the tooltip altogether, allow moving it if possible
8307
8520
  */
8308
8521
  resetTracker: function (allowMove) {
8309
8522
  var mouseTracker = this,
8310
8523
  chart = mouseTracker.chart,
8311
8524
  hoverSeries = chart.hoverSeries,
8312
8525
  hoverPoint = chart.hoverPoint,
8313
- tooltipPoints = chart.hoverPoints || hoverPoint,
8314
- tooltip = chart.tooltip;
8526
+ tooltip = chart.tooltip,
8527
+ tooltipPoints = tooltip && tooltip.shared ? chart.hoverPoints : hoverPoint;
8315
8528
 
8316
8529
  // Narrow in allowMove
8317
8530
  allowMove = allowMove && tooltip && tooltipPoints;
@@ -8390,9 +8603,10 @@ MouseTracker.prototype = {
8390
8603
  1
8391
8604
  ),
8392
8605
  selectionMax = axis.translate(
8393
- isHorizontal ?
8394
- selectionLeft + selectionBox.width :
8395
- chart.plotHeight - selectionTop,
8606
+ (isHorizontal ?
8607
+ selectionLeft + selectionBox.width :
8608
+ chart.plotHeight - selectionTop) -
8609
+ 2 * axis.minPixelPadding, // #875
8396
8610
  true,
8397
8611
  0,
8398
8612
  0,
@@ -8432,7 +8646,7 @@ MouseTracker.prototype = {
8432
8646
  mouseTracker.hideTooltipOnMouseMove = function (e) {
8433
8647
 
8434
8648
  // Get e.pageX and e.pageY back in MooTools
8435
- washMouseEvent(e);
8649
+ e = washMouseEvent(e);
8436
8650
 
8437
8651
  // If we're outside, hide the tooltip
8438
8652
  if (mouseTracker.chartPosition && chart.hoverSeries && chart.hoverSeries.isCartesian &&
@@ -8574,8 +8788,10 @@ MouseTracker.prototype = {
8574
8788
  }
8575
8789
  }
8576
8790
 
8577
- } else if (!isOutsidePlot) {
8578
- // show the tooltip
8791
+ }
8792
+
8793
+ // Show the tooltip and run mouse over events (#977)
8794
+ if (!isOutsidePlot) {
8579
8795
  mouseTracker.onmousemove(e);
8580
8796
  }
8581
8797
 
@@ -8706,7 +8922,12 @@ MouseTracker.prototype = {
8706
8922
  chart.tooltip = new Tooltip(chart, options);
8707
8923
 
8708
8924
  // set the fixed interval ticking for the smooth tooltip
8709
- this.tooltipInterval = setInterval(function () { chart.tooltip.tick(); }, 32);
8925
+ this.tooltipInterval = setInterval(function () {
8926
+ // The interval function may still be running during destroy, so check that the chart is really there before calling.
8927
+ if (chart && chart.tooltip) {
8928
+ chart.tooltip.tick();
8929
+ }
8930
+ }, 32);
8710
8931
  }
8711
8932
 
8712
8933
  this.setDOMEvents();
@@ -9101,7 +9322,7 @@ Legend.prototype = {
9101
9322
 
9102
9323
  // sort by legendIndex
9103
9324
  stableSort(allItems, function (a, b) {
9104
- return (a.options.legendIndex || 0) - (b.options.legendIndex || 0);
9325
+ return ((a.options && a.options.legendIndex) || 0) - ((b.options && b.options.legendIndex) || 0);
9105
9326
  });
9106
9327
 
9107
9328
  // reversed legend
@@ -9202,7 +9423,7 @@ Legend.prototype = {
9202
9423
  optionsY = options.y,
9203
9424
  alignTop = options.verticalAlign === 'top',
9204
9425
  spaceHeight = chart.spacingBox.height + (alignTop ? -optionsY : optionsY) - this.padding,
9205
- maxHeight = options.maxHeight, // docs
9426
+ maxHeight = options.maxHeight,
9206
9427
  clipHeight,
9207
9428
  clipRect = this.clipRect,
9208
9429
  navOptions = options.navigation,
@@ -9433,10 +9654,8 @@ Chart.prototype = {
9433
9654
  redraw = pick(redraw, true); // defaults to true
9434
9655
 
9435
9656
  fireEvent(chart, 'addSeries', { options: options }, function () {
9436
- chart.initSeries(options);
9437
- //series = chart.initSeries(options);
9438
- //series.isDirty = true;
9439
-
9657
+ series = chart.initSeries(options);
9658
+
9440
9659
  chart.isDirtyLegend = true; // the series array is out of sync with the display
9441
9660
  if (redraw) {
9442
9661
  chart.redraw();
@@ -9450,10 +9669,14 @@ Chart.prototype = {
9450
9669
  /**
9451
9670
  * Check whether a given point is within the plot area
9452
9671
  *
9453
- * @param {Number} x Pixel x relative to the plot area
9454
- * @param {Number} y Pixel y relative to the plot area
9672
+ * @param {Number} plotX Pixel x relative to the plot area
9673
+ * @param {Number} plotY Pixel y relative to the plot area
9674
+ * @param {Boolean} inverted Whether the chart is inverted
9455
9675
  */
9456
- isInsidePlot: function (x, y) {
9676
+ isInsidePlot: function (plotX, plotY, inverted) {
9677
+ var x = inverted ? plotY : plotX,
9678
+ y = inverted ? plotX : plotY;
9679
+
9457
9680
  return x >= 0 &&
9458
9681
  x <= this.plotWidth &&
9459
9682
  y >= 0 &&
@@ -9489,7 +9712,6 @@ Chart.prototype = {
9489
9712
  isDirtyBox = chart.isDirtyBox, // todo: check if it has actually changed?
9490
9713
  seriesLength = series.length,
9491
9714
  i = seriesLength,
9492
- clipRect = chart.clipRect,
9493
9715
  serie,
9494
9716
  renderer = chart.renderer,
9495
9717
  isHiddenChart = renderer.isHidden();
@@ -9571,16 +9793,6 @@ Chart.prototype = {
9571
9793
  // the plot areas size has changed
9572
9794
  if (isDirtyBox) {
9573
9795
  chart.drawChartBox();
9574
-
9575
- // move clip rect
9576
- if (clipRect) {
9577
- stop(clipRect);
9578
- clipRect.animate({ // for chart resize
9579
- width: chart.plotSizeX,
9580
- height: chart.plotSizeY + 1
9581
- });
9582
- }
9583
-
9584
9796
  }
9585
9797
 
9586
9798
 
@@ -9784,19 +9996,17 @@ Chart.prototype = {
9784
9996
  btnOptions = chart.options.chart.resetZoomButton,
9785
9997
  theme = btnOptions.theme,
9786
9998
  states = theme.states,
9787
- box = btnOptions.relativeTo === 'chart' ? null : {
9788
- x: chart.plotLeft,
9789
- y: chart.plotTop,
9790
- width: chart.plotWidth,
9791
- height: chart.plotHeight
9792
- };
9999
+ alignTo = btnOptions.relativeTo === 'chart' ? null : 'plotBox';
10000
+
9793
10001
  this.resetZoomButton = chart.renderer.button(lang.resetZoom, null, null, function () { chart.zoomOut(); }, theme, states && states.hover)
9794
10002
  .attr({
9795
10003
  align: btnOptions.position.align,
9796
10004
  title: lang.resetZoomTitle
9797
10005
  })
9798
10006
  .add()
9799
- .align(btnOptions.position, false, box);
10007
+ .align(btnOptions.position, false, chart[alignTo]);
10008
+ this.resetZoomButton.alignTo = alignTo;
10009
+
9800
10010
  },
9801
10011
 
9802
10012
  /**
@@ -9818,22 +10028,12 @@ Chart.prototype = {
9818
10028
  */
9819
10029
  zoom: function (event) {
9820
10030
  var chart = this,
9821
- optionsChart = chart.options.chart;
9822
-
9823
- // add button to reset selection
9824
- var hasZoomed;
9825
-
9826
- if (chart.resetZoomEnabled !== false && !chart.resetZoomButton) { // hook for Stock charts etc.
9827
- chart.showResetZoom();
9828
- }
10031
+ hasZoomed;
9829
10032
 
9830
10033
  // if zoom is called with no arguments, reset the axes
9831
10034
  if (!event || event.resetSelection) {
9832
10035
  each(chart.axes, function (axis) {
9833
- if (axis.options.zoomEnabled !== false) {
9834
- axis.setExtremes(null, null, false);
9835
- hasZoomed = true;
9836
- }
10036
+ hasZoomed = axis.zoom();
9837
10037
  });
9838
10038
  } else { // else, zoom in on all axes
9839
10039
  each(event.xAxis.concat(event.yAxis), function (axisData) {
@@ -9841,16 +10041,21 @@ Chart.prototype = {
9841
10041
 
9842
10042
  // don't zoom more than minRange
9843
10043
  if (chart.tracker[axis.isXAxis ? 'zoomX' : 'zoomY']) {
9844
- axis.setExtremes(axisData.min, axisData.max, false);
9845
- hasZoomed = true;
10044
+ hasZoomed = axis.zoom(axisData.min, axisData.max);
9846
10045
  }
9847
10046
  });
9848
10047
  }
10048
+
10049
+ // Show the Reset zoom button
10050
+ if (!chart.resetZoomButton) {
10051
+ chart.showResetZoom();
10052
+ }
10053
+
9849
10054
 
9850
10055
  // Redraw
9851
10056
  if (hasZoomed) {
9852
10057
  chart.redraw(
9853
- pick(optionsChart.animation, chart.pointCount < 100) // animation
10058
+ pick(chart.options.chart.animation, chart.pointCount < 100) // animation
9854
10059
  );
9855
10060
  }
9856
10061
  },
@@ -9879,7 +10084,7 @@ Chart.prototype = {
9879
10084
  }
9880
10085
 
9881
10086
  if (xAxis.series.length && newMin > mathMin(extremes.dataMin, extremes.min) && newMax < mathMax(extremes.dataMax, extremes.max)) {
9882
- xAxis.setExtremes(newMin, newMax, true, false);
10087
+ xAxis.setExtremes(newMin, newMax, true, false, { trigger: 'pan' });
9883
10088
  }
9884
10089
 
9885
10090
  chart.mouseDownX = chartX; // set new reference for next run
@@ -9913,8 +10118,9 @@ Chart.prototype = {
9913
10118
  chartTitleOptions = arr[2];
9914
10119
 
9915
10120
  if (title && titleOptions) {
9916
- title = title.destroy(); // remove old
10121
+ chart[name] = title = title.destroy(); // remove old
9917
10122
  }
10123
+
9918
10124
  if (chartTitleOptions && chartTitleOptions.text && !title) {
9919
10125
  chart[name] = chart.renderer.text(
9920
10126
  chartTitleOptions.text,
@@ -10040,7 +10246,8 @@ Chart.prototype = {
10040
10246
  width: chartWidth + PX,
10041
10247
  height: chartHeight + PX,
10042
10248
  textAlign: 'left',
10043
- lineHeight: 'normal' // #427
10249
+ lineHeight: 'normal', // #427
10250
+ zIndex: 0 // #1072
10044
10251
  }, optionsChart.style),
10045
10252
  chart.renderToClone || renderTo
10046
10253
  );
@@ -10200,19 +10407,6 @@ Chart.prototype = {
10200
10407
  });
10201
10408
  },
10202
10409
 
10203
- /**
10204
- * Fires endResize event on chart instance.
10205
- */
10206
- fireEndResize: function () {
10207
- var chart = this;
10208
-
10209
- if (chart) {
10210
- fireEvent(chart, 'endResize', null, function () {
10211
- chart.isResizing -= 1;
10212
- });
10213
- }
10214
- },
10215
-
10216
10410
  /**
10217
10411
  * Resize the chart to a given width and height
10218
10412
  * @param {Number} width
@@ -10225,10 +10419,20 @@ Chart.prototype = {
10225
10419
  chartWidth,
10226
10420
  chartHeight,
10227
10421
  spacingBox,
10422
+ resetZoomButton = chart.resetZoomButton,
10228
10423
  chartTitle = chart.title,
10229
- chartSubtitle = chart.subtitle;
10424
+ chartSubtitle = chart.subtitle,
10425
+ fireEndResize;
10230
10426
 
10427
+ // Handle the isResizing counter
10231
10428
  chart.isResizing += 1;
10429
+ fireEndResize = function () {
10430
+ if (chart) {
10431
+ fireEvent(chart, 'endResize', null, function () {
10432
+ chart.isResizing -= 1;
10433
+ });
10434
+ }
10435
+ };
10232
10436
 
10233
10437
  // set the animation for the current process
10234
10438
  setAnimation(animation, chart);
@@ -10277,6 +10481,11 @@ Chart.prototype = {
10277
10481
  if (chartSubtitle) {
10278
10482
  chartSubtitle.align(null, null, spacingBox);
10279
10483
  }
10484
+
10485
+ // Move resize button (#1115)
10486
+ if (resetZoomButton) {
10487
+ resetZoomButton.align(null, null, chart[resetZoomButton.alignTo]);
10488
+ }
10280
10489
 
10281
10490
  chart.redraw(animation);
10282
10491
 
@@ -10287,9 +10496,9 @@ Chart.prototype = {
10287
10496
  // fire endResize and set isResizing back
10288
10497
  // If animation is disabled, fire without delay
10289
10498
  if (globalAnimation === false) {
10290
- chart.fireEndResize();
10499
+ fireEndResize();
10291
10500
  } else { // else set a timeout with the animation duration
10292
- setTimeout(chart.fireEndResize, (globalAnimation && globalAnimation.duration) || 500);
10501
+ setTimeout(fireEndResize, (globalAnimation && globalAnimation.duration) || 500);
10293
10502
  }
10294
10503
  },
10295
10504
 
@@ -10306,22 +10515,42 @@ Chart.prototype = {
10306
10515
  spacingTop = optionsChart.spacingTop,
10307
10516
  spacingRight = optionsChart.spacingRight,
10308
10517
  spacingBottom = optionsChart.spacingBottom,
10309
- spacingLeft = optionsChart.spacingLeft;
10518
+ spacingLeft = optionsChart.spacingLeft,
10519
+ plotLeft,
10520
+ plotTop,
10521
+ plotWidth,
10522
+ plotHeight,
10523
+ plotBorderWidth;
10310
10524
 
10311
- chart.plotLeft = mathRound(chart.plotLeft);
10312
- chart.plotTop = mathRound(chart.plotTop);
10313
- chart.plotWidth = mathRound(chartWidth - chart.plotLeft - chart.marginRight);
10314
- chart.plotHeight = mathRound(chartHeight - chart.plotTop - chart.marginBottom);
10525
+ chart.plotLeft = plotLeft = mathRound(chart.plotLeft);
10526
+ chart.plotTop = plotTop = mathRound(chart.plotTop);
10527
+ chart.plotWidth = plotWidth = mathRound(chartWidth - plotLeft - chart.marginRight);
10528
+ chart.plotHeight = plotHeight = mathRound(chartHeight - plotTop - chart.marginBottom);
10315
10529
 
10316
- chart.plotSizeX = inverted ? chart.plotHeight : chart.plotWidth;
10317
- chart.plotSizeY = inverted ? chart.plotWidth : chart.plotHeight;
10530
+ chart.plotSizeX = inverted ? plotHeight : plotWidth;
10531
+ chart.plotSizeY = inverted ? plotWidth : plotHeight;
10532
+
10533
+ chart.plotBorderWidth = plotBorderWidth = optionsChart.plotBorderWidth || 0;
10318
10534
 
10535
+ // Set boxes used for alignment
10319
10536
  chart.spacingBox = {
10320
10537
  x: spacingLeft,
10321
10538
  y: spacingTop,
10322
10539
  width: chartWidth - spacingLeft - spacingRight,
10323
10540
  height: chartHeight - spacingTop - spacingBottom
10324
10541
  };
10542
+ chart.plotBox = {
10543
+ x: plotLeft,
10544
+ y: plotTop,
10545
+ width: plotWidth,
10546
+ height: plotHeight
10547
+ };
10548
+ chart.clipBox = {
10549
+ x: plotBorderWidth / 2,
10550
+ y: plotBorderWidth / 2,
10551
+ width: chart.plotSizeX - plotBorderWidth,
10552
+ height: chart.plotSizeY - plotBorderWidth
10553
+ };
10325
10554
 
10326
10555
  each(chart.axes, function (axis) {
10327
10556
  axis.setAxisSize();
@@ -10364,14 +10593,16 @@ Chart.prototype = {
10364
10593
  chartBackgroundColor = optionsChart.backgroundColor,
10365
10594
  plotBackgroundColor = optionsChart.plotBackgroundColor,
10366
10595
  plotBackgroundImage = optionsChart.plotBackgroundImage,
10596
+ plotBorderWidth = optionsChart.plotBorderWidth || 0,
10367
10597
  mgn,
10368
10598
  bgAttr,
10369
- plotSize = {
10370
- x: chart.plotLeft,
10371
- y: chart.plotTop,
10372
- width: chart.plotWidth,
10373
- height: chart.plotHeight
10374
- };
10599
+ plotLeft = chart.plotLeft,
10600
+ plotTop = chart.plotTop,
10601
+ plotWidth = chart.plotWidth,
10602
+ plotHeight = chart.plotHeight,
10603
+ plotBox = chart.plotBox,
10604
+ clipRect = chart.clipRect,
10605
+ clipBox = chart.clipBox;
10375
10606
 
10376
10607
  // Chart area
10377
10608
  mgn = chartBorderWidth + (optionsChart.shadow ? 8 : 0);
@@ -10391,6 +10622,7 @@ Chart.prototype = {
10391
10622
  .attr(bgAttr)
10392
10623
  .add()
10393
10624
  .shadow(optionsChart.shadow);
10625
+
10394
10626
  } else { // resize
10395
10627
  chartBackground.animate(
10396
10628
  chartBackground.crisp(null, null, null, chartWidth - mgn, chartHeight - mgn)
@@ -10402,38 +10634,48 @@ Chart.prototype = {
10402
10634
  // Plot background
10403
10635
  if (plotBackgroundColor) {
10404
10636
  if (!plotBackground) {
10405
- chart.plotBackground = renderer.rect(chart.plotLeft, chart.plotTop, chart.plotWidth, chart.plotHeight, 0)
10637
+ chart.plotBackground = renderer.rect(plotLeft, plotTop, plotWidth, plotHeight, 0)
10406
10638
  .attr({
10407
10639
  fill: plotBackgroundColor
10408
10640
  })
10409
10641
  .add()
10410
10642
  .shadow(optionsChart.plotShadow);
10411
10643
  } else {
10412
- plotBackground.animate(plotSize);
10644
+ plotBackground.animate(plotBox);
10413
10645
  }
10414
10646
  }
10415
10647
  if (plotBackgroundImage) {
10416
10648
  if (!plotBGImage) {
10417
- chart.plotBGImage = renderer.image(plotBackgroundImage, chart.plotLeft, chart.plotTop, chart.plotWidth, chart.plotHeight)
10649
+ chart.plotBGImage = renderer.image(plotBackgroundImage, plotLeft, plotTop, plotWidth, plotHeight)
10418
10650
  .add();
10419
10651
  } else {
10420
- plotBGImage.animate(plotSize);
10652
+ plotBGImage.animate(plotBox);
10421
10653
  }
10422
10654
  }
10655
+
10656
+ // Plot clip
10657
+ if (!clipRect) {
10658
+ chart.clipRect = renderer.clipRect(clipBox);
10659
+ } else {
10660
+ clipRect.animate({
10661
+ width: clipBox.width,
10662
+ height: clipBox.height
10663
+ });
10664
+ }
10423
10665
 
10424
10666
  // Plot area border
10425
- if (optionsChart.plotBorderWidth) {
10667
+ if (plotBorderWidth) {
10426
10668
  if (!plotBorder) {
10427
- chart.plotBorder = renderer.rect(chart.plotLeft, chart.plotTop, chart.plotWidth, chart.plotHeight, 0, optionsChart.plotBorderWidth)
10669
+ chart.plotBorder = renderer.rect(plotLeft, plotTop, plotWidth, plotHeight, 0, plotBorderWidth)
10428
10670
  .attr({
10429
10671
  stroke: optionsChart.plotBorderColor,
10430
- 'stroke-width': optionsChart.plotBorderWidth,
10431
- zIndex: 4
10672
+ 'stroke-width': plotBorderWidth,
10673
+ zIndex: 1
10432
10674
  })
10433
10675
  .add();
10434
10676
  } else {
10435
10677
  plotBorder.animate(
10436
- plotBorder.crisp(null, chart.plotLeft, chart.plotTop, chart.plotWidth, chart.plotHeight)
10678
+ plotBorder.crisp(null, plotLeft, plotTop, plotWidth, plotHeight)
10437
10679
  );
10438
10680
  }
10439
10681
  }
@@ -10544,8 +10786,8 @@ Chart.prototype = {
10544
10786
 
10545
10787
  // Labels
10546
10788
  if (labels.items) {
10547
- each(labels.items, function () {
10548
- var style = extend(labels.style, this.style),
10789
+ each(labels.items, function (label) {
10790
+ var style = extend(labels.style, label.style),
10549
10791
  x = pInt(style.left) + chart.plotLeft,
10550
10792
  y = pInt(style.top) + chart.plotTop + 12;
10551
10793
 
@@ -10554,7 +10796,7 @@ Chart.prototype = {
10554
10796
  delete style.top;
10555
10797
 
10556
10798
  renderer.text(
10557
- this.html,
10799
+ label.html,
10558
10800
  x,
10559
10801
  y
10560
10802
  )
@@ -10837,10 +11079,7 @@ Point.prototype = {
10837
11079
 
10838
11080
  if (series.options.colorByPoint) {
10839
11081
  defaultColors = series.chart.options.colors;
10840
- if (!point.options) {
10841
- point.options = {};
10842
- }
10843
- point.color = point.options.color = point.color || defaultColors[counters.color++];
11082
+ point.color = point.color || defaultColors[counters.color++];
10844
11083
 
10845
11084
  // loop back to zero
10846
11085
  counters.wrapColor(defaultColors.length);
@@ -10878,6 +11117,11 @@ Point.prototype = {
10878
11117
  if (options.dataLabels) {
10879
11118
  series._hasPointLabels = true;
10880
11119
  }
11120
+
11121
+ // Same approach as above for markers
11122
+ if (options.marker) {
11123
+ series._hasPointMarkers = true;
11124
+ }
10881
11125
  } else if (typeof options[0] === 'string') { // categorized data with name in first position
10882
11126
  point.name = options[0];
10883
11127
  point.y = options[1];
@@ -10892,8 +11136,6 @@ Point.prototype = {
10892
11136
  point.x = x === UNDEFINED ? series.autoIncrement() : x;
10893
11137
  }
10894
11138
 
10895
-
10896
-
10897
11139
  },
10898
11140
 
10899
11141
  /**
@@ -11026,11 +11268,15 @@ Point.prototype = {
11026
11268
  },
11027
11269
 
11028
11270
  onMouseOut: function () {
11029
- var point = this;
11030
- point.firePointEvent('mouseOut');
11031
-
11032
- point.setState();
11033
- point.series.chart.hoverPoint = null;
11271
+ var chart = this.series.chart,
11272
+ hoverPoints = chart.hoverPoints;
11273
+
11274
+ if (!hoverPoints || inArray(this, hoverPoints) === -1) { // #887
11275
+ this.firePointEvent('mouseOut');
11276
+
11277
+ this.setState();
11278
+ chart.hoverPoint = null;
11279
+ }
11034
11280
  },
11035
11281
 
11036
11282
  /**
@@ -11294,27 +11540,28 @@ Point.prototype = {
11294
11540
  // if a graphic is not applied to each point in the normal state, create a shared
11295
11541
  // graphic for the hover state
11296
11542
  if (state && markerStateOptions) {
11297
- if (!stateMarkerGraphic) {
11298
- radius = markerStateOptions.radius;
11543
+ radius = markerStateOptions.radius;
11544
+ if (!stateMarkerGraphic) { // add
11299
11545
  series.stateMarkerGraphic = stateMarkerGraphic = chart.renderer.symbol(
11300
11546
  series.symbol,
11301
- -radius,
11302
- -radius,
11547
+ plotX - radius,
11548
+ plotY - radius,
11303
11549
  2 * radius,
11304
11550
  2 * radius
11305
11551
  )
11306
11552
  .attr(pointAttr[state])
11307
- .add(series.group);
11553
+ .add(series.markerGroup);
11554
+
11555
+ } else { // update
11556
+ stateMarkerGraphic.attr({ // #1054
11557
+ x: plotX - radius,
11558
+ y: plotY - radius
11559
+ });
11308
11560
  }
11309
-
11310
- stateMarkerGraphic.translate(
11311
- plotX,
11312
- plotY
11313
- );
11314
11561
  }
11315
11562
 
11316
11563
  if (stateMarkerGraphic) {
11317
- stateMarkerGraphic[state ? 'show' : 'hide']();
11564
+ stateMarkerGraphic[state && chart.isInsidePlot(plotX, plotY) ? 'show' : 'hide']();
11318
11565
  }
11319
11566
  }
11320
11567
 
@@ -11358,9 +11605,7 @@ Series.prototype = {
11358
11605
  init: function (chart, options) {
11359
11606
  var series = this,
11360
11607
  eventType,
11361
- events,
11362
- //pointEvent,
11363
- index = chart.series.length;
11608
+ events;
11364
11609
 
11365
11610
  series.chart = chart;
11366
11611
  series.options = options = series.setOptions(options); // merge with plotOptions
@@ -11370,8 +11615,7 @@ Series.prototype = {
11370
11615
 
11371
11616
  // set some variables
11372
11617
  extend(series, {
11373
- index: index,
11374
- name: options.name || 'Series ' + (index + 1),
11618
+ name: options.name,
11375
11619
  state: NORMAL_STATE,
11376
11620
  pointAttr: {},
11377
11621
  visible: options.visible !== false, // true by default
@@ -11409,6 +11653,15 @@ Series.prototype = {
11409
11653
 
11410
11654
  // Register it in the chart
11411
11655
  chart.series.push(series);
11656
+
11657
+ // Sort series according to index option (#248, #1123)
11658
+ stableSort(chart.series, function (a, b) {
11659
+ return (a.options.index || 0) - (b.options.index || 0);
11660
+ });
11661
+ each(chart.series, function (series, i) {
11662
+ series.index = i;
11663
+ series.name = series.name || 'Series ' + (i + 1);
11664
+ });
11412
11665
  },
11413
11666
 
11414
11667
  /**
@@ -11515,17 +11768,17 @@ Series.prototype = {
11515
11768
  * @param {Object} itemOptions
11516
11769
  */
11517
11770
  setOptions: function (itemOptions) {
11518
- var series = this,
11519
- chart = series.chart,
11771
+ var chart = this.chart,
11520
11772
  chartOptions = chart.options,
11521
11773
  plotOptions = chartOptions.plotOptions,
11774
+ typeOptions = plotOptions[this.type],
11522
11775
  data = itemOptions.data,
11523
11776
  options;
11524
11777
 
11525
11778
  itemOptions.data = null; // remove from merge to prevent looping over the data set
11526
11779
 
11527
11780
  options = merge(
11528
- plotOptions[this.type],
11781
+ typeOptions,
11529
11782
  plotOptions.series,
11530
11783
  itemOptions
11531
11784
  );
@@ -11534,7 +11787,12 @@ Series.prototype = {
11534
11787
  options.data = itemOptions.data = data;
11535
11788
 
11536
11789
  // the tooltip options are merged between global and series specific options
11537
- series.tooltipOptions = merge(chartOptions.tooltip, options.tooltip);
11790
+ this.tooltipOptions = merge(chartOptions.tooltip, options.tooltip);
11791
+
11792
+ // Delte marker object if not allowed (#1125)
11793
+ if (typeOptions.marker === null) {
11794
+ delete options.marker;
11795
+ }
11538
11796
 
11539
11797
  return options;
11540
11798
 
@@ -11641,8 +11899,8 @@ Series.prototype = {
11641
11899
  yData = series.yData,
11642
11900
  currentShift = (graph && graph.shift) || 0,
11643
11901
  dataOptions = series.options.data,
11644
- point;
11645
- //point = (new series.pointClass()).init(series, options);
11902
+ point,
11903
+ proto = series.pointClass.prototype;
11646
11904
 
11647
11905
  setAnimation(animation, chart);
11648
11906
 
@@ -11663,9 +11921,9 @@ Series.prototype = {
11663
11921
  // Get options and push the point to xData, yData and series.options. In series.generatePoints
11664
11922
  // the Point instance will be created on demand and pushed to the series.data array.
11665
11923
  point = { series: series };
11666
- series.pointClass.prototype.applyOptions.apply(point, [options]);
11924
+ proto.applyOptions.apply(point, [options]);
11667
11925
  xData.push(point.x);
11668
- yData.push(series.valueCount === 4 ? [point.open, point.high, point.low, point.close] : point.y);
11926
+ yData.push(proto.toYData ? proto.toYData.call(point) : point.y);
11669
11927
  dataOptions.push(options);
11670
11928
 
11671
11929
 
@@ -11709,7 +11967,7 @@ Series.prototype = {
11709
11967
 
11710
11968
  // reset properties
11711
11969
  series.xIncrement = null;
11712
- series.pointRange = (xAxis && xAxis.categories && 1) || options.pointRange;
11970
+ series.pointRange = xAxis && xAxis.categories ? 1 : options.pointRange;
11713
11971
 
11714
11972
  if (defined(initialColor)) { // reset colors for pie
11715
11973
  chart.counters.color = initialColor;
@@ -11721,7 +11979,8 @@ Series.prototype = {
11721
11979
  dataLength = data ? data.length : [],
11722
11980
  turboThreshold = options.turboThreshold || 1000,
11723
11981
  pt,
11724
- valueCount = series.valueCount;
11982
+ pointArrayMap = series.pointArrayMap,
11983
+ valueCount = pointArrayMap && pointArrayMap.length;
11725
11984
 
11726
11985
  // In turbo mode, only one- or twodimensional arrays of numbers are allowed. The
11727
11986
  // first value is tested, and we assume that all the rest are defined the same
@@ -11769,10 +12028,15 @@ Series.prototype = {
11769
12028
  pt = { series: series };
11770
12029
  pointProto.applyOptions.apply(pt, [data[i]]);
11771
12030
  xData[i] = pt.x;
11772
- yData[i] = pointProto.toYData ? pointProto.toYData.apply(pt) : pt.y;
12031
+ yData[i] = pointProto.toYData ? pointProto.toYData.call(pt) : pt.y;
11773
12032
  }
11774
12033
  }
11775
12034
 
12035
+ // Forgetting to cast strings to numbers is a common caveat when handling CSV or JSON
12036
+ if (isString(yData[0])) {
12037
+ error(14, true);
12038
+ }
12039
+
11776
12040
  series.data = [];
11777
12041
  series.options.data = data;
11778
12042
  series.xData = xData;
@@ -11996,15 +12260,17 @@ Series.prototype = {
11996
12260
  points = series.points,
11997
12261
  dataLength = points.length,
11998
12262
  hasModifyValue = !!series.modifyValue,
11999
- isLastSeries,
12263
+ isBottomSeries,
12000
12264
  allStackSeries = yAxis.series,
12001
- i = allStackSeries.length;
12265
+ i = allStackSeries.length,
12266
+ placeBetween = options.pointPlacement === 'between';
12267
+ //nextSeriesDown;
12002
12268
 
12003
12269
  // Is it the last visible series?
12004
12270
  while (i--) {
12005
12271
  if (allStackSeries[i].visible) {
12006
- if (i === series.index) {
12007
- isLastSeries = true;
12272
+ if (allStackSeries[i] === series) { // #809
12273
+ isBottomSeries = true;
12008
12274
  }
12009
12275
  break;
12010
12276
  }
@@ -12022,7 +12288,7 @@ Series.prototype = {
12022
12288
 
12023
12289
  // get the plotX translation
12024
12290
  //point.plotX = mathRound(xAxis.translate(xValue, 0, 0, 0, 1) * 10) / 10; // Math.round fixes #591
12025
- point.plotX = xAxis.translate(xValue, 0, 0, 0, 1); // Math.round fixes #591
12291
+ point.plotX = xAxis.translate(xValue, 0, 0, 0, 1, placeBetween); // Math.round fixes #591
12026
12292
 
12027
12293
  // calculate the bottom y value for stacked series
12028
12294
  if (stacking && series.visible && stack && stack[xValue]) {
@@ -12031,8 +12297,8 @@ Series.prototype = {
12031
12297
  pointStack.cum = yBottom = pointStack.cum - yValue; // start from top
12032
12298
  yValue = yBottom + yValue;
12033
12299
 
12034
- if (isLastSeries) {
12035
- yBottom = options.threshold;
12300
+ if (isBottomSeries) {
12301
+ yBottom = pick(options.threshold, yAxis.isLog ? null : yAxis.min); // #1200
12036
12302
  }
12037
12303
 
12038
12304
  if (stacking === 'percent') {
@@ -12041,7 +12307,7 @@ Series.prototype = {
12041
12307
  }
12042
12308
 
12043
12309
  point.percentage = pointStackTotal ? point.y * 100 / pointStackTotal : 0;
12044
- point.stackTotal = pointStackTotal;
12310
+ point.total = point.stackTotal = pointStackTotal;
12045
12311
  point.stackY = yValue;
12046
12312
  }
12047
12313
 
@@ -12059,7 +12325,7 @@ Series.prototype = {
12059
12325
  point.plotY = (typeof yValue === 'number') ?
12060
12326
  mathRound(yAxis.translate(yValue, 0, 1, 0, 1) * 10) / 10 : // Math.round fixes #591
12061
12327
  UNDEFINED;
12062
-
12328
+
12063
12329
  // set client related positions for mouse tracking
12064
12330
  point.clientX = chart.inverted ?
12065
12331
  chart.plotHeight - point.plotX :
@@ -12080,13 +12346,13 @@ Series.prototype = {
12080
12346
  */
12081
12347
  setTooltipPoints: function (renew) {
12082
12348
  var series = this,
12083
- chart = series.chart,
12084
12349
  points = [],
12085
12350
  pointsLength,
12086
- plotSize = chart.plotSizeX,
12087
12351
  low,
12088
12352
  high,
12089
12353
  xAxis = series.xAxis,
12354
+ axisLength = xAxis ? (xAxis.tooltipLen || xAxis.len) : series.chart.plotSizeX, // tooltipLen and tooltipPosName used in polar
12355
+ plotX = (xAxis && xAxis.tooltipPosName) || 'plotX',
12090
12356
  point,
12091
12357
  i,
12092
12358
  tooltipPoints = []; // a lookup array for each pixel in the x dimension
@@ -12106,8 +12372,7 @@ Series.prototype = {
12106
12372
  points = points.concat(segment);
12107
12373
  });
12108
12374
 
12109
- // loop the concatenated points and apply each point to all the closest
12110
- // pixel positions
12375
+ // Reverse the points in case the X axis is reversed
12111
12376
  if (xAxis && xAxis.reversed) {
12112
12377
  points = points.reverse();
12113
12378
  }
@@ -12116,10 +12381,12 @@ Series.prototype = {
12116
12381
  pointsLength = points.length;
12117
12382
  for (i = 0; i < pointsLength; i++) {
12118
12383
  point = points[i];
12119
- low = points[i - 1] ? points[i - 1]._high + 1 : 0;
12120
- point._high = high = points[i + 1] ?
12121
- mathMax(0, mathFloor((point.plotX + (points[i + 1] ? points[i + 1].plotX : plotSize)) / 2)) :
12122
- plotSize;
12384
+ // Set this range's low to the last range's high plus one
12385
+ low = points[i - 1] ? high + 1 : 0;
12386
+ // Now find the new high
12387
+ high = points[i + 1] ?
12388
+ mathMax(0, mathFloor((point[plotX] + (points[i + 1] ? points[i + 1][plotX] : axisLength)) / 2)) :
12389
+ axisLength;
12123
12390
 
12124
12391
  while (low >= 0 && low <= high) {
12125
12392
  tooltipPoints[low++] = point;
@@ -12163,9 +12430,9 @@ Series.prototype = {
12163
12430
  chart = series.chart,
12164
12431
  hoverSeries = chart.hoverSeries;
12165
12432
 
12166
- if (!hasTouch && chart.mouseIsDown) {
12433
+ /*if (!hasTouch && chart.mouseIsDown) {
12167
12434
  return;
12168
- }
12435
+ }*/
12169
12436
 
12170
12437
  // set normal state to previous series
12171
12438
  if (hoverSeries && hoverSeries !== series) {
@@ -12221,29 +12488,86 @@ Series.prototype = {
12221
12488
  animate: function (init) {
12222
12489
  var series = this,
12223
12490
  chart = series.chart,
12224
- clipRect = series.clipRect,
12225
- animation = series.options.animation;
12491
+ renderer = chart.renderer,
12492
+ clipRect,
12493
+ markerClipRect,
12494
+ animation = series.options.animation,
12495
+ clipBox = chart.clipBox,
12496
+ inverted = chart.inverted,
12497
+ sharedClipKey;
12226
12498
 
12499
+ // Animation option is set to true
12227
12500
  if (animation && !isObject(animation)) {
12228
- animation = {};
12501
+ animation = defaultPlotOptions[series.type].animation;
12229
12502
  }
12503
+ sharedClipKey = '_sharedClip' + animation.duration + animation.easing;
12230
12504
 
12231
- if (init) { // initialize the animation
12232
- if (!clipRect.isAnimating) { // apply it only for one of the series
12233
- clipRect.attr('width', 0);
12234
- clipRect.isAnimating = true;
12505
+ // Initialize the animation. Set up the clipping rectangle.
12506
+ if (init) {
12507
+
12508
+ // If a clipping rectangle with the same properties is currently present in the chart, use that.
12509
+ clipRect = chart[sharedClipKey];
12510
+ markerClipRect = chart[sharedClipKey + 'm'];
12511
+ if (!clipRect) {
12512
+ chart[sharedClipKey] = clipRect = renderer.clipRect(
12513
+ extend(clipBox, { width: 0 })
12514
+ );
12515
+
12516
+ chart[sharedClipKey + 'm'] = markerClipRect = renderer.clipRect(
12517
+ -99, // include the width of the first marker
12518
+ inverted ? -chart.plotLeft : -chart.plotTop,
12519
+ 99,
12520
+ inverted ? chart.chartWidth : chart.chartHeight
12521
+ );
12235
12522
  }
12523
+ series.group.clip(clipRect);
12524
+ series.markerGroup.clip(markerClipRect);
12525
+ series.sharedClipKey = sharedClipKey;
12236
12526
 
12237
- } else { // run the animation
12238
- clipRect.animate({
12239
- width: chart.plotSizeX
12240
- }, animation);
12527
+ // Run the animation
12528
+ } else {
12529
+ clipRect = chart[sharedClipKey];
12530
+ if (clipRect) {
12531
+ clipRect.animate({
12532
+ width: chart.plotSizeX
12533
+ }, animation);
12534
+ chart[sharedClipKey + 'm'].animate({
12535
+ width: chart.plotSizeX + 99
12536
+ }, animation);
12537
+ }
12241
12538
 
12242
- // delete this function to allow it only once
12243
- this.animate = null;
12539
+ // Delete this function to allow it only once
12540
+ series.animate = null;
12541
+
12542
+ // Call the afterAnimate function on animation complete (but don't overwrite the animation.complete option
12543
+ // which should be available to the user).
12544
+ series.animationTimeout = setTimeout(function () {
12545
+ series.afterAnimate();
12546
+ }, animation.duration);
12244
12547
  }
12245
12548
  },
12246
-
12549
+
12550
+ /**
12551
+ * This runs after animation to land on the final plot clipping
12552
+ */
12553
+ afterAnimate: function () {
12554
+ var chart = this.chart,
12555
+ sharedClipKey = this.sharedClipKey,
12556
+ group = this.group;
12557
+
12558
+ if (group && this.options.clip !== false) {
12559
+ group.clip(chart.clipRect);
12560
+ this.markerGroup.clip(); // no clip
12561
+ }
12562
+
12563
+ // Remove the shared clipping rectancgle when all series are shown
12564
+ setTimeout(function () {
12565
+ if (sharedClipKey && chart[sharedClipKey]) {
12566
+ chart[sharedClipKey] = chart[sharedClipKey].destroy();
12567
+ chart[sharedClipKey + 'm'] = chart[sharedClipKey + 'm'].destroy();
12568
+ }
12569
+ }, 100);
12570
+ },
12247
12571
 
12248
12572
  /**
12249
12573
  * Draw the markers
@@ -12260,35 +12584,49 @@ Series.prototype = {
12260
12584
  radius,
12261
12585
  symbol,
12262
12586
  isImage,
12263
- graphic;
12587
+ graphic,
12588
+ options = series.options,
12589
+ seriesMarkerOptions = options.marker,
12590
+ pointMarkerOptions,
12591
+ enabled,
12592
+ isInside,
12593
+ markerGroup = series.markerGroup;
12264
12594
 
12265
- if (series.options.marker.enabled) {
12595
+ if (seriesMarkerOptions.enabled || series._hasPointMarkers) {
12596
+
12266
12597
  i = points.length;
12267
12598
  while (i--) {
12268
12599
  point = points[i];
12269
12600
  plotX = point.plotX;
12270
12601
  plotY = point.plotY;
12271
12602
  graphic = point.graphic;
12272
-
12603
+ pointMarkerOptions = point.marker || {};
12604
+ enabled = (seriesMarkerOptions.enabled && pointMarkerOptions.enabled === UNDEFINED) || pointMarkerOptions.enabled;
12605
+ isInside = chart.isInsidePlot(plotX, plotY, chart.inverted);
12606
+
12273
12607
  // only draw the point if y is defined
12274
- if (plotY !== UNDEFINED && !isNaN(plotY)) {
12608
+ if (enabled && plotY !== UNDEFINED && !isNaN(plotY)) {
12275
12609
 
12276
12610
  // shortcuts
12277
12611
  pointAttr = point.pointAttr[point.selected ? SELECT_STATE : NORMAL_STATE];
12278
12612
  radius = pointAttr.r;
12279
- symbol = pick(point.marker && point.marker.symbol, series.symbol);
12613
+ symbol = pick(pointMarkerOptions.symbol, series.symbol);
12280
12614
  isImage = symbol.indexOf('url') === 0;
12281
12615
 
12282
12616
  if (graphic) { // update
12283
- graphic.animate(extend({
12284
- x: plotX - radius,
12285
- y: plotY - radius
12286
- }, graphic.symbolName ? { // don't apply to image symbols #507
12287
- width: 2 * radius,
12288
- height: 2 * radius
12289
- } : {}));
12290
- } else if (radius > 0 || isImage) {
12291
- point.graphic = chart.renderer.symbol(
12617
+ graphic
12618
+ .attr({ // Since the marker group isn't clipped, each individual marker must be toggled
12619
+ visibility: isInside ? (hasSVG ? 'inherit' : VISIBLE) : HIDDEN
12620
+ })
12621
+ .animate(extend({
12622
+ x: plotX - radius,
12623
+ y: plotY - radius
12624
+ }, graphic.symbolName ? { // don't apply to image symbols #507
12625
+ width: 2 * radius,
12626
+ height: 2 * radius
12627
+ } : {}));
12628
+ } else if (isInside && (radius > 0 || isImage)) {
12629
+ point.graphic = graphic = chart.renderer.symbol(
12292
12630
  symbol,
12293
12631
  plotX - radius,
12294
12632
  plotY - radius,
@@ -12296,7 +12634,7 @@ Series.prototype = {
12296
12634
  2 * radius
12297
12635
  )
12298
12636
  .attr(pointAttr)
12299
- .add(series.group);
12637
+ .add(markerGroup);
12300
12638
  }
12301
12639
  }
12302
12640
  }
@@ -12394,8 +12732,8 @@ Series.prototype = {
12394
12732
  if (normalOptions && normalOptions.enabled === false) {
12395
12733
  normalOptions.radius = 0;
12396
12734
  }
12397
- hasPointSpecificOptions = false;
12398
-
12735
+ hasPointSpecificOptions = series.options.colorByPoint; // #868
12736
+
12399
12737
  // check if the point has specific visual options
12400
12738
  if (point.options) {
12401
12739
  for (key in pointAttrToOptions) {
@@ -12410,22 +12748,25 @@ Series.prototype = {
12410
12748
  // a specific marker config object is defined for the individual point:
12411
12749
  // create it's own attribute collection
12412
12750
  if (hasPointSpecificOptions) {
12413
-
12751
+ normalOptions = normalOptions || {};
12414
12752
  pointAttr = [];
12415
12753
  stateOptions = normalOptions.states || {}; // reassign for individual point
12416
12754
  pointStateOptionsHover = stateOptions[HOVER_STATE] = stateOptions[HOVER_STATE] || {};
12417
12755
 
12418
- // if no hover color is given, brighten the normal color
12756
+ // Handle colors for column and pies
12419
12757
  if (!series.options.marker) { // column, bar, point
12758
+ // if no hover color is given, brighten the normal color
12420
12759
  pointStateOptionsHover.color =
12421
- Color(pointStateOptionsHover.color || point.options.color)
12760
+ Color(pointStateOptionsHover.color || point.color)
12422
12761
  .brighten(pointStateOptionsHover.brightness ||
12423
12762
  stateOptionsHover.brightness).get();
12424
12763
 
12425
12764
  }
12426
12765
 
12427
12766
  // normal point state inherits series wide normal state
12428
- pointAttr[NORMAL_STATE] = series.convertAttribs(normalOptions, seriesPointAttr[NORMAL_STATE]);
12767
+ pointAttr[NORMAL_STATE] = series.convertAttribs(extend({
12768
+ color: point.color // #868
12769
+ }, normalOptions), seriesPointAttr[NORMAL_STATE]);
12429
12770
 
12430
12771
  // inherit from point normal and series hover
12431
12772
  pointAttr[HOVER_STATE] = series.convertAttribs(
@@ -12461,7 +12802,6 @@ Series.prototype = {
12461
12802
  destroy: function () {
12462
12803
  var series = this,
12463
12804
  chart = series.chart,
12464
- seriesClipRect = series.clipRect,
12465
12805
  issue134 = /AppleWebKit\/533/.test(userAgent),
12466
12806
  destroy,
12467
12807
  i,
@@ -12500,14 +12840,11 @@ Series.prototype = {
12500
12840
  }
12501
12841
  series.points = null;
12502
12842
 
12503
- // If this series clipRect is not the global one (which is removed on chart.destroy) we
12504
- // destroy it here.
12505
- if (seriesClipRect && seriesClipRect !== chart.clipRect) {
12506
- series.clipRect = seriesClipRect.destroy();
12507
- }
12843
+ // Clear the animation timeout if we are destroying the series during initial animation
12844
+ clearTimeout(series.animationTimeout);
12508
12845
 
12509
12846
  // destroy all SVGElements associated to the series
12510
- each(['area', 'graph', 'dataLabelsGroup', 'group', 'tracker', 'trackerGroup'], function (prop) {
12847
+ each(['area', 'graph', 'dataLabelsGroup', 'group', 'markerGroup', 'tracker', 'trackerGroup'], function (prop) {
12511
12848
  if (series[prop]) {
12512
12849
 
12513
12850
  // issue 134 workaround
@@ -12547,12 +12884,8 @@ Series.prototype = {
12547
12884
  pointOptions,
12548
12885
  generalOptions,
12549
12886
  str,
12550
- dataLabelsGroup = series.dataLabelsGroup,
12887
+ dataLabelsGroup,
12551
12888
  chart = series.chart,
12552
- xAxis = series.xAxis,
12553
- groupLeft = xAxis ? xAxis.left : chart.plotLeft,
12554
- yAxis = series.yAxis,
12555
- groupTop = yAxis ? yAxis.top : chart.plotTop,
12556
12889
  renderer = chart.renderer,
12557
12890
  inverted = chart.inverted,
12558
12891
  seriesType = series.type,
@@ -12562,9 +12895,7 @@ Series.prototype = {
12562
12895
  yIsNull = options.y === null,
12563
12896
  fontMetrics = renderer.fontMetrics(options.style.fontSize), // height and baseline
12564
12897
  fontLineHeight = fontMetrics.h,
12565
- fontBaseline = fontMetrics.b,
12566
- dataLabel,
12567
- enabled;
12898
+ fontBaseline = fontMetrics.b;
12568
12899
 
12569
12900
  if (isBarLike) {
12570
12901
  var defaultYs = {
@@ -12597,24 +12928,22 @@ Series.prototype = {
12597
12928
 
12598
12929
 
12599
12930
  // create a separate group for the data labels to avoid rotation
12600
- if (!dataLabelsGroup) {
12601
- dataLabelsGroup = series.dataLabelsGroup =
12602
- renderer.g('data-labels')
12603
- .attr({
12604
- visibility: series.visible ? VISIBLE : HIDDEN,
12605
- zIndex: 6
12606
- })
12607
- .translate(groupLeft, groupTop)
12608
- .add();
12609
- } else {
12610
- dataLabelsGroup.translate(groupLeft, groupTop);
12611
- }
12931
+ dataLabelsGroup = series.plotGroup(
12932
+ 'dataLabelsGroup',
12933
+ 'data-labels',
12934
+ series.visible ? VISIBLE : HIDDEN,
12935
+ 6
12936
+ );
12612
12937
 
12613
12938
  // make the labels for each point
12614
12939
  generalOptions = options;
12615
12940
  each(points, function (point) {
12616
12941
 
12617
- dataLabel = point.dataLabel;
12942
+ var plotX,
12943
+ plotY,
12944
+ individualYDelta,
12945
+ enabled,
12946
+ dataLabel = point.dataLabel;
12618
12947
 
12619
12948
  // Merge in individual options from point
12620
12949
  options = generalOptions; // reset changes from previous points
@@ -12626,24 +12955,30 @@ Series.prototype = {
12626
12955
 
12627
12956
  // Get the positions
12628
12957
  if (enabled) {
12629
- var plotX = (point.barX && point.barX + point.barW / 2) || pick(point.plotX, -999),
12630
- plotY = pick(point.plotY, -999),
12958
+ plotX = (point.barX && point.barX + point.barW / 2) || pick(point.plotX, -999);
12959
+ plotY = pick(point.plotY, -999);
12631
12960
 
12632
- // if options.y is null, which happens by default on column charts, set the position
12633
- // above or below the column depending on the threshold
12634
- individualYDelta = options.y === null ?
12635
- (point.y >= seriesOptions.threshold ?
12636
- -fontLineHeight + fontBaseline : // below the threshold
12637
- fontBaseline) : // above the threshold
12638
- options.y;
12961
+ // if options.y is null, which happens by default on column charts, set the position
12962
+ // above or below the column depending on the threshold
12963
+ individualYDelta = options.y === null ?
12964
+ (point.y >= seriesOptions.threshold ?
12965
+ -fontLineHeight + fontBaseline : // below the threshold
12966
+ fontBaseline) : // above the threshold
12967
+ options.y;
12639
12968
 
12640
12969
  x = (inverted ? chart.plotWidth - plotY : plotX) + options.x;
12641
12970
  y = mathRound((inverted ? chart.plotHeight - plotX : plotY) + individualYDelta);
12642
12971
 
12643
12972
  }
12644
12973
 
12974
+ // Check if the individual label must be disabled due to either falling
12975
+ // ouside the plot area, or the enabled option being switched off
12976
+ if (series.isCartesian && !chart.isInsidePlot(x - options.x, y)) {
12977
+ enabled = false;
12978
+ }
12979
+
12645
12980
  // If the point is outside the plot area, destroy it. #678, #820
12646
- if (dataLabel && series.isCartesian && (!chart.isInsidePlot(x, y) || !enabled)) {
12981
+ if (dataLabel && !enabled) {
12647
12982
  point.dataLabel = dataLabel.destroy();
12648
12983
 
12649
12984
  // Individual labels are disabled if the are explicitly disabled
@@ -12776,24 +13111,15 @@ Series.prototype = {
12776
13111
  },
12777
13112
 
12778
13113
  /**
12779
- * Draw the actual graph
13114
+ * Get the graph path
12780
13115
  */
12781
- drawGraph: function () {
13116
+ getGraphPath: function () {
12782
13117
  var series = this,
12783
- options = series.options,
12784
- chart = series.chart,
12785
- graph = series.graph,
12786
13118
  graphPath = [],
12787
- group = series.group,
12788
- color = options.lineColor || series.color,
12789
- lineWidth = options.lineWidth,
12790
- dashStyle = options.dashStyle,
12791
13119
  segmentPath,
12792
- renderer = chart.renderer,
12793
- singlePoints = [], // used in drawTracker
12794
- attribs;
13120
+ singlePoints = []; // used in drawTracker
12795
13121
 
12796
- // divide into segments and build graph and area paths
13122
+ // Divide into segments and build graph and area paths
12797
13123
  each(series.segments, function (segment) {
12798
13124
 
12799
13125
  segmentPath = series.getSegmentPath(segment);
@@ -12806,9 +13132,27 @@ Series.prototype = {
12806
13132
  }
12807
13133
  });
12808
13134
 
12809
- // used in drawTracker:
12810
- series.graphPath = graphPath;
13135
+ // Record it for use in drawGraph and drawTracker, and return graphPath
12811
13136
  series.singlePoints = singlePoints;
13137
+ series.graphPath = graphPath;
13138
+
13139
+ return graphPath;
13140
+
13141
+ },
13142
+
13143
+ /**
13144
+ * Draw the actual graph
13145
+ */
13146
+ drawGraph: function () {
13147
+ var options = this.options,
13148
+ graph = this.graph,
13149
+ group = this.group,
13150
+ color = options.lineColor || this.color,
13151
+ lineWidth = options.lineWidth,
13152
+ dashStyle = options.dashStyle,
13153
+ attribs,
13154
+ graphPath = this.getGraphPath();
13155
+
12812
13156
 
12813
13157
  // draw the graph
12814
13158
  if (graph) {
@@ -12819,13 +13163,14 @@ Series.prototype = {
12819
13163
  if (lineWidth) {
12820
13164
  attribs = {
12821
13165
  stroke: color,
12822
- 'stroke-width': lineWidth
13166
+ 'stroke-width': lineWidth,
13167
+ zIndex: 1 // #1069
12823
13168
  };
12824
13169
  if (dashStyle) {
12825
13170
  attribs.dashstyle = dashStyle;
12826
13171
  }
12827
13172
 
12828
- series.graph = renderer.path(graphPath)
13173
+ this.graph = this.chart.renderer.path(graphPath)
12829
13174
  .attr(attribs).add(group).shadow(options.shadow);
12830
13175
  }
12831
13176
  }
@@ -12836,8 +13181,6 @@ Series.prototype = {
12836
13181
  */
12837
13182
  invertGroups: function () {
12838
13183
  var series = this,
12839
- group = series.group,
12840
- trackerGroup = series.trackerGroup,
12841
13184
  chart = series.chart;
12842
13185
 
12843
13186
  // A fixed size is needed for inversion to work
@@ -12847,13 +13190,11 @@ Series.prototype = {
12847
13190
  height: series.xAxis.len
12848
13191
  };
12849
13192
 
12850
- // Set the series.group size
12851
- group.attr(size).invert();
12852
-
12853
- // Set the tracker group size
12854
- if (trackerGroup) {
12855
- trackerGroup.attr(size).invert();
12856
- }
13193
+ each(['group', 'trackerGroup', 'markerGroup'], function (groupName) {
13194
+ if (series[groupName]) {
13195
+ series[groupName].attr(size).invert();
13196
+ }
13197
+ });
12857
13198
  }
12858
13199
 
12859
13200
  addEvent(chart, 'resize', setInvert); // do it on resize
@@ -12869,24 +13210,34 @@ Series.prototype = {
12869
13210
  },
12870
13211
 
12871
13212
  /**
12872
- * Create the series group
13213
+ * General abstraction for creating plot groups like series.group, series.trackerGroup, series.dataLabelsGroup and
13214
+ * series.markerGroup. On subsequent calls, the group will only be adjusted to the updated plot size.
12873
13215
  */
12874
- createGroup: function () {
13216
+ plotGroup: function (prop, name, visibility, zIndex, parent) {
13217
+ var group = this[prop],
13218
+ chart = this.chart,
13219
+ xAxis = this.xAxis,
13220
+ yAxis = this.yAxis;
12875
13221
 
12876
- var chart = this.chart,
12877
- group = this.group = chart.renderer.g('series');
12878
-
12879
- group.attr({
12880
- visibility: this.visible ? VISIBLE : HIDDEN,
12881
- zIndex: this.options.zIndex
12882
- })
12883
- .translate(this.xAxis.left, this.yAxis.top)
12884
- .add(chart.seriesGroup);
13222
+ // Generate it on first call
13223
+ if (!group) {
13224
+ this[prop] = group = chart.renderer.g(name)
13225
+ .attr({
13226
+ visibility: visibility,
13227
+ zIndex: zIndex || 0.1 // IE8 needs this
13228
+ })
13229
+ .add(parent);
13230
+ }
13231
+ // Place it on first and subsequent (redraw) calls
13232
+ group.translate(
13233
+ xAxis ? xAxis.left : chart.plotLeft,
13234
+ yAxis ? yAxis.top : chart.plotTop
13235
+ );
13236
+
13237
+ return group;
12885
13238
 
12886
- // Only run this once
12887
- this.createGroup = noop;
12888
13239
  },
12889
-
13240
+
12890
13241
  /**
12891
13242
  * Render the graph and markers
12892
13243
  */
@@ -12895,34 +13246,29 @@ Series.prototype = {
12895
13246
  chart = series.chart,
12896
13247
  group,
12897
13248
  options = series.options,
12898
- doClip = options.clip !== false,
12899
13249
  animation = options.animation,
12900
- doAnimation = animation && series.animate,
12901
- duration = doAnimation ? (animation && animation.duration) || 500 : 0,
12902
- clipRect = series.clipRect,
12903
- renderer = chart.renderer;
12904
-
12905
-
12906
- // Add plot area clipping rectangle. If this is before chart.hasRendered,
12907
- // create one shared clipRect.
12908
-
12909
- // Todo: since creating the clip property, the clipRect is created but
12910
- // never used when clip is false. A better way would be that the animation
12911
- // would run, then the clipRect destroyed.
12912
- if (!clipRect) {
12913
- clipRect = series.clipRect = !chart.hasRendered && chart.clipRect ?
12914
- chart.clipRect :
12915
- renderer.clipRect(0, 0, chart.plotSizeX, chart.plotSizeY + 1);
12916
- if (!chart.clipRect) {
12917
- chart.clipRect = clipRect;
12918
- }
12919
- }
13250
+ doAnimation = animation && !!series.animate,
13251
+ visibility = series.visible ? VISIBLE : HIDDEN,
13252
+ zIndex = options.zIndex,
13253
+ hasRendered = series.hasRendered,
13254
+ chartSeriesGroup = chart.seriesGroup;
12920
13255
 
12921
-
12922
13256
  // the group
12923
- series.createGroup();
12924
- group = series.group;
13257
+ group = series.plotGroup(
13258
+ 'group',
13259
+ 'series',
13260
+ visibility,
13261
+ zIndex,
13262
+ chartSeriesGroup
13263
+ );
12925
13264
 
13265
+ series.markerGroup = series.plotGroup(
13266
+ 'markerGroup',
13267
+ 'markers',
13268
+ visibility,
13269
+ zIndex,
13270
+ chartSeriesGroup
13271
+ );
12926
13272
 
12927
13273
  series.drawDataLabels();
12928
13274
 
@@ -12934,6 +13280,9 @@ Series.prototype = {
12934
13280
  // cache attributes for shapes
12935
13281
  series.getAttribs();
12936
13282
 
13283
+ // SVGRenderer needs to know this before drawing elements (#1089)
13284
+ group.inverted = chart.inverted;
13285
+
12937
13286
  // draw the graph if any
12938
13287
  if (series.drawGraph) {
12939
13288
  series.drawGraph();
@@ -12952,37 +13301,26 @@ Series.prototype = {
12952
13301
  series.invertGroups();
12953
13302
  }
12954
13303
 
12955
- // Do the initial clipping. This must be done after inverting for VML.
12956
- if (doClip && !series.hasRendered) {
12957
- group.clip(clipRect);
12958
- if (series.trackerGroup) {
12959
- series.trackerGroup.clip(chart.clipRect);
13304
+ // Initial clipping, must be defined after inverting groups for VML
13305
+ if (options.clip !== false && !series.sharedClipKey && !hasRendered) {
13306
+ group.clip(chart.clipRect);
13307
+ if (this.trackerGroup) {
13308
+ this.trackerGroup.clip(chart.clipRect);
12960
13309
  }
12961
13310
  }
12962
-
12963
13311
 
12964
- // run the animation
13312
+ // Run the animation
12965
13313
  if (doAnimation) {
12966
13314
  series.animate();
13315
+ } else if (!hasRendered) {
13316
+ series.afterAnimate();
12967
13317
  }
12968
13318
 
12969
- // finish the individual clipRect
12970
- setTimeout(function () {
12971
- clipRect.isAnimating = false;
12972
- group = series.group; // can be destroyed during the timeout
12973
- if (group && clipRect !== chart.clipRect && clipRect.renderer) {
12974
- if (doClip) {
12975
- group.clip((series.clipRect = chart.clipRect));
12976
- }
12977
- clipRect.destroy();
12978
- }
12979
- }, duration);
12980
-
12981
13319
  series.isDirty = series.isDirtyData = false; // means data is in accordance with what you see
12982
13320
  // (See #322) series.isDirty = series.isDirtyData = false; // means data is in accordance with what you see
12983
13321
  series.hasRendered = true;
12984
13322
  },
12985
-
13323
+
12986
13324
  /**
12987
13325
  * Redraw the series after an update in the axes.
12988
13326
  */
@@ -13060,6 +13398,7 @@ Series.prototype = {
13060
13398
  seriesGroup = series.group,
13061
13399
  seriesTracker = series.tracker,
13062
13400
  dataLabelsGroup = series.dataLabelsGroup,
13401
+ markerGroup = series.markerGroup,
13063
13402
  showOrHide,
13064
13403
  i,
13065
13404
  points = series.points,
@@ -13075,6 +13414,9 @@ Series.prototype = {
13075
13414
  if (seriesGroup) { // pies don't have one
13076
13415
  seriesGroup[showOrHide]();
13077
13416
  }
13417
+ if (markerGroup) {
13418
+ markerGroup[showOrHide]();
13419
+ }
13078
13420
 
13079
13421
  // show or hide trackers
13080
13422
  if (seriesTracker) {
@@ -13153,33 +13495,6 @@ Series.prototype = {
13153
13495
  fireEvent(series, selected ? 'select' : 'unselect');
13154
13496
  },
13155
13497
 
13156
- /**
13157
- * Create a group that holds the tracking object or objects. This allows for
13158
- * individual clipping and placement of each series tracker.
13159
- */
13160
- drawTrackerGroup: function () {
13161
- var trackerGroup = this.trackerGroup,
13162
- chart = this.chart;
13163
-
13164
- if (this.isCartesian) {
13165
-
13166
- // Generate it on first call
13167
- if (!trackerGroup) {
13168
- this.trackerGroup = trackerGroup = chart.renderer.g()
13169
- .attr({
13170
- zIndex: this.options.zIndex || 1
13171
- })
13172
- .add(chart.trackerGroup);
13173
-
13174
- }
13175
- // Place it on first and subsequent (redraw) calls
13176
- trackerGroup.translate(this.xAxis.left, this.yAxis.top);
13177
-
13178
- }
13179
-
13180
- return trackerGroup;
13181
- },
13182
-
13183
13498
  /**
13184
13499
  * Draw the tracker object that sits above all data labels and markers to
13185
13500
  * track mouse events on the graph or points. For the line type charts
@@ -13199,7 +13514,7 @@ Series.prototype = {
13199
13514
  cursor = options.cursor,
13200
13515
  css = cursor && { cursor: cursor },
13201
13516
  singlePoints = series.singlePoints,
13202
- trackerGroup = series.drawTrackerGroup(),
13517
+ trackerGroup = this.isCartesian && this.plotGroup('trackerGroup', null, VISIBLE, options.zIndex || 1, chart.trackerGroup),
13203
13518
  singlePoint,
13204
13519
  i;
13205
13520
 
@@ -13293,13 +13608,12 @@ var AreaSeries = extendClass(Series, {
13293
13608
  areaSegmentPath = [].concat(segmentPath), // work on a copy for the area path
13294
13609
  i,
13295
13610
  options = this.options,
13296
- segLength = segmentPath.length,
13297
- translatedThreshold = this.yAxis.getThreshold(options.threshold);
13611
+ segLength = segmentPath.length;
13298
13612
 
13299
13613
  if (segLength === 3) { // for animation from 1 to two points
13300
13614
  areaSegmentPath.push(L, segmentPath[1], segmentPath[2]);
13301
13615
  }
13302
- if (options.stacking && this.type !== 'areaspline') {
13616
+ if (options.stacking && !this.closedStacks) {
13303
13617
 
13304
13618
  // Follow stack back. Todo: implement areaspline. A general solution could be to
13305
13619
  // reverse the entire graphPath of the previous series, though may be hard with
@@ -13315,20 +13629,29 @@ var AreaSeries = extendClass(Series, {
13315
13629
  }
13316
13630
 
13317
13631
  } else { // follow zero line back
13318
- areaSegmentPath.push(
13319
- L,
13320
- segment[segment.length - 1].plotX,
13321
- translatedThreshold,
13322
- L,
13323
- segment[0].plotX,
13324
- translatedThreshold
13325
- );
13632
+ this.closeSegment(areaSegmentPath, segment);
13326
13633
  }
13327
13634
  this.areaPath = this.areaPath.concat(areaSegmentPath);
13328
13635
 
13329
13636
  return segmentPath;
13330
13637
  },
13331
13638
 
13639
+ /**
13640
+ * Extendable method to close the segment path of an area. This is overridden in polar
13641
+ * charts.
13642
+ */
13643
+ closeSegment: function (path, segment) {
13644
+ var translatedThreshold = this.yAxis.getThreshold(this.options.threshold);
13645
+ path.push(
13646
+ L,
13647
+ segment[segment.length - 1].plotX,
13648
+ translatedThreshold,
13649
+ L,
13650
+ segment[0].plotX,
13651
+ translatedThreshold
13652
+ );
13653
+ },
13654
+
13332
13655
  /**
13333
13656
  * Draw the graph and the underlying area. This method calls the Series base
13334
13657
  * function and adds the area. The areaPath is calculated in the getSegmentPath
@@ -13357,7 +13680,8 @@ var AreaSeries = extendClass(Series, {
13357
13680
  fill: pick(
13358
13681
  options.fillColor,
13359
13682
  Color(this.color).setOpacity(options.fillOpacity || 0.75).get()
13360
- )
13683
+ ),
13684
+ zIndex: 0 // #1069
13361
13685
  }).add(this.group);
13362
13686
  }
13363
13687
  },
@@ -13395,7 +13719,7 @@ var SplineSeries = extendClass(Series, {
13395
13719
  type: 'spline',
13396
13720
 
13397
13721
  /**
13398
- * Draw the actual graph
13722
+ * Get the spline segment from a given point's previous neighbour to the given point
13399
13723
  */
13400
13724
  getPointSpline: function (segment, point, i) {
13401
13725
  var smoothing = 1.5, // 1 means control points midway between points, 2 means 1/3 from the point, 3 is 1/4 etc
@@ -13411,7 +13735,8 @@ var SplineSeries = extendClass(Series, {
13411
13735
  ret;
13412
13736
 
13413
13737
  // find control points
13414
- if (i && i < segment.length - 1) {
13738
+ if (lastPoint && nextPoint) {
13739
+
13415
13740
  var lastX = lastPoint.plotX,
13416
13741
  lastY = lastPoint.plotY,
13417
13742
  nextX = nextPoint.plotX,
@@ -13452,6 +13777,40 @@ var SplineSeries = extendClass(Series, {
13452
13777
  point.rightContY = rightContY;
13453
13778
 
13454
13779
  }
13780
+
13781
+ // Visualize control points for debugging
13782
+ /*
13783
+ if (leftContX) {
13784
+ this.chart.renderer.circle(leftContX + this.chart.plotLeft, leftContY + this.chart.plotTop, 2)
13785
+ .attr({
13786
+ stroke: 'red',
13787
+ 'stroke-width': 1,
13788
+ fill: 'none'
13789
+ })
13790
+ .add();
13791
+ this.chart.renderer.path(['M', leftContX + this.chart.plotLeft, leftContY + this.chart.plotTop,
13792
+ 'L', plotX + this.chart.plotLeft, plotY + this.chart.plotTop])
13793
+ .attr({
13794
+ stroke: 'red',
13795
+ 'stroke-width': 1
13796
+ })
13797
+ .add();
13798
+ this.chart.renderer.circle(rightContX + this.chart.plotLeft, rightContY + this.chart.plotTop, 2)
13799
+ .attr({
13800
+ stroke: 'green',
13801
+ 'stroke-width': 1,
13802
+ fill: 'none'
13803
+ })
13804
+ .add();
13805
+ this.chart.renderer.path(['M', rightContX + this.chart.plotLeft, rightContY + this.chart.plotTop,
13806
+ 'L', plotX + this.chart.plotLeft, plotY + this.chart.plotTop])
13807
+ .attr({
13808
+ stroke: 'green',
13809
+ 'stroke-width': 1
13810
+ })
13811
+ .add();
13812
+ }
13813
+ // */
13455
13814
 
13456
13815
  // moveTo or lineTo
13457
13816
  if (!i) {
@@ -13484,9 +13843,11 @@ defaultPlotOptions.areaspline = merge(defaultPlotOptions.area);
13484
13843
  var areaProto = AreaSeries.prototype,
13485
13844
  AreaSplineSeries = extendClass(SplineSeries, {
13486
13845
  type: 'areaspline',
13846
+ closedStacks: true, // instead of following the previous graph back, follow the threshold back
13487
13847
 
13488
13848
  // Mix in methods from the area series
13489
13849
  getSegmentPath: areaProto.getSegmentPath,
13850
+ closeSegment: areaProto.closeSegment,
13490
13851
  drawGraph: areaProto.drawGraph
13491
13852
  });
13492
13853
  seriesTypes.areaspline = AreaSplineSeries;
@@ -13500,6 +13861,7 @@ defaultPlotOptions.column = merge(defaultSeriesOptions, {
13500
13861
  borderRadius: 0,
13501
13862
  //colorByPoint: undefined,
13502
13863
  groupPadding: 0.2,
13864
+ //grouping: true,
13503
13865
  marker: null, // point options are specified in the base options
13504
13866
  pointPadding: 0.1,
13505
13867
  //pointWidth: null,
@@ -13536,6 +13898,10 @@ var ColumnSeries = extendClass(Series, {
13536
13898
  fill: 'color',
13537
13899
  r: 'borderRadius'
13538
13900
  },
13901
+
13902
+ /**
13903
+ * Initialize the series
13904
+ */
13539
13905
  init: function () {
13540
13906
  Series.prototype.init.apply(this, arguments);
13541
13907
 
@@ -13574,21 +13940,27 @@ var ColumnSeries = extendClass(Series, {
13574
13940
  // Get the total number of column type series.
13575
13941
  // This is called on every series. Consider moving this logic to a
13576
13942
  // chart.orderStacks() function and call it on init, addSeries and removeSeries
13577
- each(chart.series, function (otherSeries) {
13578
- if (otherSeries.type === series.type && otherSeries.visible &&
13579
- series.options.group === otherSeries.options.group) { // used in Stock charts navigator series
13580
- if (otherSeries.options.stacking) {
13581
- stackKey = otherSeries.stackKey;
13582
- if (stackGroups[stackKey] === UNDEFINED) {
13583
- stackGroups[stackKey] = columnCount++;
13943
+ if (options.grouping === false) {
13944
+ columnCount = 1;
13945
+
13946
+ } else {
13947
+ each(chart.series, function (otherSeries) {
13948
+ var otherOptions = otherSeries.options;
13949
+ if (otherSeries.type === series.type && otherSeries.visible &&
13950
+ series.options.group === otherOptions.group) { // used in Stock charts navigator series
13951
+ if (otherOptions.stacking) {
13952
+ stackKey = otherSeries.stackKey;
13953
+ if (stackGroups[stackKey] === UNDEFINED) {
13954
+ stackGroups[stackKey] = columnCount++;
13955
+ }
13956
+ columnIndex = stackGroups[stackKey];
13957
+ } else if (otherOptions.grouping !== false) { // #1162
13958
+ columnIndex = columnCount++;
13584
13959
  }
13585
- columnIndex = stackGroups[stackKey];
13586
- } else {
13587
- columnIndex = columnCount++;
13960
+ otherSeries.columnIndex = columnIndex;
13588
13961
  }
13589
- otherSeries.columnIndex = columnIndex;
13590
- }
13591
- });
13962
+ });
13963
+ }
13592
13964
 
13593
13965
  // calculate the width and position of each column based on
13594
13966
  // the number of column series in the plot, the groupPadding
@@ -13664,8 +14036,7 @@ var ColumnSeries = extendClass(Series, {
13664
14036
 
13665
14037
  },
13666
14038
 
13667
- getSymbol: function () {
13668
- },
14039
+ getSymbol: noop,
13669
14040
 
13670
14041
  /**
13671
14042
  * Use a solid rectangle like the area series types
@@ -13676,7 +14047,7 @@ var ColumnSeries = extendClass(Series, {
13676
14047
  /**
13677
14048
  * Columns have no graph
13678
14049
  */
13679
- drawGraph: function () {},
14050
+ drawGraph: noop,
13680
14051
 
13681
14052
  /**
13682
14053
  * Draw the columns. For bars, the series.group is rotated, so the same coordinates
@@ -13725,7 +14096,7 @@ var ColumnSeries = extendClass(Series, {
13725
14096
  options = series.options,
13726
14097
  cursor = options.cursor,
13727
14098
  css = cursor && { cursor: cursor },
13728
- trackerGroup = series.drawTrackerGroup(),
14099
+ trackerGroup = series.isCartesian && series.plotGroup('trackerGroup', null, VISIBLE, options.zIndex || 1, chart.trackerGroup),
13729
14100
  rel,
13730
14101
  plotY,
13731
14102
  validPlotY;
@@ -13923,7 +14294,7 @@ var ScatterSeries = extendClass(Series, {
13923
14294
 
13924
14295
  // Add the event listeners, we need to do this only once
13925
14296
  if (!series._hasTracking) {
13926
- series.group
14297
+ series.markerGroup
13927
14298
  .attr({
13928
14299
  isTracker: true
13929
14300
  })
@@ -13942,7 +14313,6 @@ var ScatterSeries = extendClass(Series, {
13942
14313
  } else {
13943
14314
  series._hasTracking = true;
13944
14315
  }
13945
-
13946
14316
  }
13947
14317
  });
13948
14318
  seriesTypes.scatter = ScatterSeries;
@@ -14019,7 +14389,8 @@ var PiePoint = extendClass(Point, {
14019
14389
  */
14020
14390
  setVisible: function (vis) {
14021
14391
  var point = this,
14022
- chart = point.series.chart,
14392
+ series = point.series,
14393
+ chart = series.chart,
14023
14394
  tracker = point.tracker,
14024
14395
  dataLabel = point.dataLabel,
14025
14396
  connector = point.connector,
@@ -14047,6 +14418,12 @@ var PiePoint = extendClass(Point, {
14047
14418
  if (point.legendItem) {
14048
14419
  chart.legend.colorizeItem(point, vis);
14049
14420
  }
14421
+
14422
+ // Handle ignore hidden slices
14423
+ if (!series.isDirty && series.options.ignoreHiddenPoint) {
14424
+ series.isDirty = true;
14425
+ chart.redraw();
14426
+ }
14050
14427
  },
14051
14428
 
14052
14429
  /**
@@ -14200,7 +14577,8 @@ var PieSeries = {
14200
14577
  fraction,
14201
14578
  radiusX, // the x component of the radius vector for a given point
14202
14579
  radiusY,
14203
- labelDistance = options.dataLabels.distance;
14580
+ labelDistance = options.dataLabels.distance,
14581
+ ignoreHiddenPoint = options.ignoreHiddenPoint;
14204
14582
 
14205
14583
  // get positions - either an integer or a percentage string must be given
14206
14584
  series.center = positions = series.getCenter();
@@ -14217,14 +14595,16 @@ var PieSeries = {
14217
14595
 
14218
14596
  // get the total sum
14219
14597
  each(points, function (point) {
14220
- total += point.y;
14598
+ total += (ignoreHiddenPoint && !point.visible) ? 0 : point.y;
14221
14599
  });
14222
14600
 
14223
14601
  each(points, function (point) {
14224
14602
  // set start and end angle
14225
14603
  fraction = total ? point.y / total : 0;
14226
14604
  start = mathRound(cumulative * circ * precision) / precision;
14227
- cumulative += fraction;
14605
+ if (!ignoreHiddenPoint || point.visible) {
14606
+ cumulative += fraction;
14607
+ }
14228
14608
  end = mathRound(cumulative * circ * precision) / precision;
14229
14609
 
14230
14610
  // set the shape
@@ -14405,7 +14785,7 @@ var PieSeries = {
14405
14785
  j;
14406
14786
 
14407
14787
  // get out if not enabled
14408
- if (!options.enabled) {
14788
+ if (!options.enabled && !series._hasPointLabels) {
14409
14789
  return;
14410
14790
  }
14411
14791
 
@@ -14646,6 +15026,7 @@ extend(Highcharts, {
14646
15026
  Chart: Chart,
14647
15027
  Color: Color,
14648
15028
  Legend: Legend,
15029
+ MouseTracker: MouseTracker,
14649
15030
  Point: Point,
14650
15031
  Tick: Tick,
14651
15032
  Tooltip: Tooltip,
@@ -14675,7 +15056,11 @@ extend(Highcharts, {
14675
15056
  splat: splat,
14676
15057
  extendClass: extendClass,
14677
15058
  pInt: pInt,
15059
+ wrap: wrap,
15060
+ svg: hasSVG,
15061
+ canvas: useCanVG,
15062
+ vml: !hasSVG && !useCanVG,
14678
15063
  product: 'Highcharts',
14679
- version: '2.2.5'
15064
+ version: '2.3.2'
14680
15065
  });
14681
15066
  }());