highcharts-js-rails 0.1.9 → 0.1.10

Sign up to get free protection for your applications and to get access to all the features.
@@ -2,7 +2,7 @@
2
2
  // @compilation_level SIMPLE_OPTIMIZATIONS
3
3
 
4
4
  /**
5
- * @license Highcharts JS 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
  }());