Added FAQ in Documentation.html about 'How to use Zoom search feature?' (FAQ 6.32)
[phpmyadmin/ammaryasirr.git] / js / highcharts / highcharts.js
blobb3b8e9d5711ba2dda36c01fc4db7d78bff4b6622
1 // ==ClosureCompiler==
2 // @compilation_level SIMPLE_OPTIMIZATIONS
4 /**
5  * @license Highcharts JS v2.1.5 (2011-06-22)
6  * 
7  * (c) 2009-2011 Torstein Hønsi
8  * 
9  * License: www.highcharts.com/license
10  */
12 // JSLint options:
13 /*jslint forin: true */
14 /*global document, window, navigator, setInterval, clearInterval, clearTimeout, setTimeout, location, jQuery, $ */
15         
16 (function() {
17 // encapsulated variables
18 var doc = document,
19         win = window,
20         math = Math,
21         mathRound = math.round,
22         mathFloor = math.floor,
23         mathCeil = math.ceil,
24         mathMax = math.max,
25         mathMin = math.min,
26         mathAbs = math.abs,
27         mathCos = math.cos,
28         mathSin = math.sin,
29         mathPI = math.PI,
30         deg2rad = mathPI * 2 / 360,
31         
32         
33         // some variables
34         userAgent = navigator.userAgent,
35         isIE = /msie/i.test(userAgent) && !win.opera,
36         docMode8 = doc.documentMode === 8,
37         isWebKit = /AppleWebKit/.test(userAgent),
38         isFirefox = /Firefox/.test(userAgent),
39         //hasSVG = win.SVGAngle || doc.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure", "1.1"),
40         hasSVG = !!doc.createElementNS && !!doc.createElementNS("http://www.w3.org/2000/svg", "svg").createSVGRect,
41         SVG_NS = 'http://www.w3.org/2000/svg',
42         Renderer,
43         hasTouch = doc.documentElement.ontouchstart !== undefined,
44         colorCounter,
45         symbolCounter,
46         symbolSizes = { },
47         idCounter = 0,
48         timeFactor = 1, // 1 = JavaScript time, 1000 = Unix time
49         garbageBin,
50         defaultOptions,
51         dateFormat, // function
52         globalAnimation,
53         pathAnim,
54         
55         
56         // some constants for frequently used strings
57         UNDEFINED,
58         DIV = 'div',
59         ABSOLUTE = 'absolute',
60         RELATIVE = 'relative',
61         HIDDEN = 'hidden',
62         PREFIX = 'highcharts-',
63         VISIBLE = 'visible',
64         PX = 'px',
65         NONE = 'none',
66         M = 'M',
67         L = 'L',
68         /*
69          * Empirical lowest possible opacities for TRACKER_FILL
70          * IE6: 0.002
71          * IE7: 0.002
72          * IE8: 0.002
73          * IE9: 0.00000000001 (unlimited)
74          * FF: 0.00000000001 (unlimited)
75          * Chrome: 0.000001
76          * Safari: 0.000001
77          * Opera: 0.00000000001 (unlimited)
78          */
79         TRACKER_FILL = 'rgba(192,192,192,'+ (hasSVG ? 0.000001 : 0.002) +')', // invisible but clickable
80         NORMAL_STATE = '',
81         HOVER_STATE = 'hover',
82         SELECT_STATE = 'select',
83         
84         // time methods, changed based on whether or not UTC is used
85         makeTime,
86         getMinutes,
87         getHours,
88         getDay,
89         getDate,
90         getMonth,
91         getFullYear,
92         setMinutes,
93         setHours,
94         setDate,
95         setMonth,
96         setFullYear,
97         
98         // check for a custom HighchartsAdapter defined prior to this file
99         globalAdapter = win.HighchartsAdapter,
100         adapter = globalAdapter || {}, 
101         
102         // Utility functions. If the HighchartsAdapter is not defined, adapter is an empty object
103         // and all the utility functions will be null. In that case they are populated by the 
104         // default adapters below.
105         each = adapter.each,
106         grep = adapter.grep,
107         map = adapter.map,
108         merge = adapter.merge,
109         hyphenate = adapter.hyphenate,
110         addEvent = adapter.addEvent,
111         removeEvent = adapter.removeEvent,
112         fireEvent = adapter.fireEvent,
113         animate = adapter.animate,
114         stop = adapter.stop,
115         
116         // lookup over the types and the associated classes
117         seriesTypes = {},
118         hoverChart;
119         
121  * Extend an object with the members of another
122  * @param {Object} a The object to be extended
123  * @param {Object} b The object to add to the first one
124  */
125 function extend(a, b) {
126         var n;
127         if (!a) {
128                 a = {};
129         }
130         for (n in b) {
131                 a[n] = b[n];
132         }
133         return a;
137  * Shortcut for parseInt
138  * @param {Object} s
139  */
140 function pInt(s, mag) {
141         return parseInt(s, mag || 10);
145  * Check for string
146  * @param {Object} s
147  */
148 function isString(s) {
149         return typeof s === 'string';
153  * Check for object
154  * @param {Object} obj
155  */
156 function isObject(obj) {
157         return typeof obj === 'object';
161  * Check for number
162  * @param {Object} n
163  */
164 function isNumber(n) {
165         return typeof n === 'number';
168 function log2lin(num) {
169         return math.log(num) / math.LN10;
171 function lin2log(num) {
172         return math.pow(10, num);
176  * Remove last occurence of an item from an array
177  * @param {Array} arr
178  * @param {Mixed} item
179  */
180 function erase(arr, item) {
181         var i = arr.length;
182         while (i--) {
183                 if (arr[i] === item) {
184                         arr.splice(i, 1);
185                         break;
186                 }
187         }
188         //return arr;
192  * Returns true if the object is not null or undefined. Like MooTools' $.defined.
193  * @param {Object} obj
194  */
195 function defined (obj) {
196         return obj !== UNDEFINED && obj !== null;
200  * Set or get an attribute or an object of attributes. Can't use jQuery attr because
201  * it attempts to set expando properties on the SVG element, which is not allowed.
202  * 
203  * @param {Object} elem The DOM element to receive the attribute(s)
204  * @param {String|Object} prop The property or an abject of key-value pairs
205  * @param {String} value The value if a single property is set
206  */
207 function attr(elem, prop, value) {
208         var key,
209                 setAttribute = 'setAttribute',
210                 ret;
211         
212         // if the prop is a string
213         if (isString(prop)) {
214                 // set the value
215                 if (defined(value)) {
217                         elem[setAttribute](prop, value);
218                 
219                 // get the value
220                 } else if (elem && elem.getAttribute) { // elem not defined when printing pie demo...
221                         ret = elem.getAttribute(prop);
222                 }
223         
224         // else if prop is defined, it is a hash of key/value pairs
225         } else if (defined(prop) && isObject(prop)) {
226                 for (key in prop) {
227                         elem[setAttribute](key, prop[key]);
228                 }
229         }
230         return ret;
233  * Check if an element is an array, and if not, make it into an array. Like
234  * MooTools' $.splat.
235  */
236 function splat(obj) {
237         if (!obj || obj.constructor !== Array) {
238                 obj = [obj];
239         }
240         return obj; 
246  * Return the first value that is defined. Like MooTools' $.pick.
247  */
248 function pick() {
249         var args = arguments,
250                 i,
251                 arg,
252                 length = args.length;
253         for (i = 0; i < length; i++) {
254                 arg = args[i];
255                 if (typeof arg !== 'undefined' && arg !== null) {
256                         return arg;
257                 }
258         }
261  * Make a style string from a JS object
262  * @param {Object} style
263  */
264 function serializeCSS(style) {
265         var s = '', 
266                 key;
267         // serialize the declaration
268         for (key in style) {
269                 s += key +':'+ style[key] + ';';
270         }
271         return s;
272         
275  * Set CSS on a given element
276  * @param {Object} el
277  * @param {Object} styles Style object with camel case property names
278  */
279 function css (el, styles) {
280         if (isIE) {
281                 if (styles && styles.opacity !== UNDEFINED) {
282                         styles.filter = 'alpha(opacity='+ (styles.opacity * 100) +')';
283                 }
284         }
285         extend(el.style, styles);
288 /* *
289  * Get CSS value on a given element
290  * @param {Object} el DOM object
291  * @param {String} styleProp Camel cased CSS propery
292  * /
293 function getStyle (el, styleProp) {
294         var ret,
295                 CURRENT_STYLE = 'currentStyle',
296                 GET_COMPUTED_STYLE = 'getComputedStyle';
297         if (el[CURRENT_STYLE]) {
298                 ret = el[CURRENT_STYLE][styleProp];
299         } else if (win[GET_COMPUTED_STYLE]) {
300                 ret = win[GET_COMPUTED_STYLE](el, null).getPropertyValue(hyphenate(styleProp));
301         }
302         return ret;
306  * Utility function to create element with attributes and styles
307  * @param {Object} tag
308  * @param {Object} attribs
309  * @param {Object} styles
310  * @param {Object} parent
311  * @param {Object} nopad
312  */
313 function createElement (tag, attribs, styles, parent, nopad) {
314         var el = doc.createElement(tag);
315         if (attribs) {
316                 extend(el, attribs);
317         }
318         if (nopad) {
319                 css(el, {padding: 0, border: NONE, margin: 0});
320         }
321         if (styles) {
322                 css(el, styles);
323         }
324         if (parent) {
325                 parent.appendChild(el);
326         }       
327         return el;
331  * Extend a prototyped class by new members
332  * @param {Object} parent
333  * @param {Object} members
334  */
335 function extendClass(parent, members) {
336         var object = function(){};
337         object.prototype = new parent();
338         extend(object.prototype, members);
339         return object;
343  * Format a number and return a string based on input settings
344  * @param {Number} number The input number to format
345  * @param {Number} decimals The amount of decimals
346  * @param {String} decPoint The decimal point, defaults to the one given in the lang options
347  * @param {String} thousandsSep The thousands separator, defaults to the one given in the lang options
348  */
349 function numberFormat (number, decimals, decPoint, thousandsSep) {
350         var lang = defaultOptions.lang,
351                 // http://kevin.vanzonneveld.net/techblog/article/javascript_equivalent_for_phps_number_format/
352                 n = number, c = isNaN(decimals = mathAbs(decimals)) ? 2 : decimals,
353                 d = decPoint === undefined ? lang.decimalPoint : decPoint,
354                 t = thousandsSep === undefined ? lang.thousandsSep : thousandsSep, s = n < 0 ? "-" : "",
355                 i = String(pInt(n = mathAbs(+n || 0).toFixed(c))),
356                 j = i.length > 3 ? i.length % 3 : 0;
357     
358         return s + (j ? i.substr(0, j) + t : "") + i.substr(j).replace(/(\d{3})(?=\d)/g, "$1" + t) +
359                 (c ? d + mathAbs(n - i).toFixed(c).slice(2) : "");
363  * Based on http://www.php.net/manual/en/function.strftime.php 
364  * @param {String} format
365  * @param {Number} timestamp
366  * @param {Boolean} capitalize
367  */
368 dateFormat = function (format, timestamp, capitalize) {
369         function pad (number) {
370                 return number.toString().replace(/^([0-9])$/, '0$1');
371         }
372         
373         if (!defined(timestamp) || isNaN(timestamp)) {
374                 return 'Invalid date';
375         }
376         format = pick(format, '%Y-%m-%d %H:%M:%S');
377         
378         var date = new Date(timestamp * timeFactor),
379                 key, // used in for constuct below
380                 // get the basic time values
381                 hours = date[getHours](),
382                 day = date[getDay](),
383                 dayOfMonth = date[getDate](),
384                 month = date[getMonth](),
385                 fullYear = date[getFullYear](),
386                 lang = defaultOptions.lang,
387                 langWeekdays = lang.weekdays,
388                 langMonths = lang.months,
389                 /* // uncomment this and the 'W' format key below to enable week numbers
390                 weekNumber = function() { 
391                         var clone = new Date(date.valueOf()),
392                                 day = clone[getDay]() == 0 ? 7 : clone[getDay](),
393                                 dayNumber;
394                         clone.setDate(clone[getDate]() + 4 - day);
395                         dayNumber = mathFloor((clone.getTime() - new Date(clone[getFullYear](), 0, 1, -6)) / 86400000);
396                         return 1 + mathFloor(dayNumber / 7);
397                 },
398                 */
399                 
400                 // list all format keys
401                 replacements = {
403                         // Day
404                         'a': langWeekdays[day].substr(0, 3), // Short weekday, like 'Mon'
405                         'A': langWeekdays[day], // Long weekday, like 'Monday'
406                         'd': pad(dayOfMonth), // Two digit day of the month, 01 to 31 
407                         'e': dayOfMonth, // Day of the month, 1 through 31 
408                         
409                         // Week (none implemented)
410                         //'W': weekNumber(),
411                         
412                         // Month
413                         'b': langMonths[month].substr(0, 3), // Short month, like 'Jan'
414                         'B': langMonths[month], // Long month, like 'January'
415                         'm': pad(month + 1), // Two digit month number, 01 through 12
416                         
417                         // Year
418                         'y': fullYear.toString().substr(2, 2), // Two digits year, like 09 for 2009
419                         'Y': fullYear, // Four digits year, like 2009
420                         
421                         // Time
422                         'H': pad(hours), // Two digits hours in 24h format, 00 through 23
423                         'I': pad((hours % 12) || 12), // Two digits hours in 12h format, 00 through 11
424                         'l': (hours % 12) || 12, // Hours in 12h format, 1 through 12
425                         'M': pad(date[getMinutes]()), // Two digits minutes, 00 through 59
426                         'p': hours < 12 ? 'AM' : 'PM', // Upper case AM or PM
427                         'P': hours < 12 ? 'am' : 'pm', // Lower case AM or PM
428                         'S': pad(date.getSeconds()) // Two digits seconds, 00 through  59
429                         
430                 };
433         // do the replaces
434         for (key in replacements) {
435                 format = format.replace('%'+ key, replacements[key]);
436         }
437                 
438         // Optionally capitalize the string and return
439         return capitalize ? format.substr(0, 1).toUpperCase() + format.substr(1) : format;
443  * Loop up the node tree and add offsetWidth and offsetHeight to get the
444  * total page offset for a given element. Used by Opera and iOS on hover and
445  * all browsers on point click.
446  * 
447  * @param {Object} el
448  * 
449  */
450 function getPosition (el) {
451         var p = { left: el.offsetLeft, top: el.offsetTop };
452         el = el.offsetParent;
453         while (el) {
454                 p.left += el.offsetLeft;
455                 p.top += el.offsetTop;
456                 if (el !== doc.body && el !== doc.documentElement) {
457                         p.left -= el.scrollLeft;
458                         p.top -= el.scrollTop;
459                 }
460                 el = el.offsetParent;
461         }
462         return p;
466  * Set the global animation to either a given value, or fall back to the 
467  * given chart's animation option
468  * @param {Object} animation
469  * @param {Object} chart
470  */
471 function setAnimation(animation, chart) {
472         globalAnimation = pick(animation, chart.animation);
475 /* 
476  * Define the adapter for frameworks. If an external adapter is not defined, 
477  * Highcharts reverts to the built-in jQuery adapter.
478  */
479 if (globalAdapter && globalAdapter.init) {
480         globalAdapter.init();
482 if (!globalAdapter && win.jQuery) {
483         var jQ = jQuery;
484         
485         /**
486          * Utility for iterating over an array. Parameters are reversed compared to jQuery.
487          * @param {Array} arr
488          * @param {Function} fn
489          */
490         each = function(arr, fn) {
491                 var i = 0, 
492                         len = arr.length;
493                 for (; i < len; i++) {
494                         if (fn.call(arr[i], arr[i], i, arr) === false) {
495                                 return i;
496                         }
497                 }
498         };
499         
500         /**
501          * Filter an array
502          */
503         grep = jQ.grep;
504         
505         /**
506          * Map an array
507          * @param {Array} arr
508          * @param {Function} fn
509          */
510         map = function(arr, fn){
511                 //return jQuery.map(arr, fn);
512                 var results = [],
513                         i = 0, len = arr.length;
514                 for (; i < len; i++) {
515                         results[i] = fn.call(arr[i], arr[i], i, arr);
516                 }
517                 return results;
518                 
519         };
520         
521         /**
522          * Deep merge two objects and return a third object
523          */
524         merge = function(){
525                 var args = arguments;
526                 return jQ.extend(true, null, args[0], args[1], args[2], args[3]);
527         };
528         
529         /**
530          * Convert a camelCase string to a hyphenated string
531          * @param {String} str
532          */
533         hyphenate = function (str) {
534                 return str.replace(/([A-Z])/g, function(a, b){ return '-'+ b.toLowerCase(); });
535         };
536         
537         /**
538          * Add an event listener
539          * @param {Object} el A HTML element or custom object
540          * @param {String} event The event type
541          * @param {Function} fn The event handler
542          */
543         addEvent = function (el, event, fn){
544                 jQ(el).bind(event, fn);
545         };
546         
547         /**
548          * Remove event added with addEvent
549          * @param {Object} el The object
550          * @param {String} eventType The event type. Leave blank to remove all events.
551          * @param {Function} handler The function to remove
552          */
553         removeEvent = function(el, eventType, handler) {
554                 // workaround for jQuery issue with unbinding custom events:
555                 // http://forum.jquery.com/topic/javascript-error-when-unbinding-a-custom-event-using-jquery-1-4-2
556                 var func = doc.removeEventListener ? 'removeEventListener' : 'detachEvent';
557                 if (doc[func] && !el[func]) {
558                         el[func] = function() {};
559                 }
560                 
561                 jQ(el).unbind(eventType, handler);
562         };
563         
564         /**
565          * Fire an event on a custom object
566          * @param {Object} el
567          * @param {String} type
568          * @param {Object} eventArguments
569          * @param {Function} defaultFunction
570          */
571         fireEvent = function(el, type, eventArguments, defaultFunction) {
572                 var event = jQ.Event(type),
573                         detachedType = 'detached'+ type;
574                 extend(event, eventArguments);
575                 
576                 // Prevent jQuery from triggering the object method that is named the
577                 // same as the event. For example, if the event is 'select', jQuery
578                 // attempts calling el.select and it goes into a loop.
579                 if (el[type]) {
580                         el[detachedType] = el[type];
581                         el[type] = null;        
582                 }
583                 
584                 // trigger it
585                 jQ(el).trigger(event);
586                 
587                 // attach the method
588                 if (el[detachedType]) {
589                         el[type] = el[detachedType];
590                         el[detachedType] = null;
591                 }
592                 
593                 if (defaultFunction && !event.isDefaultPrevented()) {
594                         defaultFunction(event);
595                 }       
596         };
598         /**
599          * Animate a HTML element or SVG element wrapper
600          * @param {Object} el
601          * @param {Object} params
602          * @param {Object} options jQuery-like animation options: duration, easing, callback
603          */
604         animate = function (el, params, options) {
605                 var $el = jQ(el);
606                 if (params.d) {
607                         el.toD = params.d; // keep the array form for paths, used in jQ.fx.step.d
608                         params.d = 1; // because in jQuery, animating to an array has a different meaning
609                 }
610                 
611                 $el.stop();
612                 $el.animate(params, options);
613                 
614         };
615         /**
616          * Stop running animation
617          */
618         stop = function (el) {
619                 jQ(el).stop();
620         };
621         
622         
623         // extend jQuery
624         jQ.extend( jQ.easing, {
625                 easeOutQuad: function (x, t, b, c, d) {
626                         return -c *(t/=d)*(t-2) + b;
627                 }
628         });
629                                         
630         // extend the animate function to allow SVG animations
631         var oldStepDefault = jQuery.fx.step._default, 
632                 oldCur = jQuery.fx.prototype.cur;
633         
634         // do the step
635         jQ.fx.step._default = function(fx){
636                 var elem = fx.elem;
637                 if (elem.attr) { // is SVG element wrapper
638                         elem.attr(fx.prop, fx.now);
639                 } else {
640                         oldStepDefault.apply(this, arguments);
641                 }
642         };
643         // animate paths
644         jQ.fx.step.d = function(fx) {
645                 var elem = fx.elem;
646                         
647                 
648                 // Normally start and end should be set in state == 0, but sometimes,
649                 // for reasons unknown, this doesn't happen. Perhaps state == 0 is skipped
650                 // in these cases
651                 if (!fx.started) {
652                         var ends = pathAnim.init(elem, elem.d, elem.toD);
653                         fx.start = ends[0];
654                         fx.end = ends[1];
655                         fx.started = true;
656                 }
657                 
658                 
659                 // interpolate each value of the path
660                 elem.attr('d', pathAnim.step(fx.start, fx.end, fx.pos, elem.toD));
661         
662         };
663         // get the current value
664         jQ.fx.prototype.cur = function() {
665                 var elem = this.elem,
666                         r;
667                 if (elem.attr) { // is SVG element wrapper
668                         r = elem.attr(this.prop);
669                 } else {
670                         r = oldCur.apply(this, arguments);
671                 }
672                 return r;
673         };
678  * Add a global listener for mousemove events
679  */
680 /*addEvent(doc, 'mousemove', function(e) {
681         if (globalMouseMove) {
682                 globalMouseMove(e);
683         }
684 });*/
687  * Path interpolation algorithm used across adapters
688  */
689 pathAnim = {
690         /**
691          * Prepare start and end values so that the path can be animated one to one
692          */
693         init: function(elem, fromD, toD) {
694                 fromD = fromD || '';
695                 var shift = elem.shift,
696                         bezier = fromD.indexOf('C') > -1,
697                         numParams = bezier ? 7 : 3,
698                         endLength,
699                         slice,
700                         i,
701                         start = fromD.split(' '),
702                         end = [].concat(toD), // copy
703                         startBaseLine,
704                         endBaseLine,
705                         sixify = function(arr) { // in splines make move points have six parameters like bezier curves
706                                 i = arr.length;
707                                 while (i--) {
708                                         if (arr[i] === M) {
709                                                 arr.splice(i + 1, 0, arr[i+1], arr[i+2], arr[i+1], arr[i+2]);
710                                         }
711                                 }
712                         };
713                 
714                 if (bezier) {
715                         sixify(start);
716                         sixify(end);
717                 }
718                 
719                 // pull out the base lines before padding
720                 if (elem.isArea) { 
721                         startBaseLine = start.splice(start.length - 6, 6);
722                         endBaseLine = end.splice(end.length - 6, 6);
723                 }
724                 
725                 // if shifting points, prepend a dummy point to the end path
726                 if (shift) {
728                         end = [].concat(end).splice(0, numParams).concat(end);
729                         elem.shift = false; // reset for following animations
730                 }
731                 
732                 // copy and append last point until the length matches the end length
733                 if (start.length) {
734                         endLength = end.length;
735                         while (start.length < endLength) {              
736                                 
737                                 //bezier && sixify(start); 
738                                 slice = [].concat(start).splice(start.length - numParams, numParams);
739                                 if (bezier) { // disable first control point
740                                         slice[numParams - 6] = slice[numParams - 2];
741                                         slice[numParams - 5] = slice[numParams - 1];
742                                 }
743                                 start = start.concat(slice);
744                         }
745                 }
746                 
747                 if (startBaseLine) { // append the base lines for areas
748                         start = start.concat(startBaseLine);
749                         end = end.concat(endBaseLine);
750                 }
751                 return [start, end];
752         },
753         
754         /**
755          * Interpolate each value of the path and return the array
756          */
757         step: function(start, end, pos, complete) {
758                 var ret = [],
759                         i = start.length,
760                         startVal;
761                         
762                 if (pos === 1) { // land on the final path without adjustment points appended in the ends
763                         ret = complete;
764                         
765                 } else if (i === end.length && pos < 1) {
766                         while (i--) {
767                                 startVal = parseFloat(start[i]);
768                                 ret[i] = 
769                                         isNaN(startVal) ? // a letter instruction like M or L
770                                                 start[i] :
771                                                 pos * (parseFloat(end[i] - startVal)) + startVal;
772                                 
773                         }
774                 } else { // if animation is finished or length not matching, land on right value
775                         ret = end;
776                 }
777                 return ret;
778         }
782  * Set the time methods globally based on the useUTC option. Time method can be either 
783  * local time or UTC (default).
784  */
785 function setTimeMethods() {
786         var useUTC = defaultOptions.global.useUTC;
787         
788         makeTime = useUTC ? Date.UTC : function(year, month, date, hours, minutes, seconds) {
789                 return new Date(
790                         year, 
791                         month, 
792                         pick(date, 1), 
793                         pick(hours, 0), 
794                         pick(minutes, 0), 
795                         pick(seconds, 0)
796                 ).getTime();
797         };
798         getMinutes = useUTC ? 'getUTCMinutes' : 'getMinutes';
799         getHours = useUTC ? 'getUTCHours' : 'getHours';
800         getDay = useUTC ? 'getUTCDay' : 'getDay';
801         getDate = useUTC ? 'getUTCDate' : 'getDate';
802         getMonth = useUTC ? 'getUTCMonth' : 'getMonth';
803         getFullYear = useUTC ? 'getUTCFullYear' : 'getFullYear';
804         setMinutes = useUTC ? 'setUTCMinutes' : 'setMinutes';
805         setHours = useUTC ? 'setUTCHours' : 'setHours';
806         setDate = useUTC ? 'setUTCDate' : 'setDate';
807         setMonth = useUTC ? 'setUTCMonth' : 'setMonth';
808         setFullYear = useUTC ? 'setUTCFullYear' : 'setFullYear';
809                 
813  * Merge the default options with custom options and return the new options structure
814  * @param {Object} options The new custom options
815  */
816 function setOptions(options) {
817         defaultOptions = merge(defaultOptions, options);
818         
819         // apply UTC
820         setTimeMethods();
821         
822         return defaultOptions;
826  * Get the updated default options. Merely exposing defaultOptions for outside modules
827  * isn't enough because the setOptions method creates a new object.
828  */
829 function getOptions() {
830         return defaultOptions;
834  * Discard an element by moving it to the bin and delete
835  * @param {Object} The HTML node to discard
836  */
837 function discardElement(element) {
838         // create a garbage bin element, not part of the DOM
839         if (!garbageBin) {
840                 garbageBin = createElement(DIV);
841         }
842         
843         // move the node and empty bin
844         if (element) {
845                 garbageBin.appendChild(element);
846         }
847         garbageBin.innerHTML = '';
850 /* ****************************************************************************
851  * Handle the options                                                         *
852  *****************************************************************************/
853 var 
855 defaultLabelOptions = {
856         enabled: true,
857         // rotation: 0,
858         align: 'center',
859         x: 0,
860         y: 15,
861         /*formatter: function() {
862                 return this.value;
863         },*/
864         style: {
865                 color: '#666',
866                 fontSize: '11px',
867                 lineHeight: '14px'
868         }
871 defaultOptions = {
872         colors: ['#4572A7', '#AA4643', '#89A54E', '#80699B', '#3D96AE', 
873                 '#DB843D', '#92A8CD', '#A47D7C', '#B5CA92'],
874         symbols: ['circle', 'diamond', 'square', 'triangle', 'triangle-down'],
875         lang: {
876                 loading: 'Loading...',
877                 months: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 
878                                 'August', 'September', 'October', 'November', 'December'],
879                 weekdays: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
880                 decimalPoint: '.',
881                 resetZoom: 'Reset zoom',
882                 resetZoomTitle: 'Reset zoom level 1:1',
883                 thousandsSep: ','
884         },
885         global: {
886                 useUTC: true
887         },
888         chart: {
889                 //animation: true,
890                 //alignTicks: false,
891                 //reflow: true,
892                 //className: null,
893                 //events: { load, selection },
894                 //margin: [null],
895                 //marginTop: null,
896                 //marginRight: null,
897                 //marginBottom: null,
898                 //marginLeft: null,
899                 borderColor: '#4572A7',
900                 //borderWidth: 0,
901                 borderRadius: 5,                
902                 defaultSeriesType: 'line',
903                 ignoreHiddenSeries: true,
904                 //inverted: false,
905                 //shadow: false,
906                 spacingTop: 10,
907                 spacingRight: 10,
908                 spacingBottom: 15,
909                 spacingLeft: 10,
910                 style: {
911                         fontFamily: '"Lucida Grande", "Lucida Sans Unicode", Verdana, Arial, Helvetica, sans-serif', // default font
912                         fontSize: '12px'
913                 },
914                 backgroundColor: '#FFFFFF',
915                 //plotBackgroundColor: null,
916                 plotBorderColor: '#C0C0C0'
917                 //plotBorderWidth: 0,
918                 //plotShadow: false,
919                 //zoomType: ''
920         },
921         title: {
922                 text: 'Chart title',
923                 align: 'center',
924                 // floating: false,
925                 // margin: 15,
926                 // x: 0,
927                 // verticalAlign: 'top',
928                 y: 15, // docs
929                 style: {
930                         color: '#3E576F',
931                         fontSize: '16px'
932                 }
934         },
935         subtitle: {
936                 text: '',
937                 align: 'center',
938                 // floating: false
939                 // x: 0,
940                 // verticalAlign: 'top',
941                 y: 30, // docs
942                 style: {
943                         color: '#6D869F'
944                 }
945         },
946         
947         plotOptions: {
948                 line: { // base series options
949                         allowPointSelect: false,
950                         showCheckbox: false,
951                         animation: {
952                                 duration: 1000
953                         },
954                         // connectNulls: false, // docs
955                         //cursor: 'default',
956                         //dashStyle: null,
957                         //enableMouseTracking: true,
958                         events: {},
959                         //legendIndex: 0, // docs (+ pie points)
960                         lineWidth: 2,
961                         shadow: true,
962                         // stacking: null,
963                         marker: { 
964                                 enabled: true,
965                                 //symbol: null, 
966                                 lineWidth: 0,
967                                 radius: 4,
968                                 lineColor: '#FFFFFF',
969                                 //fillColor: null, 
970                                 states: { // states for a single point
971                                         hover: {
972                                                 //radius: base + 2
973                                         },
974                                         select: {
975                                                 fillColor: '#FFFFFF',
976                                                 lineColor: '#000000',
977                                                 lineWidth: 2
978                                         }                                       
979                                 }
980                         },
981                         point: {
982                                 events: {}
983                         },
984                         dataLabels: merge(defaultLabelOptions, {
985                                 enabled: false,
986                                 y: -6,
987                                 formatter: function() {
988                                         return this.y;
989                                 }
990                         }),
991                         
992                         //pointStart: 0,
993                         //pointInterval: 1,
994                         showInLegend: true,
995                         states: { // states for the entire series
996                                 hover: {
997                                         //enabled: false,
998                                         //lineWidth: base + 1,
999                                         marker: {
1000                                                 // lineWidth: base + 1,
1001                                                 // radius: base + 1
1002                                         }
1003                                 },
1004                                 select: {
1005                                         marker: {}
1006                                 }
1007                         },
1008                         stickyTracking: true
1009                         //zIndex: null
1010                 }
1011         },
1012         labels: {
1013                 //items: [],
1014                 style: {
1015                         //font: defaultFont,
1016                         position: ABSOLUTE,
1017                         color: '#3E576F'
1018                 }
1019         },
1020         legend: {
1021                 enabled: true,
1022                 align: 'center',
1023                 //floating: false,
1024                 layout: 'horizontal',
1025                 labelFormatter: function() {
1026                         return this.name;
1027                 },
1028                 // lineHeight: 16, // docs: deprecated
1029                 borderWidth: 1,
1030                 borderColor: '#909090',
1031                 borderRadius: 5,
1032                 // margin: 10,
1033                 // reversed: false,
1034                 shadow: false,
1035                 // backgroundColor: null,
1036                 style: {
1037                         padding: '5px'
1038                 },
1039                 itemStyle: {
1040                         cursor: 'pointer',
1041                         color: '#3E576F'
1042                 },
1043                 itemHoverStyle: {
1044                         cursor: 'pointer',
1045                         color: '#000000'
1046                 },
1047                 itemHiddenStyle: {
1048                         color: '#C0C0C0'
1049                 },
1050                 itemCheckboxStyle: {
1051                         position: ABSOLUTE,
1052                         width: '13px', // for IE precision
1053                         height: '13px'
1054                 },
1055                 // itemWidth: undefined,
1056                 symbolWidth: 16,
1057                 symbolPadding: 5,
1058                 verticalAlign: 'bottom',
1059                 // width: undefined,
1060                 x: 0, // docs
1061                 y: 0 // docs
1062         },
1063         
1064         loading: {
1065                 hideDuration: 100,
1066                 labelStyle: {
1067                         fontWeight: 'bold',
1068                         position: RELATIVE,
1069                         top: '1em'
1070                 },
1071                 showDuration: 100,
1072                 style: {
1073                         position: ABSOLUTE,
1074                         backgroundColor: 'white',
1075                         opacity: 0.5,
1076                         textAlign: 'center'
1077                 }
1078         },
1079         
1080         tooltip: {
1081                 enabled: true,
1082                 //crosshairs: null,
1083                 backgroundColor: 'rgba(255, 255, 255, .85)',
1084                 borderWidth: 2,
1085                 borderRadius: 5,
1086                 //formatter: defaultFormatter,
1087                 shadow: true,
1088                 //shared: false,
1089                 snap: hasTouch ? 25 : 10,
1090                 style: {
1091                         color: '#333333',
1092                         fontSize: '12px',
1093                         padding: '5px',
1094                         whiteSpace: 'nowrap'
1095                 }
1096         },
1097         
1098         toolbar: {
1099                 itemStyle: {
1100                         color: '#4572A7',
1101                         cursor: 'pointer'
1102                 }
1103         },
1104         
1105         credits: {
1106                 enabled: true,
1107                 text: 'Highcharts.com',
1108                 href: 'http://www.highcharts.com',
1109                 position: {
1110                         align: 'right',
1111                         x: -10,
1112                         verticalAlign: 'bottom',
1113                         y: -5
1114                 },
1115                 style: {
1116                         cursor: 'pointer',
1117                         color: '#909090',
1118                         fontSize: '10px'
1119                 }
1120         }
1123 // Axis defaults
1124 var defaultXAxisOptions =  {
1125         // allowDecimals: null,
1126         // alternateGridColor: null,
1127         // categories: [],
1128         dateTimeLabelFormats: {
1129                 second: '%H:%M:%S',
1130                 minute: '%H:%M',
1131                 hour: '%H:%M',
1132                 day: '%e. %b',
1133                 week: '%e. %b',
1134                 month: '%b \'%y',
1135                 year: '%Y'
1136         },
1137         endOnTick: false,
1138         gridLineColor: '#C0C0C0',
1139         // gridLineDashStyle: 'solid', // docs
1140         // gridLineWidth: 0,
1141         // reversed: false,
1142         
1143         labels: defaultLabelOptions,
1144                 // { step: null },
1145         lineColor: '#C0D0E0',
1146         lineWidth: 1,
1147         //linkedTo: null,
1148         max: null,
1149         min: null,
1150         minPadding: 0.01,
1151         maxPadding: 0.01,
1152         //maxZoom: null,
1153         minorGridLineColor: '#E0E0E0',
1154         // minorGridLineDashStyle: null,
1155         minorGridLineWidth: 1,
1156         minorTickColor: '#A0A0A0',
1157         //minorTickInterval: null,
1158         minorTickLength: 2,
1159         minorTickPosition: 'outside', // inside or outside
1160         //minorTickWidth: 0,
1161         //opposite: false,
1162         //offset: 0,
1163         //plotBands: [{
1164         //      events: {},
1165         //      zIndex: 1,
1166         //      labels: { align, x, verticalAlign, y, style, rotation, textAlign }
1167         //}],
1168         //plotLines: [{
1169         //      events: {}
1170         //  dashStyle: {}
1171         //      zIndex:
1172         //      labels: { align, x, verticalAlign, y, style, rotation, textAlign }
1173         //}],
1174         //reversed: false,
1175         // showFirstLabel: true,
1176         // showLastLabel: false,
1177         startOfWeek: 1, 
1178         startOnTick: false,
1179         tickColor: '#C0D0E0',
1180         //tickInterval: null,
1181         tickLength: 5,
1182         tickmarkPlacement: 'between', // on or between
1183         tickPixelInterval: 100,
1184         tickPosition: 'outside',
1185         tickWidth: 1,
1186         title: {
1187                 //text: null,
1188                 align: 'middle', // low, middle or high
1189                 //margin: 0 for horizontal, 10 for vertical axes,
1190                 //rotation: 0,
1191                 //side: 'outside',
1192                 style: {
1193                         color: '#6D869F',
1194                         //font: defaultFont.replace('normal', 'bold')
1195                         fontWeight: 'bold'
1196                 }
1197                 //x: 0,
1198                 //y: 0
1199         },
1200         type: 'linear' // linear, logarithmic or datetime // docs
1203 defaultYAxisOptions = merge(defaultXAxisOptions, {
1204         endOnTick: true,
1205         gridLineWidth: 1,
1206         tickPixelInterval: 72,
1207         showLastLabel: true,
1208         labels: {
1209                 align: 'right',
1210                 x: -8,
1211                 y: 3
1212         },
1213         lineWidth: 0,
1214         maxPadding: 0.05,
1215         minPadding: 0.05,
1216         startOnTick: true,
1217         tickWidth: 0,
1218         title: {
1219                 rotation: 270,
1220                 text: 'Y-values'
1221         },
1222         stackLabels: {
1223                 enabled: false,
1224                 //align: dynamic,
1225                 //y: dynamic,
1226                 //x: dynamic,
1227                 //verticalAlign: dynamic,
1228                 //textAlign: dynamic,
1229                 //rotation: 0,
1230                 formatter: function() {
1231                         return this.total;
1232                 },
1233                 style: defaultLabelOptions.style
1234         }
1237 defaultLeftAxisOptions = {
1238         labels: {
1239                 align: 'right',
1240                 x: -8,
1241                 y: null // docs
1242         },
1243         title: {
1244                 rotation: 270
1245         }
1247 defaultRightAxisOptions = {
1248         labels: {
1249                 align: 'left',
1250                 x: 8,
1251                 y: null // docs
1252         },
1253         title: {
1254                 rotation: 90
1255         }
1257 defaultBottomAxisOptions = { // horizontal axis
1258         labels: {
1259                 align: 'center',
1260                 x: 0,
1261                 y: 14
1262                 // staggerLines: null
1263         },
1264         title: {
1265                 rotation: 0
1266         }
1268 defaultTopAxisOptions = merge(defaultBottomAxisOptions, {
1269         labels: {
1270                 y: -5
1271                 // staggerLines: null
1272         }
1278 // Series defaults
1279 var defaultPlotOptions = defaultOptions.plotOptions, 
1280         defaultSeriesOptions = defaultPlotOptions.line; 
1281 //defaultPlotOptions.line = merge(defaultSeriesOptions);
1282 defaultPlotOptions.spline = merge(defaultSeriesOptions);
1283 defaultPlotOptions.scatter = merge(defaultSeriesOptions, {
1284         lineWidth: 0,
1285         states: {
1286                 hover: {
1287                         lineWidth: 0
1288                 }
1289         }
1291 defaultPlotOptions.area = merge(defaultSeriesOptions, {
1292         // threshold: 0,
1293         // lineColor: null, // overrides color, but lets fillColor be unaltered
1294         // fillOpacity: 0.75,
1295         // fillColor: null
1298 defaultPlotOptions.areaspline = merge(defaultPlotOptions.area);
1299 defaultPlotOptions.column = merge(defaultSeriesOptions, {
1300         borderColor: '#FFFFFF',
1301         borderWidth: 1,
1302         borderRadius: 0,
1303         //colorByPoint: undefined,
1304         groupPadding: 0.2,
1305         marker: null, // point options are specified in the base options
1306         pointPadding: 0.1,
1307         //pointWidth: null,
1308         minPointLength: 0, 
1309         states: {
1310                 hover: {
1311                         brightness: 0.1,
1312                         shadow: false
1313                 },
1314                 select: {
1315                         color: '#C0C0C0',
1316                         borderColor: '#000000',
1317                         shadow: false
1318                 }
1319         },
1320         dataLabels: {
1321                 y: null,
1322                 verticalAlign: null
1323         }
1325 defaultPlotOptions.bar = merge(defaultPlotOptions.column, {
1326         dataLabels: {
1327                 align: 'left',
1328                 x: 5,
1329                 y: 0
1330         }
1332 defaultPlotOptions.pie = merge(defaultSeriesOptions, {
1333         //dragType: '', // n/a
1334         borderColor: '#FFFFFF',
1335         borderWidth: 1,
1336         center: ['50%', '50%'],
1337         colorByPoint: true, // always true for pies
1338         dataLabels: {
1339                 // align: null,
1340                 // connectorWidth: 1,
1341                 // connectorColor: '#606060',
1342                 // connectorPadding: 5,
1343                 distance: 30,
1344                 enabled: true,
1345                 formatter: function() {
1346                         return this.point.name;
1347                 },
1348                 y: 5
1349         },
1350         //innerSize: 0,
1351         legendType: 'point',
1352         marker: null, // point options are specified in the base options
1353         size: '75%',
1354         showInLegend: false,
1355         slicedOffset: 10,
1356         states: {
1357                 hover: {
1358                         brightness: 0.1,
1359                         shadow: false
1360                 }
1361         }
1362         
1365 // set the default time methods
1366 setTimeMethods();
1370  * Handle color operations. The object methods are chainable.
1371  * @param {String} input The input color in either rbga or hex format
1372  */
1373 var Color = function(input) {
1374         // declare variables
1375         var rgba = [], result;
1376         
1377         /**
1378          * Parse the input color to rgba array
1379          * @param {String} input
1380          */
1381         function init(input) {
1382                 
1383                 // rgba
1384                 result = /rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]?(?:\.[0-9]+)?)\s*\)/.exec(input);
1385                 if (result) {
1386                         rgba = [pInt(result[1]), pInt(result[2]), pInt(result[3]), parseFloat(result[4], 10)];
1387                 }
1389                 // hex
1390                 else {
1391                         result = /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(input);
1392                         if (result) {
1393                                 rgba = [pInt(result[1], 16), pInt(result[2], 16), pInt(result[3], 16), 1];
1394                         }
1395                 }
1396         
1397         }
1398         /**
1399          * Return the color a specified format
1400          * @param {String} format
1401          */
1402         function get(format) {
1403                 var ret;
1404                 
1405                 // it's NaN if gradient colors on a column chart
1406                 if (rgba && !isNaN(rgba[0])) {
1407                         if (format === 'rgb') {
1408                                 ret = 'rgb('+ rgba[0] +','+ rgba[1] +','+ rgba[2] +')';
1409                         } else if (format === 'a') {
1410                                 ret = rgba[3];
1411                         } else {
1412                                 ret = 'rgba('+ rgba.join(',') +')';
1413                         }
1414                 } else {
1415                         ret = input;
1416                 }
1417                 return ret;
1418         }
1419         
1420         /**
1421          * Brighten the color
1422          * @param {Number} alpha
1423          */
1424         function brighten(alpha) {
1425                 if (isNumber(alpha) && alpha !== 0) {
1426                         var i;
1427                         for (i = 0; i < 3; i++) {
1428                                 rgba[i] += pInt(alpha * 255);
1429                                 
1430                                 if (rgba[i] < 0) {
1431                                         rgba[i] = 0;
1432                                 }
1433                                 if (rgba[i] > 255) {
1434                                         rgba[i] = 255;
1435                                 }
1436                         }
1437                 }
1438                 return this;
1439         }
1440         /**
1441          * Set the color's opacity to a given alpha value
1442          * @param {Number} alpha
1443          */
1444         function setOpacity(alpha) {
1445                 rgba[3] = alpha;
1446                 return this;
1447         }       
1448         
1449         // initialize: parse the input
1450         init(input);
1451         
1452         // public methods
1453         return {
1454                 get: get,
1455                 brighten: brighten,
1456                 setOpacity: setOpacity
1457         };
1461  * A wrapper object for SVG elements 
1462  */
1463 function SVGElement () {}
1465 SVGElement.prototype = {
1466         /**
1467          * Initialize the SVG renderer
1468          * @param {Object} renderer
1469          * @param {String} nodeName
1470          */
1471         init: function(renderer, nodeName) {
1472                 this.element = doc.createElementNS(SVG_NS, nodeName);
1473                 this.renderer = renderer;
1474         },
1475         /**
1476          * Animate a given attribute
1477          * @param {Object} params
1478          * @param {Number} options The same options as in jQuery animation
1479          * @param {Function} complete Function to perform at the end of animation
1480          */
1481         animate: function(params, options, complete) {
1482                 var animOptions = pick(options, globalAnimation, true);
1483                 if (animOptions) {
1484                         animOptions = merge(animOptions);
1485                         if (complete) { // allows using a callback with the global animation without overwriting it
1486                                 animOptions.complete = complete;
1487                         }
1488                         animate(this, params, animOptions);
1489                 } else {
1490                         this.attr(params);
1491                         if (complete) {
1492                                 complete();
1493                         }
1494                 }
1495         },
1496         /**
1497          * Set or get a given attribute
1498          * @param {Object|String} hash
1499          * @param {Mixed|Undefined} val
1500          */
1501         attr: function(hash, val) {
1502                 var key, 
1503                         value, 
1504                         i, 
1505                         child,
1506                         element = this.element,
1507                         nodeName = element.nodeName,
1508                         renderer = this.renderer,
1509                         skipAttr,
1510                         shadows = this.shadows,
1511                         hasSetSymbolSize,
1512                         ret = this;
1513                         
1514                 // single key-value pair
1515                 if (isString(hash) && defined(val)) {
1516                         key = hash;
1517                         hash = {};
1518                         hash[key] = val;
1519                 }
1520                 
1521                 // used as a getter: first argument is a string, second is undefined
1522                 if (isString(hash)) {
1523                         key = hash;
1524                         if (nodeName === 'circle') {
1525                                 key = { x: 'cx', y: 'cy' }[key] || key;
1526                         } else if (key === 'strokeWidth') {
1527                                 key = 'stroke-width';
1528                         }
1529                         ret = attr(element, key) || this[key] || 0;
1530                         
1531                         if (key !== 'd' && key !== 'visibility') { // 'd' is string in animation step
1532                                 ret = parseFloat(ret);
1533                         }
1534                         
1535                 // setter
1536                 } else {
1537                 
1538                         for (key in hash) {
1539                                 skipAttr = false; // reset
1540                                 value = hash[key];
1541                                 
1542                                 // paths
1543                                 if (key === 'd') {
1544                                         if (value && value.join) { // join path
1545                                                 value = value.join(' ');
1546                                         }                                       
1547                                         if (/(NaN| {2}|^$)/.test(value)) {
1548                                                 value = 'M 0 0';
1549                                         }
1550                                         this.d = value; // shortcut for animations
1551                                         
1552                                 // update child tspans x values
1553                                 } else if (key === 'x' && nodeName === 'text') { 
1554                                         for (i = 0; i < element.childNodes.length; i++ ) {
1555                                                 child = element.childNodes[i];
1556                                                 // if the x values are equal, the tspan represents a linebreak
1557                                                 if (attr(child, 'x') === attr(element, 'x')) {
1558                                                         //child.setAttribute('x', value);
1559                                                         attr(child, 'x', value);
1560                                                 }
1561                                         }
1562                                         
1563                                         if (this.rotation) {
1564                                                 attr(element, 'transform', 'rotate('+ this.rotation +' '+ value +' '+
1565                                                         pInt(hash.y || attr(element, 'y')) +')');
1566                                         }
1567                                         
1568                                 // apply gradients
1569                                 } else if (key === 'fill') {
1570                                         value = renderer.color(value, element, key);
1571                                 
1572                                 // circle x and y
1573                                 } else if (nodeName === 'circle' && (key === 'x' || key === 'y')) {
1574                                         key = { x: 'cx', y: 'cy' }[key] || key;
1575                                         
1576                                 // translation and text rotation
1577                                 } else if (key === 'translateX' || key === 'translateY' || key === 'rotation' || key === 'verticalAlign') {
1578                                         this[key] = value;
1579                                         this.updateTransform();
1580                                         skipAttr = true;
1581         
1582                                 // apply opacity as subnode (required by legacy WebKit and Batik)
1583                                 } else if (key === 'stroke') {
1584                                         value = renderer.color(value, element, key);
1585                                         
1586                                 // emulate VML's dashstyle implementation
1587                                 } else if (key === 'dashstyle') {
1588                                         key = 'stroke-dasharray';
1589                                         value = value && value.toLowerCase();
1590                                         if (value === 'solid') {
1591                                                 value = NONE;
1592                                         } else if (value) {
1593                                                 value = value
1594                                                         .replace('shortdashdotdot', '3,1,1,1,1,1,')
1595                                                         .replace('shortdashdot', '3,1,1,1')
1596                                                         .replace('shortdot', '1,1,')
1597                                                         .replace('shortdash', '3,1,')
1598                                                         .replace('longdash', '8,3,')
1599                                                         .replace(/dot/g, '1,3,')
1600                                                         .replace('dash', '4,3,')
1601                                                         .replace(/,$/, '')
1602                                                         .split(','); // ending comma
1603                                                 
1604                                                 i = value.length;
1605                                                 while (i--) {
1606                                                         value[i] = pInt(value[i]) * hash['stroke-width'];
1607                                                 }
1608                                                 
1609                                                 value = value.join(',');
1610                                         }       
1611                                         
1612                                 // special
1613                                 } else if (key === 'isTracker') {
1614                                         this[key] = value;
1615                                 
1616                                 // IE9/MooTools combo: MooTools returns objects instead of numbers and IE9 Beta 2
1617                                 // is unable to cast them. Test again with final IE9.
1618                                 } else if (key === 'width') {
1619                                         value = pInt(value);
1620                                 
1621                                 // Text alignment
1622                                 } else if (key === 'align') {
1623                                         key = 'text-anchor';
1624                                         value = { left: 'start', center: 'middle', right: 'end' }[value];
1625                                 }
1626                                 
1627                                 
1628                                 
1629                                 // jQuery animate changes case
1630                                 if (key === 'strokeWidth') {
1631                                         key = 'stroke-width';
1632                                 }
1633                                 
1634                                 // Chrome/Win < 6 bug (http://code.google.com/p/chromium/issues/detail?id=15461)                                
1635                                 if (isWebKit && key === 'stroke-width' && value === 0) {
1636                                         value = 0.000001;
1637                                 }
1638                                 
1639                                 // symbols
1640                                 if (this.symbolName && /^(x|y|r|start|end|innerR)/.test(key)) {
1641                                         
1642                                         
1643                                         if (!hasSetSymbolSize) {
1644                                                 this.symbolAttr(hash);
1645                                                 hasSetSymbolSize = true;
1646                                         }
1647                                         skipAttr = true;
1648                                 }
1649                                 
1650                                 // let the shadow follow the main element
1651                                 if (shadows && /^(width|height|visibility|x|y|d)$/.test(key)) {
1652                                         i = shadows.length;
1653                                         while (i--) {
1654                                                 attr(shadows[i], key, value);
1655                                         }                                       
1656                                 }
1657                                 
1658                                 // validate heights
1659                                 if ((key === 'width' || key === 'height') && nodeName === 'rect' && value < 0) {
1660                                         value = 0;
1661                                 }
1662                                 
1663                                 if (key === 'text') {
1664                                         // only one node allowed
1665                                         this.textStr = value;
1666                                         if (this.added) {
1667                                                 renderer.buildText(this);
1668                                         }
1669                                 } else if (!skipAttr) {
1670                                         //element.setAttribute(key, value);
1671                                         attr(element, key, value);
1672                                 }
1673                                 
1674                         }
1675                         
1676                 }
1677                 return ret;
1678         },
1679         
1680         /**
1681          * If one of the symbol size affecting parameters are changed,
1682          * check all the others only once for each call to an element's
1683          * .attr() method
1684          * @param {Object} hash
1685          */
1686         symbolAttr: function(hash) {
1687                 var wrapper = this;
1688                 
1689                 each(['x', 'y', 'r', 'start', 'end', 'width', 'height', 'innerR'], function(key) {
1690                         wrapper[key] = pick(hash[key], wrapper[key]);
1691                 });
1692                 
1693                 wrapper.attr({ 
1694                         d: wrapper.renderer.symbols[wrapper.symbolName](
1695                                         mathRound(wrapper.x * 2) / 2, // Round to halves. Issue #274.
1696                                         mathRound(wrapper.y * 2) / 2, 
1697                                         wrapper.r, 
1698                         {
1699                                 start: wrapper.start, 
1700                                 end: wrapper.end,
1701                                 width: wrapper.width, 
1702                                 height: wrapper.height,
1703                                 innerR: wrapper.innerR
1704                         })
1705                 });
1706         },
1707         
1708         /**
1709          * Apply a clipping path to this object
1710          * @param {String} id
1711          */
1712         clip: function(clipRect) {
1713                 return this.attr('clip-path', 'url('+ this.renderer.url +'#'+ clipRect.id +')');
1714         },
1715         
1716         /**
1717          * Calculate the coordinates needed for drawing a rectangle crisply and return the
1718          * calculated attributes
1719          * @param {Number} strokeWidth
1720          * @param {Number} x
1721          * @param {Number} y
1722          * @param {Number} width
1723          * @param {Number} height
1724          */
1725         crisp: function(strokeWidth, x, y, width, height) {
1726                 
1727                 var wrapper = this,
1728                         key,
1729                         attr = {},
1730                         values = {},
1731                         normalizer;
1732                         
1733                 strokeWidth = strokeWidth || wrapper.strokeWidth || 0;
1734                 normalizer = strokeWidth % 2 / 2;
1736                 // normalize for crisp edges
1737                 values.x = mathFloor(x || wrapper.x || 0) + normalizer;
1738                 values.y = mathFloor(y || wrapper.y || 0) + normalizer;
1739                 values.width = mathFloor((width || wrapper.width || 0) - 2 * normalizer);
1740                 values.height = mathFloor((height || wrapper.height || 0) - 2 * normalizer);
1741                 values.strokeWidth = strokeWidth;
1742                 
1743                 for (key in values) {
1744                         if (wrapper[key] !== values[key]) { // only set attribute if changed
1745                                 wrapper[key] = attr[key] = values[key];
1746                         }
1747                 }
1748                 
1749                 return attr;
1750         },
1751         
1752         /**
1753          * Set styles for the element
1754          * @param {Object} styles
1755          */
1756         css: function(styles) {
1757                 var elemWrapper = this,
1758                         elem = elemWrapper.element,
1759                         textWidth = styles && styles.width && elem.nodeName === 'text',
1760                         camelStyles = styles,
1761                         n;
1762                         
1763                 // convert legacy
1764                 if (styles && styles.color) {
1765                         styles.fill = styles.color;
1766                 }
1767                 
1768                 // save the styles in an object
1769                 styles = extend(
1770                         elemWrapper.styles,
1771                         styles
1772                 );
1773                 
1774                 
1775                 // store object
1776                 elemWrapper.styles = styles;
1777                 
1778                 // hyphenate
1779                 if (defined(styles)) {
1780                         styles = {};
1781                         for (n in camelStyles) {
1782                                 styles[hyphenate(n)] = camelStyles[n];
1783                         }
1784                 }
1785                 
1786                 // serialize and set style attribute
1787                 if (isIE && !hasSVG) { // legacy IE doesn't support setting style attribute
1788                         if (textWidth) {
1789                                 delete styles.width;
1790                         } 
1791                         css(elemWrapper.element, styles);       
1792                 } else {
1793                         elemWrapper.attr({
1794                                 style: serializeCSS(styles)
1795                         });
1796                 }       
1797                 
1798                 
1799                 // re-build text
1800                 if (textWidth && elemWrapper.added) {
1801                         elemWrapper.renderer.buildText(elemWrapper);
1802                 }
1803                 
1804                 return elemWrapper;
1805         },
1806         
1807         /**
1808          * Add an event listener
1809          * @param {String} eventType
1810          * @param {Function} handler
1811          */
1812         on: function(eventType, handler) {
1813                 var fn = handler;
1814                 // touch
1815                 if (hasTouch && eventType === 'click') {
1816                         eventType = 'touchstart';
1817                         fn = function(e) {
1818                                 e.preventDefault();
1819                                 handler();
1820                         };
1821                 }
1822                 // simplest possible event model for internal use
1823                 this.element['on'+ eventType] = fn;
1824                 return this;
1825         },
1826         
1827         
1828         /**
1829          * Move an object and its children by x and y values
1830          * @param {Number} x
1831          * @param {Number} y
1832          */
1833         translate: function(x, y) {
1834                 return this.attr({
1835                         translateX: x,
1836                         translateY: y
1837                 });
1838         },
1839         
1840         /**
1841          * Invert a group, rotate and flip
1842          */
1843         invert: function() {
1844                 var wrapper = this;
1845                 wrapper.inverted = true;
1846                 wrapper.updateTransform();
1847                 return wrapper;
1848         },
1849         
1850         /**
1851          * Private method to update the transform attribute based on internal 
1852          * properties
1853          */
1854         updateTransform: function() {
1855                 var wrapper = this,
1856                         translateX = wrapper.translateX || 0,
1857                         translateY = wrapper.translateY || 0,
1858                         inverted = wrapper.inverted,
1859                         rotation = wrapper.rotation,
1860                         transform = [];
1861                         
1862                 // flipping affects translate as adjustment for flipping around the group's axis
1863                 if (inverted) {
1864                         translateX += wrapper.attr('width');
1865                         translateY += wrapper.attr('height');
1866                 }
1867                         
1868                 if(wrapper.imagesize) {
1869                         translateX -= wrapper.imagesize[0]/2;
1870                         translateY -= wrapper.imagesize[1]/2;
1871                 }
1872                 
1873                 // apply translate
1874                 if (translateX || translateY) {
1875                         transform.push('translate('+ translateX +','+ translateY +')');
1876                 }
1877                 
1878                 // apply rotation
1879                 if (inverted) {
1880                         transform.push('rotate(90) scale(-1,1)');
1881                 } else if (rotation) { // text rotation
1882                         transform.push('rotate('+ rotation +' '+ wrapper.x +' '+ wrapper.y +')');
1883                 }
1884                 
1885                 if (transform.length) {
1886                         attr(wrapper.element, 'transform', transform.join(' '));
1887                 }
1888         },
1889         /**
1890          * Bring the element to the front
1891          */
1892         toFront: function() {
1893                 var element = this.element;
1894                 element.parentNode.appendChild(element);
1895                 return this;
1896         },
1897         
1898         
1899         /**
1900          * Break down alignment options like align, verticalAlign, x and y 
1901          * to x and y relative to the chart.
1902          * 
1903          * @param {Object} alignOptions
1904          * @param {Boolean} alignByTranslate
1905          * @param {Object} box The box to align to, needs a width and height
1906          * 
1907          */
1908         align: function(alignOptions, alignByTranslate, box) {
1909                 var elemWrapper = this;
1910                 
1911                 if (!alignOptions) { // called on resize
1912                         alignOptions = elemWrapper.alignOptions;
1913                         alignByTranslate = elemWrapper.alignByTranslate;
1914                 } else { // first call on instanciate
1915                         elemWrapper.alignOptions = alignOptions;
1916                         elemWrapper.alignByTranslate = alignByTranslate;
1917                         if (!box) { // boxes other than renderer handle this internally
1918                                 elemWrapper.renderer.alignedObjects.push(elemWrapper);
1919                         }
1920                 }
1921                 
1922                 box = pick(box, elemWrapper.renderer);
1923                 
1924                 var align = alignOptions.align,
1925                         vAlign = alignOptions.verticalAlign,
1926                         x = (box.x || 0) + (alignOptions.x || 0), // default: left align
1927                         y = (box.y || 0) + (alignOptions.y || 0), // default: top align
1928                         attribs = {};
1929                         
1930                         
1931                 // align
1932                 if (/^(right|center)$/.test(align)) {
1933                         x += (box.width - (alignOptions.width || 0) ) /
1934                                         { right: 1, center: 2 }[align];
1935                 }
1936                 attribs[alignByTranslate ? 'translateX' : 'x'] = mathRound(x);
1937                 
1938                 
1939                 // vertical align
1940                 if (/^(bottom|middle)$/.test(vAlign)) {
1941                         y += (box.height - (alignOptions.height || 0)) /
1942                                         ({ bottom: 1, middle: 2 }[vAlign] || 1);
1943                         
1944                 }
1945                 attribs[alignByTranslate ? 'translateY' : 'y'] = mathRound(y);
1946                 
1947                 // animate only if already placed
1948                 elemWrapper[elemWrapper.placed ? 'animate' : 'attr'](attribs);
1949                 elemWrapper.placed = true;
1950                 elemWrapper.alignAttr = attribs;
1951                 
1952                 return elemWrapper;
1953         },
1954         
1955         /**
1956          * Get the bounding box (width, height, x and y) for the element
1957          */
1958         getBBox: function() {           
1959                 var bBox,
1960                         width,
1961                         height,
1962                         rotation = this.rotation,
1963                         rad = rotation * deg2rad;
1964                         
1965                 try { // fails in Firefox if the container has display: none
1966                         // use extend because IE9 is not allowed to change width and height in case 
1967                         // of rotation (below)
1968                         bBox = extend({}, this.element.getBBox());
1969                 } catch(e) {
1970                         bBox = { width: 0, height: 0 };
1971                 }
1972                 width = bBox.width;
1973                 height = bBox.height;
1974                         
1975                 // adjust for rotated text
1976                 if (rotation) {
1977                         bBox.width = mathAbs(height * mathSin(rad)) + mathAbs(width * mathCos(rad));
1978                         bBox.height = mathAbs(height * mathCos(rad)) + mathAbs(width * mathSin(rad));
1979                 }
1980                 
1981                 return bBox;
1982         },
1983         
1984         /* *
1985          * Manually compute width and height of rotated text from non-rotated. Shared by SVG and VML
1986          * @param {Object} bBox
1987          * @param {number} rotation
1988          * /
1989         rotateBBox: function(bBox, rotation) {
1990                 var rad = rotation * math.PI * 2 / 360, // radians
1991                         width = bBox.width,
1992                         height = bBox.height;
1993                         
1994                 
1995         },*/
1996         
1997         /**
1998          * Show the element
1999          */
2000         show: function() {
2001                 return this.attr({ visibility: VISIBLE });
2002         },
2003         
2004         /**
2005          * Hide the element
2006          */
2007         hide: function() {
2008                 return this.attr({ visibility: HIDDEN });
2009         },
2010         
2011         /**
2012          * Add the element
2013          * @param {Object|Undefined} parent Can be an element, an element wrapper or undefined
2014          *    to append the element to the renderer.box.
2015          */ 
2016         add: function(parent) {
2017         
2018                 var renderer = this.renderer,
2019                         parentWrapper = parent || renderer,
2020                         parentNode = parentWrapper.element || renderer.box,
2021                         childNodes = parentNode.childNodes,
2022                         element = this.element,
2023                         zIndex = attr(element, 'zIndex'),
2024                         otherElement,
2025                         otherZIndex,
2026                         i;
2027                         
2028                 // mark as inverted
2029                 this.parentInverted = parent && parent.inverted;
2030                 
2031                 // build formatted text
2032                 if (this.textStr !== undefined) {
2033                         renderer.buildText(this);
2034                 }
2035                 
2036                 // mark the container as having z indexed children
2037                 if (zIndex) {
2038                         parentWrapper.handleZ = true;
2039                         zIndex = pInt(zIndex);
2040                 }
2042                 // insert according to this and other elements' zIndex
2043                 if (parentWrapper.handleZ) { // this element or any of its siblings has a z index
2044                         for (i = 0; i < childNodes.length; i++) {
2045                                 otherElement = childNodes[i];
2046                                 otherZIndex = attr(otherElement, 'zIndex');
2047                                 if (otherElement !== element && (
2048                                                 // insert before the first element with a higher zIndex
2049                                                 pInt(otherZIndex) > zIndex || 
2050                                                 // if no zIndex given, insert before the first element with a zIndex
2051                                                 (!defined(zIndex) && defined(otherZIndex))  
2052                                                 
2053                                                 )) {
2054                                         parentNode.insertBefore(element, otherElement);
2055                                         return this;
2056                                 }
2057                         }
2058                 }
2059                 
2060                 // default: append at the end
2061                 parentNode.appendChild(element);
2062                 
2063                 this.added = true;
2064                 
2065                 return this;
2066         },
2068         /**
2069          * Destroy the element and element wrapper
2070          */
2071         destroy: function() {
2072                 var wrapper = this,
2073                         element = wrapper.element || {},
2074                         shadows = wrapper.shadows,
2075                         parentNode = element.parentNode,
2076                         key;
2077                 
2078                 // remove events
2079                 element.onclick = element.onmouseout = element.onmouseover = element.onmousemove = null;
2080                 stop(wrapper); // stop running animations
2081                 
2082                 // remove element
2083                 if (parentNode) {
2084                         parentNode.removeChild(element);
2085                 }
2086                 
2087                 // destroy shadows
2088                 if (shadows) {
2089                         each(shadows, function(shadow) {
2090                                 parentNode = shadow.parentNode;
2091                                 if (parentNode) { // the entire chart HTML can be overwritten
2092                                         parentNode.removeChild(shadow);
2093                                 }                               
2094                         });
2095                 }
2096                 
2097                 // remove from alignObjects
2098                 erase(wrapper.renderer.alignedObjects, wrapper);
2099                                 
2100                 for (key in wrapper) {
2101                         delete wrapper[key];
2102                 }
2103                 
2104                 return null;
2105         },
2106         
2107         /**
2108          * Empty a group element
2109          */
2110         empty: function() {
2111                 var element = this.element,
2112                         childNodes = element.childNodes,
2113                         i = childNodes.length;
2114                         
2115                 while (i--) {
2116                         element.removeChild(childNodes[i]);
2117                 }
2118         },
2119         
2120         /**
2121          * Add a shadow to the element. Must be done after the element is added to the DOM
2122          * @param {Boolean} apply
2123          */
2124         shadow: function(apply, group) {
2125                 var shadows = [],
2126                         i,
2127                         shadow,
2128                         element = this.element,
2129                         
2130                         // compensate for inverted plot area
2131                         transform = this.parentInverted ? '(-1,-1)' : '(1,1)';
2132                         
2133                 
2134                 if (apply) {
2135                         for (i = 1; i <= 3; i++) {
2136                                 shadow = element.cloneNode(0);
2137                                 attr(shadow, {
2138                                         'isShadow': 'true',
2139                                         'stroke': 'rgb(0, 0, 0)',
2140                                         'stroke-opacity': 0.05 * i,
2141                                         'stroke-width': 7 - 2 * i,
2142                                         'transform': 'translate'+ transform,
2143                                         'fill': NONE
2144                                 });
2145                                 
2146                                 if (group) {
2147                                         group.element.appendChild(shadow);
2148                                 } else {
2149                                         element.parentNode.insertBefore(shadow, element);
2150                                 }
2151                                 
2152                                 shadows.push(shadow);
2153                         }
2154                         
2155                         this.shadows = shadows;
2156                 }
2157                 return this;
2158         
2159         }
2163  * The default SVG renderer
2164  */
2165 var SVGRenderer = function() {
2166         this.init.apply(this, arguments);
2168 SVGRenderer.prototype = {
2169         
2170         Element: SVGElement,
2171         
2172         /**
2173          * Initialize the SVGRenderer
2174          * @param {Object} container
2175          * @param {Number} width
2176          * @param {Number} height
2177          * @param {Boolean} forExport
2178          */
2179         init: function(container, width, height, forExport) {
2180                 var renderer = this,
2181                         loc = location,
2182                         boxWrapper;
2183                                         
2184                 boxWrapper = renderer.createElement('svg')
2185                         .attr({
2186                                 xmlns: SVG_NS,
2187                                 version: '1.1'
2188                         });
2189                 container.appendChild(boxWrapper.element);
2190                 
2191                 // object properties
2192                 renderer.box = boxWrapper.element;
2193                 renderer.boxWrapper = boxWrapper;
2194                 renderer.alignedObjects = [];
2195                 renderer.url = isIE ? '' : loc.href.replace(/#.*?$/, ''); // page url used for internal references
2196                 renderer.defs = this.createElement('defs').add();
2197                 renderer.forExport = forExport;
2198                 
2199                 renderer.setSize(width, height, false);
2200                 
2201         },
2202         
2203         
2204         /**
2205          * Create a wrapper for an SVG element
2206          * @param {Object} nodeName
2207          */
2208         createElement: function(nodeName) {
2209                 var wrapper = new this.Element();
2210                 wrapper.init(this, nodeName);
2211                 return wrapper;
2212         },
2213         
2214         
2215         /** 
2216          * Parse a simple HTML string into SVG tspans
2217          * 
2218          * @param {Object} textNode The parent text SVG node
2219          */
2220         buildText: function(wrapper) {
2221                 var textNode = wrapper.element,
2222                         lines = pick(wrapper.textStr, '').toString()
2223                                 .replace(/<(b|strong)>/g, '<span style="font-weight:bold">')
2224                                 .replace(/<(i|em)>/g, '<span style="font-style:italic">')
2225                                 .replace(/<a/g, '<span')
2226                                 .replace(/<\/(b|strong|i|em|a)>/g, '</span>')
2227                                 .split(/<br.*?>/g),
2228                         childNodes = textNode.childNodes,
2229                         styleRegex = /style="([^"]+)"/,
2230                         hrefRegex = /href="([^"]+)"/,
2231                         parentX = attr(textNode, 'x'),
2232                         textStyles = wrapper.styles,
2233                         reverse = isFirefox && textStyles && textStyles['-hc-direction'] === 'rtl' && 
2234                                 !this.forExport && pInt(userAgent.split('Firefox/')[1]) < 4, // issue #38
2235                         arr,
2236                         width = textStyles && pInt(textStyles.width),
2237                         textLineHeight = textStyles && textStyles['line-height'],
2238                         lastLine,
2239                         GET_COMPUTED_STYLE = 'getComputedStyle',
2240                         i = childNodes.length;
2241                 
2242                 // remove old text
2243                 while (i--) {
2244                         textNode.removeChild(childNodes[i]);
2245                 }
2246                 
2247                 if (width && !wrapper.added) {
2248                         this.box.appendChild(textNode); // attach it to the DOM to read offset width
2249                 }
2250                 
2251                 each(lines, function(line, lineNo) {
2252                         var spans, spanNo = 0, lineHeight;
2253                         
2254                         line = line.replace(/<span/g, '|||<span').replace(/<\/span>/g, '</span>|||');
2255                         spans = line.split('|||');
2256                         
2257                         each(spans, function (span) {
2258                                 if (span !== '' || spans.length === 1) {
2259                                         var attributes = {},
2260                                                 tspan = doc.createElementNS(SVG_NS, 'tspan');
2261                                         if (styleRegex.test(span)) {
2262                                                 attr(
2263                                                         tspan, 
2264                                                         'style', 
2265                                                         span.match(styleRegex)[1].replace(/(;| |^)color([ :])/, '$1fill$2')
2266                                                 );
2267                                         }
2268                                         if (hrefRegex.test(span)) {
2269                                                 attr(tspan, 'onclick', 'location.href=\"'+ span.match(hrefRegex)[1] +'\"');
2270                                                 css(tspan, { cursor: 'pointer' });
2271                                         }
2272                                         
2273                                         span = (span.replace(/<(.|\n)*?>/g, '') || ' ')
2274                                                 .replace(/&lt;/g, '<')
2275                                                 .replace(/&gt;/g, '>');
2276                                         
2277                                         // issue #38 workaround.
2278                                         if (reverse) {
2279                                                 arr = [];
2280                                                 i = span.length;
2281                                                 while (i--) {
2282                                                         arr.push(span.charAt(i));
2283                                                 }
2284                                                 span = arr.join('');
2285                                         }
2286                                         
2287                                         // add the text node
2288                                         tspan.appendChild(doc.createTextNode(span));
2289                                         
2290                                         if (!spanNo) { // first span in a line, align it to the left
2291                                                 attributes.x = parentX;
2292                                         } else {
2293                                                 // Firefox ignores spaces at the front or end of the tspan
2294                                                 attributes.dx = 3; // space
2295                                         }
2296                                         
2297                                         // first span on subsequent line, add the line height
2298                                         if (!spanNo) {                                          
2299                                                 if (lineNo) {
2300                                                         
2301                                                         // allow getting the right offset height in exporting in IE
2302                                                         if (!hasSVG && wrapper.renderer.forExport) {
2303                                                                 css(tspan, { display: 'block' });
2304                                                         }
2305                                                         
2306                                                         // Webkit and opera sometimes return 'normal' as the line height. In that
2307                                                         // case, webkit uses offsetHeight, while Opera falls back to 18
2308                                                         lineHeight = win[GET_COMPUTED_STYLE] &&
2309                                                                 win[GET_COMPUTED_STYLE](lastLine, null).getPropertyValue('line-height');
2310                                                         
2311                                                         if (!lineHeight || isNaN(lineHeight)) {
2312                                                                 lineHeight = textLineHeight || lastLine.offsetHeight || 18;
2313                                                         }
2314                                                         attr(tspan, 'dy', lineHeight);
2315                                                 }
2316                                                 lastLine = tspan; // record for use in next line                                                
2317                                         }
2318                                         
2319                                         // add attributes
2320                                         attr(tspan, attributes);
2321                                         
2322                                         // append it
2323                                         textNode.appendChild(tspan);
2324                                         
2325                                         spanNo++;
2326                                         
2327                                         // check width and apply soft breaks
2328                                         if (width) {
2329                                                 var words = span.replace(/-/g, '- ').split(' '),
2330                                                         tooLong,
2331                                                         actualWidth,
2332                                                         rest = [];
2333                                                         
2334                                                 while (words.length || rest.length) {
2335                                                         actualWidth = textNode.getBBox().width;
2336                                                         tooLong = actualWidth > width;
2337                                                         if (!tooLong || words.length === 1) { // new line needed
2338                                                                 words = rest;
2339                                                                 rest = [];
2340                                                                 if (words.length) {
2341                                                                         tspan = doc.createElementNS(SVG_NS, 'tspan');
2342                                                                         attr(tspan, {
2343                                                                                 dy: textLineHeight || 16,
2344                                                                                 x: parentX
2345                                                                         });
2346                                                                         textNode.appendChild(tspan);
2347                                                                 
2348                                                                         if (actualWidth > width) { // a single word is pressing it out
2349                                                                                 width = actualWidth;
2350                                                                         }
2351                                                                 }
2352                                                         } else { // append to existing line tspan
2353                                                                 tspan.removeChild(tspan.firstChild);
2354                                                                 rest.unshift(words.pop());                                                      
2355                                                         }
2356                                                         if (words.length) {
2357                                                                 tspan.appendChild(doc.createTextNode(words.join(' ').replace(/- /g, '-')));
2358                                                         }
2359                                                 }
2360                                         }
2361                                 }
2362                         });
2363                 });
2364                 
2365                 
2366         },
2367         
2368         /**
2369          * Make a straight line crisper by not spilling out to neighbour pixels
2370          * @param {Array} points
2371          * @param {Number} width 
2372          */
2373         crispLine: function(points, width) {
2374                 // points format: [M, 0, 0, L, 100, 0]
2375                 // normalize to a crisp line
2376                 if (points[1] === points[4]) {
2377                         points[1] = points[4] = mathRound(points[1]) + (width % 2 / 2);
2378                 }
2379                 if (points[2] === points[5]) {
2380                         points[2] = points[5] = mathRound(points[2]) + (width % 2 / 2);
2381                 }
2382                 return points;
2383         },
2384         
2385         
2386         /**
2387          * Draw a path
2388          * @param {Array} path An SVG path in array form
2389          */
2390         path: function (path) {
2391                 return this.createElement('path').attr({ 
2392                         d: path, 
2393                         fill: NONE
2394                 });
2395         },
2396         
2397         /**
2398          * Draw and return an SVG circle
2399          * @param {Number} x The x position
2400          * @param {Number} y The y position
2401          * @param {Number} r The radius
2402          */
2403         circle: function (x, y, r) {
2404                 var attr = isObject(x) ?
2405                         x :
2406                         {
2407                                 x: x,
2408                                 y: y,
2409                                 r: r
2410                         };
2411                 
2412                 return this.createElement('circle').attr(attr);
2413         },
2414         
2415         /**
2416          * Draw and return an arc
2417          * @param {Number} x X position
2418          * @param {Number} y Y position
2419          * @param {Number} r Radius
2420          * @param {Number} innerR Inner radius like used in donut charts
2421          * @param {Number} start Starting angle
2422          * @param {Number} end Ending angle
2423          */
2424         arc: function (x, y, r, innerR, start, end) {
2425                 // arcs are defined as symbols for the ability to set 
2426                 // attributes in attr and animate
2427                 
2428                 if (isObject(x)) {
2429                         y = x.y;
2430                         r = x.r;
2431                         innerR = x.innerR;
2432                         start = x.start;
2433                         end = x.end;
2434                         x = x.x;
2435                 }
2436                 
2437                 return this.symbol('arc', x || 0, y || 0, r || 0, {
2438                         innerR: innerR || 0,
2439                         start: start || 0,
2440                         end: end || 0
2441                 });
2442         },
2443         
2444         /**
2445          * Draw and return a rectangle
2446          * @param {Number} x Left position
2447          * @param {Number} y Top position
2448          * @param {Number} width
2449          * @param {Number} height
2450          * @param {Number} r Border corner radius
2451          * @param {Number} strokeWidth A stroke width can be supplied to allow crisp drawing
2452          */
2453         rect: function (x, y, width, height, r, strokeWidth) {
2454                 if (isObject(x)) {
2455                         y = x.y;
2456                         width = x.width;
2457                         height = x.height;
2458                         r = x.r;
2459                         strokeWidth = x.strokeWidth;
2460                         x = x.x;        
2461                 }
2462                 var wrapper = this.createElement('rect').attr({
2463                         rx: r,
2464                         ry: r,
2465                         fill: NONE
2466                 });
2467                 
2468                 return wrapper.attr(wrapper.crisp(strokeWidth, x, y, mathMax(width, 0), mathMax(height, 0)));
2469         },
2470         
2471         /**
2472          * Resize the box and re-align all aligned elements
2473          * @param {Object} width
2474          * @param {Object} height
2475          * @param {Boolean} animate
2476          * 
2477          */
2478         setSize: function(width, height, animate) {
2479                 var renderer = this,
2480                         alignedObjects = renderer.alignedObjects,
2481                         i = alignedObjects.length;
2482                 
2483                 renderer.width = width;
2484                 renderer.height = height;
2485                 
2486                 renderer.boxWrapper[pick(animate, true) ? 'animate' : 'attr']({
2487                         width: width,
2488                         height: height
2489                 });             
2490                 
2491                 while (i--) {
2492                         alignedObjects[i].align();
2493                 }
2494         },
2495         
2496         /**
2497          * Create a group
2498          * @param {String} name The group will be given a class name of 'highcharts-{name}'.
2499          *     This can be used for styling and scripting.
2500          */
2501         g: function(name) {
2502                 return this.createElement('g').attr(
2503                         defined(name) && { 'class': PREFIX + name }
2504                 );
2505         },
2506         
2507         /**
2508          * Display an image
2509          * @param {String} src
2510          * @param {Number} x
2511          * @param {Number} y
2512          * @param {Number} width
2513          * @param {Number} height
2514          */
2515         image: function(src, x, y, width, height) {
2516                 var attribs = {
2517                                 preserveAspectRatio: NONE       
2518                         },
2519                         elemWrapper;
2520                         
2521                 // optional properties
2522                 if (arguments.length > 1) {
2523                         extend(attribs, {
2524                                 x: x,
2525                                 y: y,
2526                                 width: width,
2527                                 height: height
2528                         });
2529                 }
2530                 
2531                 elemWrapper = this.createElement('image').attr(attribs);                
2532                 
2533                 // set the href in the xlink namespace
2534                 if (elemWrapper.element.setAttributeNS) {
2535                         elemWrapper.element.setAttributeNS('http://www.w3.org/1999/xlink', 
2536                                 'href', src);
2537                 } else {
2538                         // could be exporting in IE
2539                         // using href throws "not supported" in ie7 and under, requries regex shim to fix later
2540                         elemWrapper.element.setAttribute('hc-svg-href', src);
2541                 }
2542                         
2543                 return elemWrapper;                                     
2544         },
2545         
2546         /**
2547          * Draw a symbol out of pre-defined shape paths from the namespace 'symbol' object.
2548          * 
2549          * @param {Object} symbol
2550          * @param {Object} x
2551          * @param {Object} y
2552          * @param {Object} radius
2553          * @param {Object} options
2554          */
2555         symbol: function(symbol, x, y, radius, options) {
2556                 
2557                 var obj,
2558                         
2559                         // get the symbol definition function
2560                         symbolFn = this.symbols[symbol],
2561                         
2562                         // check if there's a path defined for this symbol
2563                         path = symbolFn && symbolFn(
2564                                 mathRound(x), 
2565                                 mathRound(y),
2566                                 radius, 
2567                                 options
2568                         ),
2569                         
2570                         imageRegex = /^url\((.*?)\)$/,
2571                         imageSrc,
2572                         imageSize;
2573                         
2574                 if (path) {
2575                 
2576                         obj = this.path(path);
2577                         // expando properties for use in animate and attr
2578                         extend(obj, {
2579                                 symbolName: symbol,
2580                                 x: x,
2581                                 y: y,
2582                                 r: radius
2583                         });
2584                         if (options) {
2585                                 extend(obj, options);
2586                         }
2587                         
2588                         
2589                 // image symbols
2590                 } else if (imageRegex.test(symbol)) {
2591                         
2592                         var centerImage = function(img, size) {
2593                                 img.attr({
2594                                         width: size[0],
2595                                         height: size[1]
2596                                 }).translate(
2597                                         obj.translateX-mathRound(size[0] / 2),
2598                                         obj.translateY-mathRound(size[1] / 2)
2599                                 );
2600                                 img.imagesize = [size[0], size[1]];
2601                         };
2602                         
2603                         imageSrc = symbol.match(imageRegex)[1];
2604                         imageSize = symbolSizes[imageSrc];
2605                         
2606                         // create the image synchronously, add attribs async
2607                         obj = this.image(imageSrc)
2608                                 .attr({
2609                                         x: x,
2610                                         y: y
2611                                 });
2613                         if (imageSize) {
2614                                 centerImage(obj, imageSize);
2615                         } else {
2616                                 // initialize image to be 0 size so export will still function if there's no cached sizes
2617                                 obj.attr({ width: 0, height: 0 });
2619                                 // create a dummy JavaScript image to get the width and height  
2620                                 createElement('img', {
2621                                         onload: function() {
2622                                                 var img = this;
2623                                                 centerImage(obj, symbolSizes[imageSrc] = [img.width, img.height]);
2624                                         },
2625                                         src: imageSrc
2626                                 });
2627                         }
2628                                 
2629                 // default circles
2630                 } else {
2631                         obj = this.circle(x, y, radius);
2632                 }
2633                 
2634                 return obj;
2635         },
2636         
2637         /**
2638          * An extendable collection of functions for defining symbol paths.
2639          */
2640         symbols: {
2641                 'square': function (x, y, radius) {
2642                         var len = 0.707 * radius;
2643                         return [
2644                                 M, x-len, y-len,
2645                                 L, x+len, y-len,
2646                                 x+len, y+len,
2647                                 x-len, y+len,
2648                                 'Z'
2649                         ];
2650                 },
2651                         
2652                 'triangle': function (x, y, radius) {
2653                         return [
2654                                 M, x, y-1.33 * radius,
2655                                 L, x+radius, y + 0.67 * radius,
2656                                 x-radius, y + 0.67 * radius,
2657                                 'Z'
2658                         ];
2659                 },
2660                         
2661                 'triangle-down': function (x, y, radius) {
2662                         return [
2663                                 M, x, y + 1.33 * radius,
2664                                 L, x-radius, y-0.67 * radius,
2665                                 x+radius, y-0.67 * radius,
2666                                 'Z'
2667                         ];
2668                 },
2669                 'diamond': function (x, y, radius) {
2670                         return [
2671                                 M, x, y-radius,
2672                                 L, x+radius, y,
2673                                 x, y+radius,
2674                                 x-radius, y,
2675                                 'Z'
2676                         ];
2677                 },
2678                 'arc': function (x, y, radius, options) {
2679                         var start = options.start,
2680                                 end = options.end - 0.000001, // to prevent cos and sin of start and end from becoming equal on 360 arcs
2681                                 innerRadius = options.innerR,
2682                                 cosStart = mathCos(start),
2683                                 sinStart = mathSin(start),
2684                                 cosEnd = mathCos(end),
2685                                 sinEnd = mathSin(end),
2686                                 longArc = options.end - start < mathPI ? 0 : 1;
2687                                 
2688                         return [
2689                                 M,
2690                                 x + radius * cosStart,
2691                                 y + radius * sinStart,
2692                                 'A', // arcTo
2693                                 radius, // x radius
2694                                 radius, // y radius
2695                                 0, // slanting
2696                                 longArc, // long or short arc
2697                                 1, // clockwise
2698                                 x + radius * cosEnd,
2699                                 y + radius * sinEnd,
2700                                 L,                              
2701                                 x + innerRadius * cosEnd, 
2702                                 y + innerRadius * sinEnd,
2703                                 'A', // arcTo
2704                                 innerRadius, // x radius
2705                                 innerRadius, // y radius
2706                                 0, // slanting
2707                                 longArc, // long or short arc
2708                                 0, // clockwise
2709                                 x + innerRadius * cosStart,
2710                                 y + innerRadius * sinStart,
2711                                 
2712                                 'Z' // close
2713                         ];
2714                 }
2715         },
2716         
2717         /**
2718          * Define a clipping rectangle
2719          * @param {String} id
2720          * @param {Number} x
2721          * @param {Number} y
2722          * @param {Number} width
2723          * @param {Number} height
2724          */
2725         clipRect: function (x, y, width, height) {
2726                 var wrapper,
2727                         id = PREFIX + idCounter++,
2728                         
2729                         clipPath = this.createElement('clipPath').attr({
2730                                 id: id
2731                         }).add(this.defs);
2732                 
2733                 wrapper = this.rect(x, y, width, height, 0).add(clipPath);
2734                 wrapper.id = id;
2735                 
2736                 return wrapper;
2737         },
2738         
2739         
2740         /**
2741          * Take a color and return it if it's a string, make it a gradient if it's a
2742          * gradient configuration object
2743          * 
2744          * @param {Object} color The color or config object
2745          */
2746         color: function(color, elem, prop) {
2747                 var colorObject,
2748                         regexRgba = /^rgba/;
2749                 if (color && color.linearGradient) {
2750                         var renderer = this,
2751                                 strLinearGradient = 'linearGradient',
2752                                 linearGradient = color[strLinearGradient],
2753                                 id = PREFIX + idCounter++,
2754                                 gradientObject,
2755                                 stopColor,
2756                                 stopOpacity;
2757                         gradientObject = renderer.createElement(strLinearGradient).attr({
2758                                 id: id,
2759                                 gradientUnits: 'userSpaceOnUse',
2760                                 x1: linearGradient[0],
2761                                 y1: linearGradient[1],
2762                                 x2: linearGradient[2],
2763                                 y2: linearGradient[3]
2764                         }).add(renderer.defs);
2765                         
2766                         each(color.stops, function(stop) {
2767                                 if (regexRgba.test(stop[1])) {
2768                                         colorObject = Color(stop[1]);
2769                                         stopColor = colorObject.get('rgb');
2770                                         stopOpacity = colorObject.get('a');
2771                                 } else {
2772                                         stopColor = stop[1];
2773                                         stopOpacity = 1;
2774                                 }
2775                                 renderer.createElement('stop').attr({
2776                                         offset: stop[0],
2777                                         'stop-color': stopColor,
2778                                         'stop-opacity': stopOpacity
2779                                 }).add(gradientObject);
2780                         });
2781                         
2782                         return 'url('+ this.url +'#'+ id +')';
2783                         
2784                 // Webkit and Batik can't show rgba.
2785                 } else if (regexRgba.test(color)) {
2786                         colorObject = Color(color);
2787                         attr(elem, prop +'-opacity', colorObject.get('a'));
2788                         
2789                         return colorObject.get('rgb');
2790                         
2791                         
2792                 } else {
2793                         return color;
2794                 }
2795                 
2796         },
2797         
2798                 
2799         /**
2800          * Add text to the SVG object
2801          * @param {String} str
2802          * @param {Number} x Left position
2803          * @param {Number} y Top position
2804          */
2805         text: function(str, x, y) {
2806                 
2807                 // declare variables
2808                 var defaultChartStyle = defaultOptions.chart.style,
2809                         wrapper;
2810         
2811                 x = mathRound(pick(x, 0));
2812                 y = mathRound(pick(y, 0));
2813                 
2814                 wrapper = this.createElement('text')
2815                         .attr({
2816                                 x: x,
2817                                 y: y,
2818                                 text: str       
2819                         })
2820                         .css({
2821                                 'font-family': defaultChartStyle.fontFamily,
2822                                 'font-size': defaultChartStyle.fontSize
2823                         });
2824                         
2825                 wrapper.x = x;
2826                 wrapper.y = y;
2827                 return wrapper;
2828         }
2829 }; // end SVGRenderer
2831 // general renderer
2832 Renderer = SVGRenderer;
2836 /* **************************************************************************** 
2837  *                                                                            * 
2838  * START OF INTERNET EXPLORER <= 8 SPECIFIC CODE                              *
2839  *                                                                            *
2840  * For applications and websites that don't need IE support, like platform    *
2841  * targeted mobile apps and web apps, this code can be removed.               *
2842  *                                                                            *
2843  *****************************************************************************/
2844 var VMLRenderer;
2845 if (!hasSVG) {
2848  * The VML element wrapper.
2849  */
2850 var VMLElement = extendClass( SVGElement, {
2851         
2852         /**
2853          * Initialize a new VML element wrapper. It builds the markup as a string
2854          * to minimize DOM traffic.
2855          * @param {Object} renderer
2856          * @param {Object} nodeName
2857          */
2858         init: function(renderer, nodeName) {
2859                 var markup =  ['<', nodeName, ' filled="f" stroked="f"'],
2860                         style = ['position: ', ABSOLUTE, ';'];
2861                 
2862                 // divs and shapes need size
2863                 if (nodeName === 'shape' || nodeName === DIV) {
2864                         style.push('left:0;top:0;width:10px;height:10px;');
2865                 }
2866                 if (docMode8) {
2867                         style.push('visibility: ', nodeName === DIV ? HIDDEN : VISIBLE);
2868                 }
2869                 
2870                 markup.push(' style="', style.join(''), '"/>');
2871                 
2872                 // create element with default attributes and style
2873                 if (nodeName) {
2874                         markup = nodeName === DIV || nodeName === 'span' || nodeName === 'img' ? 
2875                                 markup.join('')
2876                                 : renderer.prepVML(markup);
2877                         this.element = createElement(markup);
2878                 }
2879                 
2880                 this.renderer = renderer;
2881         },
2882         
2883         /**
2884          * Add the node to the given parent
2885          * @param {Object} parent
2886          */
2887         add: function(parent) {
2888                 var wrapper = this,
2889                         renderer = wrapper.renderer,
2890                         element = wrapper.element,
2891                         box = renderer.box,
2892                         inverted = parent && parent.inverted,
2893                 
2894                         // get the parent node
2895                         parentNode = parent ? 
2896                                 parent.element || parent : 
2897                                 box;
2898                         
2899                         
2900                 // if the parent group is inverted, apply inversion on all children
2901                 if (inverted) { // only on groups
2902                         renderer.invertChild(element, parentNode);                      
2903                 }
2904                 
2905                 // issue #140 workaround - related to #61 and #74
2906                 if (docMode8 && parentNode.gVis === HIDDEN) {
2907                         css(element, { visibility: HIDDEN });
2908                 }
2909                 
2910                 // append it
2911                 parentNode.appendChild(element);
2912                 
2913                 // align text after adding to be able to read offset
2914                 wrapper.added = true;
2915                 if (wrapper.alignOnAdd) {
2916                         wrapper.updateTransform();
2917                 }               
2918                 
2919                 return wrapper;
2920         },
2921         
2922         /**
2923          * Get or set attributes
2924          */
2925         attr: function(hash, val) {
2926                 var key, 
2927                         value, 
2928                         i, 
2929                         element = this.element || {},
2930                         elemStyle = element.style,
2931                         nodeName = element.nodeName,
2932                         renderer = this.renderer,
2933                         symbolName = this.symbolName,
2934                         childNodes,
2935                         hasSetSymbolSize,
2936                         shadows = this.shadows,
2937                         skipAttr,
2938                         ret = this;
2939                         
2940                 // single key-value pair
2941                 if (isString(hash) && defined(val)) {
2942                         key = hash;
2943                         hash = {};
2944                         hash[key] = val;
2945                 }
2946                 
2947                 // used as a getter, val is undefined
2948                 if (isString(hash)) {
2949                         key = hash;
2950                         if (key === 'strokeWidth' || key === 'stroke-width') {
2951                                 ret = this.strokeweight;
2952                         } else {
2953                                 ret = this[key];
2954                         }
2955                         
2956                 // setter
2957                 } else {                
2958                         for (key in hash) {
2959                                 value = hash[key];
2960                                 skipAttr = false;
2961                                 
2962                                 // prepare paths
2963                                 // symbols
2964                                 if (symbolName && /^(x|y|r|start|end|width|height|innerR)/.test(key)) {
2965                                         // if one of the symbol size affecting parameters are changed,
2966                                         // check all the others only once for each call to an element's
2967                                         // .attr() method
2968                                         if (!hasSetSymbolSize) {
2969                                                 this.symbolAttr(hash);                                          
2970                                         
2971                                                 hasSetSymbolSize = true;
2972                                         } 
2973                                         
2974                                         skipAttr = true;
2975                                         
2976                                 } else if (key === 'd') {
2977                                         value = value || [];
2978                                         this.d = value.join(' '); // used in getter for animation
2979                                         
2980                                         // convert paths 
2981                                         i = value.length;
2982                                         var convertedPath = [];
2983                                         while (i--) {                                   
2984                                                 
2985                                                 // Multiply by 10 to allow subpixel precision.
2986                                                 // Substracting half a pixel seems to make the coordinates
2987                                                 // align with SVG, but this hasn't been tested thoroughly
2988                                                 if (isNumber(value[i])) {
2989                                                         convertedPath[i] = mathRound(value[i] * 10) - 5;
2990                                                 }
2991                                                 // close the path
2992                                                 else if (value[i] === 'Z') {
2993                                                         convertedPath[i] = 'x';
2994                                                 } 
2995                                                 else {
2996                                                         convertedPath[i] = value[i];
2997                                                 }
2998                                                 
2999                                         }
3000                                         value = convertedPath.join(' ') || 'x'; 
3001                                         element.path = value;
3002                         
3003                                         // update shadows
3004                                         if (shadows) {
3005                                                 i = shadows.length;
3006                                                 while (i--) {
3007                                                         shadows[i].path = value;
3008                                                 }
3009                                         }
3010                                         skipAttr = true;
3011         
3012                                 // directly mapped to css
3013                                 } else if (key === 'zIndex' || key === 'visibility') {
3014                                         
3015                                         // issue 61 workaround
3016                                         if (docMode8 && key === 'visibility' && nodeName === 'DIV') {
3017                                                 element.gVis = value;
3018                                                 childNodes = element.childNodes;
3019                                                 i = childNodes.length;
3020                                                 while (i--) {
3021                                                         css(childNodes[i], { visibility: value });
3022                                                 }
3023                                                 if (value === VISIBLE) { // issue 74
3024                                                         value = null;
3025                                                 }
3026                                         }
3027                                         
3028                                         if (value) {
3029                                                 elemStyle[key] = value;
3030                                         }
3031                                         
3032                                         
3033                                         
3034                                         skipAttr = true;
3035                                 
3036                                 // width and height
3037                                 } else if (/^(width|height)$/.test(key)) {
3038                                         
3039                                                                                 
3040                                         // clipping rectangle special
3041                                         if (this.updateClipping) {
3042                                                 this[key] = value;
3043                                                 this.updateClipping();
3044                                                 
3045                                         } else {
3046                                                 // normal
3047                                                 elemStyle[key] = value;
3048                                         }
3049                                         
3050                                         skipAttr = true;
3051                                         
3052                                 // x and y 
3053                                 } else if (/^(x|y)$/.test(key)) {
3055                                         this[key] = value; // used in getter
3056                                         
3057                                         if (element.tagName === 'SPAN') {
3058                                                 this.updateTransform();
3059                                         
3060                                         } else {
3061                                                 elemStyle[{ x: 'left', y: 'top' }[key]] = value;
3062                                         }
3063                                         
3064                                 // class name
3065                                 } else if (key === 'class') {
3066                                         // IE8 Standards mode has problems retrieving the className
3067                                         element.className = value;
3068                         
3069                                 // stroke
3070                                 } else if (key === 'stroke') {
3071                                         
3072                                         value = renderer.color(value, element, key);                            
3073                                                 
3074                                         key = 'strokecolor';
3075                                         
3076                                 // stroke width
3077                                 } else if (key === 'stroke-width' || key === 'strokeWidth') {
3078                                         element.stroked = value ? true : false;
3079                                         key = 'strokeweight';
3080                                         this[key] = value; // used in getter, issue #113
3081                                         if (isNumber(value)) {
3082                                                 value += PX;
3083                                         }
3084                                         
3085                                 // dashStyle                                     
3086                                 } else if (key === 'dashstyle') {
3087                                         var strokeElem = element.getElementsByTagName('stroke')[0] ||
3088                                                 createElement(renderer.prepVML(['<stroke/>']), null, null, element);
3089                                         strokeElem[key] = value || 'solid';
3090                                         this.dashstyle = value; /* because changing stroke-width will change the dash length
3091                                                 and cause an epileptic effect */ 
3092                                         skipAttr = true;
3093                                         
3094                                 // fill
3095                                 } else if (key === 'fill') {
3096                                         
3097                                         if (nodeName === 'SPAN') { // text color
3098                                                 elemStyle.color = value;
3099                                         } else {
3100                                                 element.filled = value !== NONE ? true : false;
3101                                                 
3102                                                 value = renderer.color(value, element, key);
3103                                                 
3104                                                 key = 'fillcolor';
3105                                         }
3106                                 
3107                                 // translation for animation
3108                                 } else if (key === 'translateX' || key === 'translateY' || key === 'rotation' || key === 'align') {
3109                                         if (key === 'align') {
3110                                                 key = 'textAlign';
3111                                         }
3112                                         this[key] = value;
3113                                         this.updateTransform();
3114                                         
3115                                         skipAttr = true;
3116                                 }
3117                                 
3118                                 // text for rotated and non-rotated elements
3119                                 else if (key === 'text') {
3120                                         this.bBox = null;
3121                                         element.innerHTML = value;
3122                                         skipAttr = true;
3123                                 } 
3124                                 
3125                                         
3126                                 // let the shadow follow the main element
3127                                 if (shadows && key === 'visibility') {
3128                                         i = shadows.length;
3129                                         while (i--) {
3130                                                 shadows[i].style[key] = value;
3131                                         }
3132                                 }
3133                                 
3134                                 
3135                                 
3136                                 if (!skipAttr) {
3137                                         if (docMode8) { // IE8 setAttribute bug
3138                                                 element[key] = value;
3139                                         } else {
3140                                                 attr(element, key, value);
3141                                         }
3142                                 }
3143                         }                       
3144                 }
3145                 return ret;
3146         },
3147         
3148         /**
3149          * Set the element's clipping to a predefined rectangle
3150          * 
3151          * @param {String} id The id of the clip rectangle
3152          */
3153         clip: function(clipRect) {
3154                 var wrapper = this,
3155                         clipMembers = clipRect.members;
3156                         
3157                 clipMembers.push(wrapper);
3158                 wrapper.destroyClip = function() {
3159                         erase(clipMembers, wrapper);
3160                 };
3161                 return wrapper.css(clipRect.getCSS(wrapper.inverted));
3162         },
3163         
3164         /**
3165          * Set styles for the element
3166          * @param {Object} styles
3167          */
3168         css: function(styles) {
3169                 var wrapper = this,
3170                         element = wrapper.element,
3171                         textWidth = styles && element.tagName === 'SPAN' && styles.width;
3172                 
3173                 /*if (textWidth) {
3174                         extend(styles, {
3175                                 display: 'block',
3176                                 whiteSpace: 'normal'
3177                         });     
3178                 }*/
3179                 if (textWidth) {
3180                         delete styles.width;
3181                         wrapper.textWidth = textWidth;
3182                         wrapper.updateTransform();      
3183                 }
3184                 
3185                 wrapper.styles = extend(wrapper.styles, styles);
3186                 css(wrapper.element, styles);
3187                 
3188                 return wrapper;
3189         },
3190         
3191         /**
3192          * Extend element.destroy by removing it from the clip members array
3193          */
3194         destroy: function() {
3195                 var wrapper = this;
3196                 
3197                 if (wrapper.destroyClip) {
3198                         wrapper.destroyClip();
3199                 }
3200                 
3201                 SVGElement.prototype.destroy.apply(wrapper);
3202         },
3203         
3204         /**
3205          * Remove all child nodes of a group, except the v:group element
3206          */
3207         empty: function() {
3208                 var element = this.element,
3209                         childNodes = element.childNodes,
3210                         i = childNodes.length,
3211                         node;
3212                         
3213                 while (i--) {
3214                         node = childNodes[i];
3215                         node.parentNode.removeChild(node);
3216                 }
3217         },
3218         
3219         /**
3220          * VML override for calculating the bounding box based on offsets
3221          * 
3222          * @return {Object} A hash containing values for x, y, width and height
3223          */
3224         
3225         getBBox: function() {
3226                 var wrapper = this,
3227                         element = wrapper.element,
3228                         bBox = wrapper.bBox;
3229                 
3230                 if (!bBox) {
3231                         // faking getBBox in exported SVG in legacy IE
3232                         if (element.nodeName === 'text') {
3233                                 element.style.position = ABSOLUTE;
3234                         }
3235                         
3236                         bBox = wrapper.bBox = {
3237                                 x: element.offsetLeft,
3238                                 y: element.offsetTop,
3239                                 width: element.offsetWidth,
3240                                 height: element.offsetHeight
3241                         };
3242                 }
3243                 return bBox;
3244                                         
3245         },
3246         
3247         /**
3248          * Add an event listener. VML override for normalizing event parameters.
3249          * @param {String} eventType
3250          * @param {Function} handler
3251          */
3252         on: function(eventType, handler) {
3253                 // simplest possible event model for internal use
3254                 this.element['on'+ eventType] = function() {
3255                         var evt = win.event;
3256                         evt.target = evt.srcElement;
3257                         handler(evt);
3258                 };
3259                 return this;
3260         },
3261         
3262         
3263         /**
3264          * VML override private method to update elements based on internal 
3265          * properties based on SVG transform
3266          */
3267         updateTransform: function(hash) { 
3268                 // aligning non added elements is expensive
3269                 if (!this.added) {
3270                         this.alignOnAdd = true;
3271                         return;
3272                 }
3273                 
3274                 var wrapper = this,
3275                         elem = wrapper.element,
3276                         translateX = wrapper.translateX || 0,
3277                         translateY = wrapper.translateY || 0,
3278                         x = wrapper.x || 0,
3279                         y = wrapper.y || 0,
3280                         align = wrapper.textAlign || 'left',
3281                         alignCorrection = { left: 0, center: 0.5, right: 1 }[align],
3282                         nonLeft = align && align !== 'left';
3283                 
3284                 // apply translate
3285                 if (translateX || translateY) {
3286                         wrapper.css({
3287                                 marginLeft: translateX,
3288                                 marginTop: translateY
3289                         });
3290                 }
3291                 
3292                 // apply inversion
3293                 if (wrapper.inverted) { // wrapper is a group
3294                         each(elem.childNodes, function(child) {
3295                                 wrapper.renderer.invertChild(child, elem);
3296                         });
3297                 }
3298                 
3299                 if (elem.tagName === 'SPAN') {
3300                         
3301                         var width, height,
3302                                 rotation = wrapper.rotation,
3303                                 lineHeight,
3304                                 radians = 0,
3305                                 costheta = 1,
3306                                 sintheta = 0,
3307                                 quad,
3308                                 textWidth = pInt(wrapper.textWidth),
3309                                 xCorr = wrapper.xCorr || 0,
3310                                 yCorr = wrapper.yCorr || 0,
3311                                 currentTextTransform = [rotation, align, elem.innerHTML, wrapper.textWidth].join(',');
3312                                 
3313                         if (currentTextTransform !== wrapper.cTT) { // do the calculations and DOM access only if properties changed
3314                                 
3315                                 if (defined(rotation)) {
3316                                         radians = rotation * deg2rad; // deg to rad
3317                                         costheta = mathCos(radians);
3318                                         sintheta = mathSin(radians);                            
3319                                          
3320                                         // Adjust for alignment and rotation.
3321                                         // Test case: http://highcharts.com/tests/?file=text-rotation
3322                                         css(elem, {
3323                                                 filter: rotation ? ['progid:DXImageTransform.Microsoft.Matrix(M11=', costheta, 
3324                                                         ', M12=', -sintheta, ', M21=', sintheta, ', M22=', costheta, 
3325                                                         ', sizingMethod=\'auto expand\')'].join('') : NONE
3326                                         });
3327                                 }
3328                                 
3329                                 width = elem.offsetWidth;
3330                                 height = elem.offsetHeight;
3331                                 
3332                                 // update textWidth
3333                                 if (width > textWidth) {
3334                                         css(elem, {
3335                                                 width: textWidth +PX,
3336                                                 display: 'block',
3337                                                 whiteSpace: 'normal'
3338                                         });
3339                                         width = textWidth;
3340                                 }
3341                                 
3342                                 // correct x and y
3343                                 lineHeight = mathRound((pInt(elem.style.fontSize) || 12) * 1.2);
3344                                 xCorr = costheta < 0 && -width;
3345                                 yCorr = sintheta < 0 && -height;
3346                                 
3347                                 // correct for lineHeight and corners spilling out after rotation
3348                                 quad = costheta * sintheta < 0;
3349                                 xCorr += sintheta * lineHeight * (quad ? 1 - alignCorrection : alignCorrection);
3350                                 yCorr -= costheta * lineHeight * (rotation ? (quad ? alignCorrection : 1 - alignCorrection) : 1);
3351                                 
3352                                 // correct for the length/height of the text
3353                                 if (nonLeft) {
3354                                         xCorr -= width * alignCorrection * (costheta < 0 ? -1 : 1);
3355                                         if (rotation) {
3356                                                 yCorr -= height * alignCorrection * (sintheta < 0 ? -1 : 1);
3357                                         }
3358                                         css(elem, {
3359                                                 textAlign: align
3360                                         });
3361                                 }
3362                                 
3363                                 // record correction
3364                                 wrapper.xCorr = xCorr;
3365                                 wrapper.yCorr = yCorr; 
3366                         }
3367                         
3368                         // apply position with correction
3369                         css(elem, {
3370                                 left: x + xCorr,
3371                                 top: y + yCorr
3372                         });
3373                         
3374                         // record current text transform
3375                         wrapper.cTT = currentTextTransform;
3376                 }
3377         },
3378         
3379         /**
3380          * Apply a drop shadow by copying elements and giving them different strokes 
3381          * @param {Boolean} apply
3382          */
3383         shadow: function(apply, group) {
3384                 var shadows = [],
3385                         i,
3386                         element = this.element,
3387                         renderer = this.renderer,
3388                         shadow,
3389                         elemStyle = element.style,
3390                         markup,
3391                         path = element.path;
3392                         
3393                 // some times empty paths are not strings
3394                 if (path && typeof path.value !== 'string') {
3395                         path = 'x';
3396                 }
3397                         
3398                 if (apply) {
3399                         for (i = 1; i <= 3; i++) {
3400                                 markup = ['<shape isShadow="true" strokeweight="', ( 7 - 2 * i ) ,
3401                                         '" filled="false" path="', path,
3402                                         '" coordsize="100,100" style="', element.style.cssText, '" />'];
3403                                 shadow = createElement(renderer.prepVML(markup),
3404                                         null, {
3405                                                 left: pInt(elemStyle.left) + 1,
3406                                                 top: pInt(elemStyle.top) + 1
3407                                         }
3408                                 );
3409                                 
3410                                 // apply the opacity
3411                                 markup = ['<stroke color="black" opacity="', (0.05 * i), '"/>'];
3412                                 createElement(renderer.prepVML(markup), null, null, shadow);                            
3413                                 
3414                                 
3415                                 // insert it
3416                                 if (group) {
3417                                         group.element.appendChild(shadow);
3418                                 } else {
3419                                         element.parentNode.insertBefore(shadow, element);
3420                                 }
3421                                 
3422                                 // record it
3423                                 shadows.push(shadow);                           
3424                                 
3425                         }
3426                         
3427                         this.shadows = shadows;
3428                 }
3429                 return this;
3430         
3431         }
3433         
3435  * The VML renderer
3436  */
3437 VMLRenderer = function() {
3438         this.init.apply(this, arguments);
3440 VMLRenderer.prototype = merge( SVGRenderer.prototype, { // inherit SVGRenderer
3441         
3442         Element: VMLElement,
3443         isIE8: userAgent.indexOf('MSIE 8.0') > -1,
3444         
3446         /**
3447          * Initialize the VMLRenderer
3448          * @param {Object} container
3449          * @param {Number} width
3450          * @param {Number} height
3451          */
3452         init: function(container, width, height) {
3453                 var renderer = this,
3454                         boxWrapper;
3456                 renderer.alignedObjects = [];
3457                 
3458                 boxWrapper = renderer.createElement(DIV);
3459                 container.appendChild(boxWrapper.element);
3460                 
3461                 
3462                 // generate the containing box
3463                 renderer.box = boxWrapper.element;
3464                 renderer.boxWrapper = boxWrapper;
3465                 
3466                 
3467                 renderer.setSize(width, height, false);
3468                 
3469                 // The only way to make IE6 and IE7 print is to use a global namespace. However,
3470                 // with IE8 the only way to make the dynamic shapes visible in screen and print mode
3471                 // seems to be to add the xmlns attribute and the behaviour style inline. 
3472                 if (!doc.namespaces.hcv) {                      
3473                         
3474                         doc.namespaces.add('hcv', 'urn:schemas-microsoft-com:vml');
3475                         
3476                         // setup default css
3477                         doc.createStyleSheet().cssText = 
3478                                 'hcv\\:fill, hcv\\:path, hcv\\:shape, hcv\\:stroke'+
3479                                 '{ behavior:url(#default#VML); display: inline-block; } ';
3480                         
3481                 }       
3482         },
3483         
3484         /**
3485          * Define a clipping rectangle. In VML it is accomplished by storing the values
3486          * for setting the CSS style to all associated members.
3487          * 
3488          * @param {Number} x
3489          * @param {Number} y
3490          * @param {Number} width
3491          * @param {Number} height
3492          */
3493         clipRect: function (x, y, width, height) {
3494                                 
3495                 // create a dummy element
3496                 var clipRect = this.createElement();
3497                 
3498                 // mimic a rectangle with its style object for automatic updating in attr
3499                 return extend(clipRect, {
3500                         members: [],
3501                         left: x,
3502                         top: y,
3503                         width: width,
3504                         height: height,
3505                         getCSS: function(inverted) {
3506                                 var rect = this,//clipRect.element.style,
3507                                         top = rect.top,
3508                                         left = rect.left,
3509                                         right = left + rect.width,
3510                                         bottom = top + rect.height,
3511                                         ret = {
3512                                                 clip: 'rect('+ 
3513                                                         mathRound(inverted ? left : top) + 'px,'+ 
3514                                                         mathRound(inverted ? bottom : right) + 'px,'+ 
3515                                                         mathRound(inverted ? right : bottom) + 'px,'+ 
3516                                                         mathRound(inverted ? top : left) +'px)'
3517                                         };
3518                                         
3519                                 // issue 74 workaround
3520                                 if (!inverted && docMode8) {
3521                                         extend(ret, {
3522                                                 width: right +PX,
3523                                                 height: bottom +PX
3524                                         });
3525                                 }
3526                                 return ret;
3527                         },
3528                         
3529                         // used in attr and animation to update the clipping of all members
3530                         updateClipping: function() {
3531                                 each(clipRect.members, function(member) {
3532                                         member.css(clipRect.getCSS(member.inverted));
3533                                 });
3534                         }
3535                 });
3536                 
3537         },
3538         
3539         
3540         /**
3541          * Take a color and return it if it's a string, make it a gradient if it's a
3542          * gradient configuration object, and apply opacity.
3543          * 
3544          * @param {Object} color The color or config object
3545          */
3546         color: function(color, elem, prop) {
3547                 var colorObject,
3548                         regexRgba = /^rgba/,
3549                         markup;
3550                         
3551                 if (color && color.linearGradient) {
3552                         
3553                         var stopColor, 
3554                                 stopOpacity,
3555                                 linearGradient = color.linearGradient,
3556                                 angle,
3557                                 color1,
3558                                 opacity1,
3559                                 color2,
3560                                 opacity2;       
3561                                 
3562                         each(color.stops, function(stop, i) {
3563                                 if (regexRgba.test(stop[1])) {
3564                                         colorObject = Color(stop[1]);
3565                                         stopColor = colorObject.get('rgb');
3566                                         stopOpacity = colorObject.get('a');
3567                                 } else {
3568                                         stopColor = stop[1];
3569                                         stopOpacity = 1;
3570                                 }
3571                                 
3572                                 if (!i) { // first
3573                                         color1 = stopColor;
3574                                         opacity1 = stopOpacity;
3575                                 } else {
3576                                         color2 = stopColor;
3577                                         opacity2 = stopOpacity;
3578                                 }
3579                         });
3580                         
3581                         
3582                         
3583                         // calculate the angle based on the linear vector
3584                         angle = 90  - math.atan(
3585                                 (linearGradient[3] - linearGradient[1]) / // y vector
3586                                 (linearGradient[2] - linearGradient[0]) // x vector
3587                                 ) * 180 / mathPI;
3588                         
3589                         // when colors attribute is used, the meanings of opacity and o:opacity2
3590                         // are reversed.
3591                         markup = ['<', prop, ' colors="0% ', color1, ',100% ', color2, '" angle="', angle,
3592                                 '" opacity="', opacity2, '" o:opacity2="', opacity1,
3593                                 '" type="gradient" focus="100%" />'];
3594                         createElement(this.prepVML(markup), null, null, elem);
3595                         
3596                         
3597                 
3598                 // if the color is an rgba color, split it and add a fill node
3599                 // to hold the opacity component
3600                 } else if (regexRgba.test(color) && elem.tagName !== 'IMG') {
3601                         
3602                         colorObject = Color(color);
3603                         
3604                         markup = ['<', prop, ' opacity="', colorObject.get('a'), '"/>'];
3605                         createElement(this.prepVML(markup), null, null, elem);
3606                         
3607                         return colorObject.get('rgb');
3608                         
3609                         
3610                 } else {
3611                         return color;
3612                 }
3613                 
3614         },
3615         
3616         /**
3617          * Take a VML string and prepare it for either IE8 or IE6/IE7. 
3618          * @param {Array} markup A string array of the VML markup to prepare
3619          */
3620         prepVML: function(markup) {
3621                 var vmlStyle = 'display:inline-block;behavior:url(#default#VML);',
3622                         isIE8 = this.isIE8;
3623         
3624                 markup = markup.join('');
3625                 
3626                 if (isIE8) { // add xmlns and style inline
3627                         markup = markup.replace('/>', ' xmlns="urn:schemas-microsoft-com:vml" />');
3628                         if (markup.indexOf('style="') === -1) {
3629                                 markup = markup.replace('/>', ' style="'+ vmlStyle +'" />');
3630                         } else {
3631                                 markup = markup.replace('style="', 'style="'+ vmlStyle);
3632                         }
3634                 } else { // add namespace
3635                         markup = markup.replace('<', '<hcv:');
3636                 }
3638                 return markup;
3639         },
3640         
3641         /**
3642          * Create rotated and aligned text
3643          * @param {String} str
3644          * @param {Number} x
3645          * @param {Number} y
3646          */
3647         text: function(str, x, y) {
3648                 
3649                 var defaultChartStyle = defaultOptions.chart.style; 
3650                         
3651                 return this.createElement('span')
3652                         .attr({
3653                                 text: str,
3654                                 x: mathRound(x),
3655                                 y: mathRound(y)
3656                         })
3657                         .css({
3658                                 whiteSpace: 'nowrap',
3659                                 fontFamily: defaultChartStyle.fontFamily,
3660                                 fontSize: defaultChartStyle.fontSize
3661                         });
3662         },
3663         
3664         /**
3665          * Create and return a path element
3666          * @param {Array} path
3667          */
3668         path: function (path) {
3669                 // create the shape
3670                 return this.createElement('shape').attr({
3671                         // subpixel precision down to 0.1 (width and height = 10px)
3672                         coordsize: '100 100',
3673                         d: path
3674                 });
3675         },
3676         
3677         /**
3678          * Create and return a circle element. In VML circles are implemented as
3679          * shapes, which is faster than v:oval
3680          * @param {Number} x
3681          * @param {Number} y
3682          * @param {Number} r
3683          */
3684         circle: function(x, y, r) {
3685                 return this.symbol('circle').attr({ x: x, y: y, r: r});
3686         },
3687         
3688         /**
3689          * Create a group using an outer div and an inner v:group to allow rotating 
3690          * and flipping. A simple v:group would have problems with positioning
3691          * child HTML elements and CSS clip.
3692          * 
3693          * @param {String} name The name of the group
3694          */
3695         g: function(name) {
3696                 var wrapper,
3697                         attribs;
3698                 
3699                 // set the class name   
3700                 if (name) {
3701                         attribs = { 'className': PREFIX + name, 'class': PREFIX + name };
3702                 }
3703                 
3704                 // the div to hold HTML and clipping    
3705                 wrapper = this.createElement(DIV).attr(attribs);
3706                 
3707                 return wrapper;
3708         },
3709         
3710         /**
3711          * VML override to create a regular HTML image
3712          * @param {String} src
3713          * @param {Number} x
3714          * @param {Number} y
3715          * @param {Number} width
3716          * @param {Number} height
3717          */
3718         image: function(src, x, y, width, height) {
3719                 var obj = this.createElement('img')
3720                         .attr({ src: src });
3721                         
3722                 if (arguments.length > 1) {
3723                         obj.css({
3724                                 left: x,
3725                                 top: y,
3726                                 width: width,
3727                                 height: height
3728                         });
3729                 }
3730                 return obj;
3731         },
3732         
3733         /**
3734          * VML uses a shape for rect to overcome bugs and rotation problems
3735          */
3736         rect: function(x, y, width, height, r, strokeWidth) {
3737                 
3738                 if (isObject(x)) {
3739                         y = x.y;
3740                         width = x.width;
3741                         height = x.height;
3742                         r = x.r;
3743                         strokeWidth = x.strokeWidth;
3744                         x = x.x;
3745                 }
3746                 var wrapper = this.symbol('rect');
3747                 wrapper.r = r;
3748                 
3749                 return wrapper.attr(wrapper.crisp(strokeWidth, x, y, mathMax(width, 0), mathMax(height, 0)));
3750         },
3751         
3752         /**
3753          * In the VML renderer, each child of an inverted div (group) is inverted
3754          * @param {Object} element
3755          * @param {Object} parentNode
3756          */
3757         invertChild: function(element, parentNode) {
3758                 var parentStyle = parentNode.style;
3759                         
3760                 css(element, { 
3761                         flip: 'x',
3762                         left: pInt(parentStyle.width) - 10,
3763                         top: pInt(parentStyle.height) - 10,
3764                         rotation: -90
3765                 });
3766         },
3767         
3768         /**
3769          * Symbol definitions that override the parent SVG renderer's symbols
3770          * 
3771          */
3772         symbols: {
3773                 // VML specific arc function
3774                 arc: function (x, y, radius, options) {
3775                         var start = options.start,
3776                                 end = options.end,
3777                                 cosStart = mathCos(start),
3778                                 sinStart = mathSin(start),
3779                                 cosEnd = mathCos(end),
3780                                 sinEnd = mathSin(end),
3781                                 innerRadius = options.innerR,
3782                                 circleCorrection = 0.07 / radius,
3783                                 innerCorrection = (innerRadius && 0.1 / innerRadius) || 0;
3784                                 
3785                         if (end - start === 0) { // no angle, don't show it. 
3786                                 return ['x'];
3787                                 
3788                         //} else if (end - start == 2 * mathPI) { // full circle
3789                         } else if (2 * mathPI - end + start < circleCorrection) { // full circle
3790                                 // empirical correction found by trying out the limits for different radii
3791                                 cosEnd = - circleCorrection;
3792                         } else if (end - start < innerCorrection) { // issue #186, another mysterious VML arc problem
3793                                 cosEnd = mathCos(start + innerCorrection);
3794                         }
3795                                                                 
3796                         return [
3797                                 'wa', // clockwise arc to
3798                                 x - radius, // left
3799                                 y - radius, // top
3800                                 x + radius, // right
3801                                 y + radius, // bottom
3802                                 x + radius * cosStart, // start x
3803                                 y + radius * sinStart, // start y
3804                                 x + radius * cosEnd, // end x
3805                                 y + radius * sinEnd, // end y
3806                                 
3807                                 
3808                                 'at', // anti clockwise arc to
3809                                 x - innerRadius, // left
3810                                 y - innerRadius, // top
3811                                 x + innerRadius, // right
3812                                 y + innerRadius, // bottom
3813                                 x + innerRadius * cosEnd, // start x
3814                                 y + innerRadius * sinEnd, // start y
3815                                 x + innerRadius * cosStart, // end x
3816                                 y + innerRadius * sinStart, // end y
3817                                 
3818                                 'x', // finish path
3819                                 'e' // close
3820                         ];
3821                         
3822                 },
3823                 // Add circle symbol path. This performs significantly faster than v:oval.
3824                 circle: function (x, y, r) {
3825                         return [
3826                                 'wa', // clockwisearcto
3827                                 x - r, // left
3828                                 y - r, // top
3829                                 x + r, // right
3830                                 y + r, // bottom
3831                                 x + r, // start x
3832                                 y,     // start y
3833                                 x + r, // end x
3834                                 y,     // end y
3835                                 //'x', // finish path
3836                                 'e' // close
3837                         ];
3838                 },
3839                 /** 
3840                  * Add rectangle symbol path which eases rotation and omits arcsize problems
3841                  * compared to the built-in VML roundrect shape
3842                  * 
3843                  * @param {Number} left Left position
3844                  * @param {Number} top Top position
3845                  * @param {Number} r Border radius
3846                  * @param {Object} options Width and height
3847                  */
3848                 
3849                 rect: function (left, top, r, options) {
3850                         if (!defined(options)) {
3851                                 return [];
3852                         }
3853                         var width = options.width,
3854                                 height = options.height,
3855                                 right = left + width,
3856                                 bottom = top + height;
3857                 
3858                         r = mathMin(r, width, height);
3859                         
3860                         return [
3861                                 M,
3862                                 left + r, top,
3863                                 
3864                                 L,
3865                                 right - r, top,
3866                                 'wa',
3867                                 right - 2 * r, top,
3868                                 right, top + 2 * r,
3869                                 right - r, top,
3870                                 right, top + r,
3871                                 
3872                                 L,
3873                                 right, bottom - r,
3874                                 'wa',
3875                                 right - 2 * r, bottom - 2 * r,
3876                                 right, bottom,
3877                                 right, bottom - r,
3878                                 right - r, bottom,
3879                                 
3880                                 L,
3881                                 left + r, bottom,
3882                                 'wa',
3883                                 left, bottom - 2 * r,
3884                                 left + 2 * r, bottom, 
3885                                 left + r, bottom,
3886                                 left, bottom - r,
3887                                 
3888                                 L,
3889                                 left, top + r,
3890                                 'wa',
3891                                 left, top,
3892                                 left + 2 * r, top + 2 * r,
3893                                 left, top + r,
3894                                 left + r, top,
3895                                 
3896                                 
3897                                 'x',
3898                                 'e'
3899                         ];
3900                                 
3901                 }
3902         }
3905 // general renderer
3906 Renderer = VMLRenderer;
3908 /* **************************************************************************** 
3909  *                                                                            * 
3910  * END OF INTERNET EXPLORER <= 8 SPECIFIC CODE                                *
3911  *                                                                            *
3912  *****************************************************************************/
3913         
3916  * The chart class
3917  * @param {Object} options
3918  * @param {Function} callback Function to run when the chart has loaded
3919  */
3920 function Chart (options, callback) {
3921         
3922         defaultXAxisOptions = merge(defaultXAxisOptions, defaultOptions.xAxis);
3923         defaultYAxisOptions = merge(defaultYAxisOptions, defaultOptions.yAxis);
3924         defaultOptions.xAxis = defaultOptions.yAxis = null;
3925                 
3926         // Handle regular options
3927         options = merge(defaultOptions, options);
3928         
3929         // Define chart variables
3930         var optionsChart = options.chart,
3931                 optionsMargin = optionsChart.margin,
3932                 margin = isObject(optionsMargin) ?
3933                         optionsMargin : 
3934                         [optionsMargin, optionsMargin, optionsMargin, optionsMargin],
3935                 optionsMarginTop = pick(optionsChart.marginTop, margin[0]),
3936                 optionsMarginRight = pick(optionsChart.marginRight, margin[1]),
3937                 optionsMarginBottom = pick(optionsChart.marginBottom, margin[2]),
3938                 optionsMarginLeft = pick(optionsChart.marginLeft, margin[3]),
3939                 spacingTop = optionsChart.spacingTop,
3940                 spacingRight = optionsChart.spacingRight,
3941                 spacingBottom = optionsChart.spacingBottom,
3942                 spacingLeft = optionsChart.spacingLeft,
3943                 spacingBox, 
3944                 chartTitleOptions,
3945                 chartSubtitleOptions,
3946                 plotTop,
3947                 marginRight,
3948                 marginBottom,
3949                 plotLeft,
3950                 axisOffset,
3951                 renderTo,
3952                 renderToClone,
3953                 container,
3954                 containerId,
3955                 containerWidth,
3956                 containerHeight,
3957                 chartWidth,
3958                 chartHeight,
3959                 oldChartWidth,
3960                 oldChartHeight,
3961                 chartBackground,
3962                 plotBackground,
3963                 plotBGImage,
3964                 plotBorder,
3965                 chart = this,
3966                 chartEvents = optionsChart.events,
3967                 runChartClick = chartEvents && !!chartEvents.click,
3968                 eventType,
3969                 isInsidePlot, // function
3970                 tooltip,
3971                 mouseIsDown,
3972                 loadingDiv,
3973                 loadingSpan,
3974                 loadingShown,
3975                 plotHeight,
3976                 plotWidth,
3977                 tracker,
3978                 trackerGroup,
3979                 placeTrackerGroup,
3980                 legend,
3981                 legendWidth,
3982                 legendHeight,
3983                 chartPosition,// = getPosition(container),
3984                 hasCartesianSeries = optionsChart.showAxes,
3985                 isResizing = 0,
3986                 axes = [],
3987                 maxTicks, // handle the greatest amount of ticks on grouped axes
3988                 series = [], 
3989                 inverted,
3990                 renderer,
3991                 tooltipTick,
3992                 tooltipInterval,
3993                 hoverX,
3994                 drawChartBox, // function
3995                 getMargins, // function
3996                 resetMargins, // function
3997                 setChartSize, // function
3998                 resize,
3999                 zoom, // function
4000                 zoomOut; // function
4001                 
4003         /**
4004          * Create a new axis object
4005          * @param {Object} chart
4006          * @param {Object} options
4007          */
4008         function Axis (chart, options) {
4010                 // Define variables
4011                 var isXAxis = options.isX,
4012                         opposite = options.opposite, // needed in setOptions                    
4013                         horiz = inverted ? !isXAxis : isXAxis,
4014                         side = horiz ? 
4015                                 (opposite ? 0 /* top */  : 2 /* bottom */) :
4016                                 (opposite ? 1 /* right*/ : 3 /* left */  ),
4017                         stacks = {};
4018                         
4019         
4020                 options = merge(
4021                                 isXAxis ? defaultXAxisOptions : defaultYAxisOptions,
4022                                 [defaultTopAxisOptions, defaultRightAxisOptions, 
4023                                         defaultBottomAxisOptions, defaultLeftAxisOptions][side],
4024                                 options
4025                         );
4026         
4027                 var axis = this,
4028                         type = options.type,
4029                         isDatetimeAxis = type === 'datetime',
4030                         isLog = type === 'logarithmic',
4031                         offset = options.offset || 0,
4032                         xOrY = isXAxis ? 'x' : 'y',
4033                         axisLength,
4034                         transA, // translation factor
4035                         oldTransA, // used for prerendering
4036                         transB = horiz ? plotLeft : marginBottom, // translation addend
4037                         translate, // fn
4038                         getPlotLinePath, // fn
4039                         axisGroup,
4040                         gridGroup,
4041                         axisLine,
4042                         dataMin,
4043                         dataMax,
4044                         associatedSeries,
4045                         userMin,
4046                         userMax,
4047                         max = null,
4048                         min = null,
4049                         oldMin,
4050                         oldMax,
4051                         minPadding = options.minPadding,
4052                         maxPadding = options.maxPadding,
4053                         isLinked = defined(options.linkedTo),
4054                         ignoreMinPadding, // can be set to true by a column or bar series
4055                         ignoreMaxPadding,
4056                         usePercentage,
4057                         events = options.events,
4058                         eventType,
4059                         plotLinesAndBands = [],
4060                         tickInterval,
4061                         minorTickInterval,
4062                         magnitude,
4063                         tickPositions, // array containing predefined positions
4064                         ticks = {},
4065                         minorTicks = {},
4066                         alternateBands = {},
4067                         tickAmount,
4068                         labelOffset,
4069                         axisTitleMargin,// = options.title.margin,
4070                         dateTimeLabelFormat,
4071                         categories = options.categories,
4072                         labelFormatter = options.labels.formatter ||  // can be overwritten by dynamic format
4073                                 function() {
4074                                         var value = this.value, 
4075                                                 ret;
4076                                         
4077                                         if (dateTimeLabelFormat) { // datetime axis
4078                                                 ret = dateFormat(dateTimeLabelFormat, value);
4079                                                 
4080                                         } else if (tickInterval % 1000000 === 0) { // use M abbreviation
4081                                                 ret = (value / 1000000) +'M';
4082                                                 
4083                                         } else if (tickInterval % 1000 === 0) { // use k abbreviation
4084                                                 ret = (value / 1000) +'k';
4085                                                 
4086                                         } else if (!categories && value >= 1000) { // add thousands separators
4087                                                 ret = numberFormat(value, 0);
4088                                         
4089                                         } else { // strings (categories) and small numbers
4090                                                 ret = value;
4091                                         }
4092                                         return ret;
4093                                 },
4094                                 
4095                         staggerLines = horiz && options.labels.staggerLines,
4096                         reversed = options.reversed,
4097                         tickmarkOffset = (categories && options.tickmarkPlacement === 'between') ? 0.5 : 0;             
4099                 /**
4100                  * The Tick class
4101                  */
4102                 function Tick(pos, minor) {
4103                         var tick = this;
4104                         tick.pos = pos;
4105                         tick.minor = minor;
4106                         tick.isNew = true;                              
4107                         
4108                         if (!minor) {
4109                                 tick.addLabel();
4110                         }
4111                 }
4112                 Tick.prototype = {
4113                         /**
4114                          * Write the tick label
4115                          */
4116                         addLabel: function() {
4117                                 var pos = this.pos,
4118                                         labelOptions = options.labels,
4119                                         str,
4120                                         withLabel = !((pos === min && !pick(options.showFirstLabel, 1)) ||
4121                                                 (pos === max && !pick(options.showLastLabel, 0))),
4122                                         width = (categories && horiz && categories.length && 
4123                                                 !labelOptions.step && !labelOptions.staggerLines &&
4124                                                 !labelOptions.rotation &&
4125                                                 plotWidth / categories.length) ||
4126                                                 (!horiz && plotWidth / 2),
4127                                         css,
4128                                         label = this.label;
4129                                         
4130                                 
4131                                 // get the string
4132                                 str = labelFormatter.call({
4133                                                 isFirst: pos === tickPositions[0],
4134                                                 isLast: pos === tickPositions[tickPositions.length - 1],
4135                                                 dateTimeLabelFormat: dateTimeLabelFormat,
4136                                                 value: (categories && categories[pos] ? categories[pos] : pos)
4137                                         });
4138                                 
4139                                 
4140                                 // prepare CSS
4141                                 css = width && { width: mathMax(1, mathRound(width - 2 * (labelOptions.padding || 10))) +PX };
4142                                 css = extend(css, labelOptions.style);
4143                                 
4144                                 // first call
4145                                 if (label === UNDEFINED) {
4146                                         this.label =  
4147                                                 defined(str) && withLabel && labelOptions.enabled ?
4148                                                         renderer.text(
4149                                                                         str,
4150                                                                         0,
4151                                                                         0
4152                                                                 )
4153                                                                 .attr({
4154                                                                         align: labelOptions.align,
4155                                                                         rotation: labelOptions.rotation
4156                                                                 })
4157                                                                 // without position absolute, IE export sometimes is wrong
4158                                                                 .css(css)
4159                                                                 .add(axisGroup):
4160                                                         null;
4161                                                         
4162                                 // update
4163                                 } else if (label) {
4164                                         label.attr({ text: str })
4165                                                 .css(css);
4166                                 }
4167                         },
4168                         /**
4169                          * Get the offset height or width of the label
4170                          */
4171                         getLabelSize: function() {
4172                                 var label = this.label;
4173                                 return label ? 
4174                                         ((this.labelBBox = label.getBBox()))[horiz ? 'height' : 'width'] :
4175                                         0;
4176                                 },
4177                         /**
4178                          * Put everything in place
4179                          * 
4180                          * @param index {Number}
4181                          * @param old {Boolean} Use old coordinates to prepare an animation into new position
4182                          */
4183                         render: function(index, old) {
4184                                 var tick = this,
4185                                         major = !tick.minor,
4186                                         label = tick.label,
4187                                         pos = tick.pos,
4188                                         labelOptions = options.labels,
4189                                         gridLine = tick.gridLine,
4190                                         gridLineWidth = major ? options.gridLineWidth : options.minorGridLineWidth,
4191                                         gridLineColor = major ? options.gridLineColor : options.minorGridLineColor,
4192                                         dashStyle = major ? 
4193                                                 options.gridLineDashStyle : 
4194                                                 options.minorGridLineDashStyle,
4195                                         gridLinePath,
4196                                         mark = tick.mark,
4197                                         markPath,
4198                                         tickLength = major ? options.tickLength : options.minorTickLength,
4199                                         tickWidth = major ? options.tickWidth : (options.minorTickWidth || 0),
4200                                         tickColor = major ? options.tickColor : options.minorTickColor,
4201                                         tickPosition = major ? options.tickPosition : options.minorTickPosition,
4202                                         step = labelOptions.step,
4203                                         cHeight = (old && oldChartHeight) || chartHeight,
4204                                         attribs,
4205                                         x,
4206                                         y;
4207                                         
4208                                 // get x and y position for ticks and labels
4209                                 x = horiz ? 
4210                                         translate(pos + tickmarkOffset, null, null, old) + transB : 
4211                                         plotLeft + offset + (opposite ? ((old && oldChartWidth) || chartWidth) - marginRight - plotLeft : 0);
4212                                         
4213                                 y = horiz ?
4214                                         cHeight - marginBottom + offset - (opposite ? plotHeight : 0) :
4215                                         cHeight - translate(pos + tickmarkOffset, null, null, old) - transB;
4216                                         
4217                                 // create the grid line
4218                                 if (gridLineWidth) {
4219                                         gridLinePath = getPlotLinePath(pos + tickmarkOffset, gridLineWidth, old);
4220                                         
4221                                         if (gridLine === UNDEFINED) {
4222                                                 attribs = {
4223                                                         stroke: gridLineColor,
4224                                                         'stroke-width': gridLineWidth
4225                                                 };
4226                                                 if (dashStyle) {
4227                                                         attribs.dashstyle = dashStyle;
4228                                                 }
4229                                                 tick.gridLine = gridLine =
4230                                                         gridLineWidth ?
4231                                                                 renderer.path(gridLinePath)
4232                                                                         .attr(attribs).add(gridGroup) :
4233                                                                 null;
4234                                         } 
4235                                         if (gridLine && gridLinePath) {
4236                                                 gridLine.animate({
4237                                                         d: gridLinePath
4238                                                 });
4239                                         }
4240                                 }
4241                                 
4242                                 // create the tick mark
4243                                 if (tickWidth) {
4244                                         
4245                                         // negate the length
4246                                         if (tickPosition === 'inside') {
4247                                                 tickLength = -tickLength;
4248                                         }
4249                                         if (opposite) {
4250                                                 tickLength = -tickLength;
4251                                         }
4252                         
4253                                         markPath = renderer.crispLine([
4254                                                 M, 
4255                                                 x, 
4256                                                 y, 
4257                                                 L, 
4258                                                 x + (horiz ? 0 : -tickLength), 
4259                                                 y + (horiz ? tickLength : 0)
4260                                         ], tickWidth);
4261                                         
4262                                         if (mark) { // updating
4263                                                 mark.animate({
4264                                                         d: markPath
4265                                                 });
4266                                         } else { // first time
4267                                                 tick.mark = renderer.path(
4268                                                         markPath
4269                                                 ).attr({
4270                                                         stroke: tickColor,
4271                                                         'stroke-width': tickWidth
4272                                                 }).add(axisGroup);
4273                                         }
4274                                 }
4275                                 
4276                                 // the label is created on init - now move it into place
4277                                 if (label && !isNaN(x)) {
4278                                         x = x + labelOptions.x - (tickmarkOffset && horiz ? 
4279                                                 tickmarkOffset * transA * (reversed ? -1 : 1) : 0); 
4280                                         y = y + labelOptions.y - (tickmarkOffset && !horiz ? 
4281                                                 tickmarkOffset * transA * (reversed ? 1 : -1) : 0);
4282                                                 
4283                                         // vertically centered
4284                                         if (!defined(labelOptions.y)) {
4285                                                 y += pInt(label.styles.lineHeight) * 0.9 - label.getBBox().height / 2;
4286                                         }
4287                                         
4288                                                 
4289                                         // correct for staggered labels
4290                                         if (staggerLines) {
4291                                                 y += (index / (step || 1) % staggerLines) * 16;
4292                                         }
4293                                         // apply step
4294                                         if (step) {
4295                                                 // show those indices dividable by step 
4296                                                 label[index % step ? 'hide' : 'show']();
4297                                         }
4298                                         
4299                                         label[tick.isNew ? 'attr' : 'animate']({
4300                                                 x: x,
4301                                                 y: y
4302                                         });
4303                                 }
4304                                 
4305                                 tick.isNew = false;
4306                         },
4307                         /**
4308                          * Destructor for the tick prototype
4309                          */
4310                         destroy: function() {
4311                                 var tick = this,
4312                                         n;
4313                                 for (n in tick) {
4314                                         if (tick[n] && tick[n].destroy) {
4315                                                 tick[n].destroy();
4316                                         }
4317                                 }
4318                         }
4319                 };
4320                 
4321                 /**
4322                  * The object wrapper for plot lines and plot bands
4323                  * @param {Object} options
4324                  */
4325                 function PlotLineOrBand(options) {
4326                         var plotLine = this;
4327                         if (options) {
4328                                 plotLine.options = options;
4329                                 plotLine.id = options.id;
4330                         }
4331                         
4332                         //plotLine.render()
4333                         return plotLine;
4334                 }
4335                 
4336                 PlotLineOrBand.prototype = {
4338                 /**
4339                  * Render the plot line or plot band. If it is already existing,
4340                  * move it.
4341                  */
4342                 render: function () {
4343                         var plotLine = this,
4344                                 options = plotLine.options,
4345                                 optionsLabel = options.label,
4346                                 label = plotLine.label,
4347                                 width = options.width,
4348                                 to = options.to,
4349                                 toPath, // bands only
4350                                 from = options.from,
4351                                 dashStyle = options.dashStyle,
4352                                 svgElem = plotLine.svgElem,
4353                                 path = [],
4354                                 addEvent,
4355                                 eventType,
4356                                 xs,
4357                                 ys,
4358                                 x,
4359                                 y,
4360                                 color = options.color,
4361                                 zIndex = options.zIndex,
4362                                 events = options.events,
4363                                 attribs;
4364                         
4365                         // plot line
4366                         if (width) {
4367                                 path = getPlotLinePath(options.value, width);
4368                                 attribs = {
4369                                         stroke: color,
4370                                         'stroke-width': width
4371                                 };
4372                                 if (dashStyle) {
4373                                         attribs.dashstyle = dashStyle;
4374                                 }
4375                         }
4376                         
4377                         // plot band
4378                         else if (defined(from) && defined(to)) {
4379                                 // keep within plot area
4380                                 from = mathMax(from, min);
4381                                 to = mathMin(to, max);
4382                         
4383                                 toPath = getPlotLinePath(to);
4384                                 path = getPlotLinePath(from);
4385                                 if (path && toPath) {
4386                                         path.push(
4387                                                 toPath[4],
4388                                                 toPath[5],
4389                                                 toPath[1],
4390                                                 toPath[2]
4391                                         );
4392                                 } else { // outside the axis area
4393                                         path = null;
4394                                 }
4395                                 attribs = {
4396                                         fill: color
4397                                 };
4398                         } else {
4399                                 return;
4400                         }
4401                         // zIndex 
4402                         if (defined(zIndex)) {
4403                                 attribs.zIndex = zIndex;
4404                         }
4405                         
4406                         // common for lines and bands
4407                         if (svgElem) {
4408                                 if (path) {
4409                                         svgElem.animate({
4410                                                 d: path
4411                                         }, null, svgElem.onGetPath);
4412                                 } else {
4413                                         svgElem.hide();
4414                                         svgElem.onGetPath = function() {
4415                                                 svgElem.show();
4416                                         };
4417                                 }
4418                         } else if (path && path.length) {
4419                                 plotLine.svgElem = svgElem = renderer.path(path)
4420                                         .attr(attribs).add();
4421                                         
4422                                 // events
4423                                 if (events) {
4424                                         addEvent = function(eventType) {
4425                                                 svgElem.on(eventType, function(e) {
4426                                                         events[eventType].apply(plotLine, [e]);
4427                                                 });
4428                                         };
4429                                         for (eventType in events) {
4430                                                 addEvent(eventType);
4431                                         }
4432                                 }
4433                         }
4434                         
4435                         // the plot band/line label
4436                         if (optionsLabel && defined(optionsLabel.text) && path && path.length && plotWidth > 0 && plotHeight > 0) {
4437                                 // apply defaults
4438                                 optionsLabel = merge({
4439                                         align: horiz && toPath && 'center',
4440                                         x: horiz ? !toPath && 4 : 10,
4441                                         verticalAlign : !horiz && toPath && 'middle',
4442                                         y: horiz ? toPath ? 16 : 10 : toPath ? 6 : -4,
4443                                         rotation: horiz && !toPath && 90
4444                                 }, optionsLabel);
4445                                 
4446                                 // add the SVG element
4447                                 if (!label) {
4448                                         plotLine.label = label = renderer.text(
4449                                                         optionsLabel.text,
4450                                                         0,
4451                                                         0
4452                                                 )
4453                                                 .attr({
4454                                                         align: optionsLabel.textAlign || optionsLabel.align,
4455                                                         rotation: optionsLabel.rotation,
4456                                                         zIndex: zIndex
4457                                                 })
4458                                                 .css(optionsLabel.style)
4459                                                 .add();
4460                                 }
4461                                 
4462                                 // get the bounding box and align the label
4463                                 xs = [path[1], path[4], pick(path[6], path[1])];
4464                                 ys = [path[2], path[5], pick(path[7], path[2])];
4465                                 x = mathMin.apply(math, xs);
4466                                 y = mathMin.apply(math, ys);
4467                                 
4468                                 label.align(optionsLabel, false, {
4469                                         x: x,
4470                                         y: y,
4471                                         width: mathMax.apply(math, xs) - x,
4472                                         height: mathMax.apply(math, ys) - y
4473                                 });
4474                                 label.show();
4475                                 
4476                         } else if (label) { // move out of sight
4477                                 label.hide();
4478                         }
4479                         
4480                         // chainable
4481                         return plotLine;
4482                 },
4483                 
4484                 /**
4485                  * Remove the plot line or band
4486                  */
4487                 destroy: function() {
4488                         var obj = this,
4489                                 n;
4490                                 
4491                         for (n in obj) {
4492                                 if (obj[n] && obj[n].destroy) {
4493                                         obj[n].destroy(); // destroy SVG wrappers
4494                                 }
4495                                 delete obj[n];
4496                         }
4497                         // remove it from the lookup
4498                         erase(plotLinesAndBands, obj);
4499                 }
4500                 };
4501                 
4502                 /**
4503                  * The class for stack items
4504                  */
4505                 function StackItem(options, isNegative, x) {
4506                         var stackItem = this;
4507                 
4508                         // Tells if the stack is negative 
4509                         stackItem.isNegative = isNegative;
4510                         
4511                         // Save the options to be able to style the label
4512                         stackItem.options = options;
4513                         
4514                         // Save the x value to be able to position the label later
4515                         stackItem.x = x;
4516                         
4517                         // The align options and text align varies on whether the stack is negative and
4518                         // if the chart is inverted or not.
4519                         // First test the user supplied value, then use the dynamic.
4520                         stackItem.alignOptions = {
4521                                 align: options.align || (inverted ? (isNegative ? 'left' : 'right') : 'center'),
4522                                 verticalAlign: options.verticalAlign || (inverted ? 'middle' : (isNegative ? 'bottom' : 'top')),
4523                                 y: pick(options.y, inverted ? 4 : (isNegative ? 14 : -6)),
4524                                 x: pick(options.x, inverted ? (isNegative ? -6 : 6) : 0)
4525                         };
4526                         
4527                         stackItem.textAlign = options.textAlign || (inverted ? (isNegative ? 'right' : 'left') : 'center');
4528                 }
4529                 
4530                 StackItem.prototype = {
4531                         /**
4532                          * Sets the total of this stack. Should be called when a serie is hidden or shown
4533                          * since that will affect the total of other stacks.
4534                          */
4535                         setTotal: function(total) {
4536                                 this.total = total;
4537                                 this.cum = total;
4538                         },
4540                         /**
4541                          * Renders the stack total label and adds it to the stack label group.
4542                          */
4543                         render: function(group) {
4544                                 var stackItem = this,                                                                   // aliased this
4545                                         str = stackItem.options.formatter.call(stackItem);      // format the text in the label
4547                                 // Change the text to reflect the new total and set visibility to hidden in case the serie is hidden
4548                                 if (stackItem.label) {
4549                                         stackItem.label.attr({text: str, visibility: HIDDEN});
4550                                 // Create new label
4551                                 } else {
4552                                         stackItem.label =
4553                                                 chart.renderer.text(str, 0, 0)                          // dummy positions, actual position updated with setOffset method in columnseries
4554                                                         .css(stackItem.options.style)                   // apply style
4555                                                         .attr({align: stackItem.textAlign,                      // fix the text-anchor
4556                                                                 rotation: stackItem.options.rotation,   // rotation
4557                                                                 visibility: HIDDEN })                                   // hidden until setOffset is called
4558                                                         .add(group);                                                    // add to the labels-group
4559                                 }
4560                         },
4562                         /**
4563                          * Sets the offset that the stack has from the x value and repositions the label.
4564                          */
4565                         setOffset: function(xOffset, xWidth) {
4566                                 var stackItem = this,                                                                           // aliased this
4567                                         neg = stackItem.isNegative,                                                             // special treatment is needed for negative stacks
4568                                         y = axis.translate(stackItem.total),                                    // stack value translated mapped to chart coordinates
4569                                         yZero = axis.translate(0),                                                              // stack origin
4570                                         h = mathAbs(y - yZero),                                                                 // stack height
4571                                         x = chart.xAxis[0].translate(stackItem.x) + xOffset,    // stack x position
4572                                         plotHeight = chart.plotHeight,
4573                                         stackBox = {    // this is the box for the complete stack
4574                                                         x: inverted ? (neg ? y : y - h) : x,
4575                                                         y: inverted ? plotHeight - x - xWidth : (neg ? (plotHeight - y - h) : plotHeight - y),
4576                                                         width: inverted ? h : xWidth,
4577                                                         height: inverted ? xWidth : h
4578                                         };
4579                                 
4580                                 if (stackItem.label) {
4581                                         stackItem.label
4582                                                 .align(stackItem.alignOptions, null, stackBox)  // align the label to the box
4583                                                 .attr({visibility: VISIBLE});                                   // set visibility
4584                                 }
4585                         }
4586                 };
4587                 
4588                 /**
4589                  * Get the minimum and maximum for the series of each axis 
4590                  */
4591                 function getSeriesExtremes() {
4592                         var posStack = [],
4593                                 negStack = [],
4594                                 run;
4595                                 
4596                         // reset dataMin and dataMax in case we're redrawing
4597                         dataMin = dataMax = null;
4598                         
4599                         // get an overview of what series are associated with this axis
4600                         associatedSeries = [];
4601                         
4602                         each(series, function(serie) {
4603                                 run = false;
4604                                 
4605                                 
4606                                 // match this axis against the series' given or implicated axis
4607                                 each(['xAxis', 'yAxis'], function(strAxis) {
4608                                         if (
4609                                                 // the series is a cartesian type, and...
4610                                                 serie.isCartesian &&
4611                                                 // we're in the right x or y dimension, and...
4612                                                 ((strAxis === 'xAxis' && isXAxis) || (strAxis === 'yAxis' && !isXAxis)) && (
4613                                                         // the axis number is given in the options and matches this axis index, or
4614                                                         (serie.options[strAxis] === options.index) || 
4615                                                         // the axis index is not given
4616                                                         (serie.options[strAxis] === UNDEFINED && options.index === 0)
4617                                                 )
4618                                         ) {
4619                                                 serie[strAxis] = axis;
4620                                                 associatedSeries.push(serie);
4621                                                 
4622                                                 // the series is visible, run the min/max detection
4623                                                 run = true;             
4624                                         }
4625                                 });
4626                                 // ignore hidden series if opted 
4627                                 if (!serie.visible && optionsChart.ignoreHiddenSeries) {
4628                                         run = false;
4629                                 }                               
4630                                 
4631                                 if (run) {
4632                                         
4633                                         var stacking,
4634                                                 posPointStack,
4635                                                 negPointStack,
4636                                                 stackKey,
4637                                                 negKey;
4638                 
4639                                         if (!isXAxis) {
4640                                                 stacking = serie.options.stacking;
4641                                                 usePercentage = stacking === 'percent';
4642         
4643                                                 // create a stack for this particular series type
4644                                                 if (stacking) {
4645                                                         stackKey = serie.type + pick(serie.options.stack, '');
4646                                                         negKey = '-'+ stackKey;
4647                                                         serie.stackKey = stackKey; // used in translate
4648                                                                         
4649                                                         posPointStack = posStack[stackKey] || []; // contains the total values for each x
4650                                                         posStack[stackKey] = posPointStack;
4651                                                         
4652                                                         negPointStack = negStack[negKey] || [];
4653                                                         negStack[negKey] = negPointStack;
4654                                                 }
4655                                                 if (usePercentage) {
4656                                                         dataMin = 0;
4657                                                         dataMax = 99;                   
4658                                                 }
4659                                         } 
4660                                         if (serie.isCartesian) { // line, column etc. need axes, pie doesn't
4661                                                 each(serie.data, function(point, i) {
4662                                                         var pointX = point.x,
4663                                                                 pointY = point.y,
4664                                                                 isNegative = pointY < 0, 
4665                                                                 pointStack = isNegative ? negPointStack : posPointStack,
4666                                                                 key = isNegative ? negKey : stackKey,
4667                                                                 totalPos,
4668                                                                 pointLow;
4669                                                         
4670                                                         // initial values
4671                                                         if (dataMin === null) {
4673                                                                 // start out with the first point
4674                                                                 dataMin = dataMax = point[xOrY]; 
4675                                                         }
4676                 
4677                                                         // x axis
4678                                                         if (isXAxis) {
4679                                                                 if (pointX > dataMax) {
4680                                                                         dataMax = pointX;
4681                                                                 } else if (pointX < dataMin) {
4682                                                                         dataMin = pointX;
4683                                                                 }
4684                                                         }
4685                                                         
4686                                                         // y axis
4687                                                         else if (defined(pointY)) {
4688                                                                 if (stacking) {
4689                                                                         pointStack[pointX] = 
4690                                                                                 defined(pointStack[pointX]) ? 
4691                                                                                 pointStack[pointX] + pointY : pointY;
4692                                                                 }
4693                                                                 totalPos = pointStack ? pointStack[pointX] : pointY;
4694                                                                 pointLow = pick(point.low, totalPos);
4695                                                                 if (!usePercentage) {
4696                                                                         if (totalPos > dataMax) {
4697                                                                                 dataMax = totalPos;
4698                                                                         } else if (pointLow < dataMin) {
4699                                                                                 dataMin = pointLow;
4700                                                                         }
4701                                                                 }
4702                                                                 if (stacking) {         
4703                                                                         // add the series
4704                                                                         if (!stacks[key]) {
4705                                                                                 stacks[key] = {};
4706                                                                         }
4707                                                                         
4708                                                                         // If the StackItem is there, just update the values,
4709                                                                         // if not, create one first
4710                                                                         if (!stacks[key][pointX]) {
4711                                                                                 stacks[key][pointX] = new StackItem(options.stackLabels, isNegative, pointX);
4712                                                                         }
4713                                                                         stacks[key][pointX].setTotal(totalPos);
4714                                                                 }
4715                                                         }
4716                                                 });
4717                                                 
4718                                                         
4719                                                 // For column, areas and bars, set the minimum automatically to zero
4720                                                 // and prevent that minPadding is added in setScale
4721                                                 if (/(area|column|bar)/.test(serie.type) && !isXAxis) {
4722                                                         var threshold = 0; // use series.options.threshold?
4723                                                         if (dataMin >= threshold) {
4724                                                                 dataMin = threshold;
4725                                                                 ignoreMinPadding = true;
4726                                                         } else if (dataMax < threshold) {
4727                                                                 dataMax = threshold;
4728                                                                 ignoreMaxPadding = true;
4729                                                         }
4730                                                 }
4731                                         }
4732                                 }
4733                         });
4734                         
4735                 }
4736         
4737                 /**
4738                  * Translate from axis value to pixel position on the chart, or back
4739                  * 
4740                  */
4741                 translate = function(val, backwards, cvsCoord, old, handleLog) {
4742                         var sign = 1,
4743                                 cvsOffset = 0,
4744                                 localA = old ? oldTransA : transA,
4745                                 localMin = old ? oldMin : min,
4746                                 returnValue;
4747                                 
4748                         if (!localA) {
4749                                 localA = transA;
4750                         }
4751                                 
4752                         if (cvsCoord) {
4753                                 sign *= -1; // canvas coordinates inverts the value
4754                                 cvsOffset = axisLength;
4755                         }
4756                         if (reversed) { // reversed axis
4757                                 sign *= -1; 
4758                                 cvsOffset -= sign * axisLength;
4759                         }
4760                         
4761                         if (backwards) { // reverse translation
4762                                 if (reversed) {
4763                                         val = axisLength - val;
4764                                 }
4765                                 returnValue = val / localA + localMin; // from chart pixel to value     
4766                                 if (isLog && handleLog) {
4767                                         returnValue = lin2log(returnValue);
4768                                 }                       
4769                         
4770                         } else { // normal translation
4771                                 if (isLog && handleLog) {
4772                                         val = log2lin(val);
4773                                 }
4774                                 returnValue = sign * (val - localMin) * localA + cvsOffset; // from value to chart pixel
4775                         }
4776                         
4777                         return returnValue;
4778                 };
4779                 
4780                 /**
4781                  * Create the path for a plot line that goes from the given value on 
4782                  * this axis, across the plot to the opposite side
4783                  * @param {Number} value
4784                  * @param {Number} lineWidth Used for calculation crisp line
4785                  * @param {Number] old Use old coordinates (for resizing and rescaling)
4786                  */
4787                 getPlotLinePath = function(value, lineWidth, old) {
4788                         var x1, 
4789                                 y1, 
4790                                 x2, 
4791                                 y2,
4792                                 translatedValue = translate(value, null, null, old),
4793                                 cHeight = (old && oldChartHeight) || chartHeight,
4794                                 cWidth = (old && oldChartWidth) || chartWidth,
4795                                 skip;
4796                                 
4797                         x1 = x2 = mathRound(translatedValue + transB);
4798                         y1 = y2 = mathRound(cHeight - translatedValue - transB);
4799                         
4800                         if (isNaN(translatedValue)) { // no min or max
4801                                 skip = true;
4802                         
4803                         } else if (horiz) { 
4804                                 y1 = plotTop;
4805                                 y2 = cHeight - marginBottom;
4806                                 if (x1 < plotLeft || x1 > plotLeft + plotWidth) {
4807                                         skip = true;
4808                                 }
4809                         } else {
4810                                 x1 = plotLeft;
4811                                 x2 = cWidth - marginRight;
4812                                 if (y1 < plotTop || y1 > plotTop + plotHeight) {
4813                                         skip = true;
4814                                 }
4815                         }
4816                         return skip ? 
4817                                 null : 
4818                                 renderer.crispLine([M, x1, y1, L, x2, y2], lineWidth || 0);
4819                 };
4820                 
4821                 
4822                 /**
4823                  * Take an interval and normalize it to multiples of 1, 2, 2.5 and 5
4824                  * @param {Number} interval
4825                  */
4826                 function normalizeTickInterval(interval, multiples) {
4827                         var normalized, i;
4828                                 
4829                         // round to a tenfold of 1, 2, 2.5 or 5
4830                         magnitude = multiples ? 1 : math.pow(10, mathFloor(math.log(interval) / math.LN10));
4831                         normalized = interval / magnitude;
4832                         
4833                         // multiples for a linear scale
4834                         if (!multiples) {
4835                                 multiples = [1, 2, 2.5, 5, 10];
4836                                 //multiples = [1, 2, 2.5, 4, 5, 7.5, 10];
4837                                 
4838                                 // the allowDecimals option
4839                                 if (options.allowDecimals === false || isLog) {
4840                                         if (magnitude === 1) {
4841                                                 multiples = [1, 2, 5, 10];
4842                                         } else if (magnitude <= 0.1) {
4843                                                 multiples = [1 / magnitude];
4844                                         }                                       
4845                                 }
4846                         }
4847                         
4848                         // normalize the interval to the nearest multiple
4849                         for (i = 0; i < multiples.length; i++) {
4850                                 interval = multiples[i];
4851                                 if (normalized <= (multiples[i] + (multiples[i+1] || multiples[i])) / 2) {
4852                                         break;
4853                                 }
4854                         }
4855                         
4856                         // multiply back to the correct magnitude
4857                         interval *= magnitude;
4858                         
4859                         return interval;
4860                 }
4861         
4862                 /**
4863                  * Set the tick positions to a time unit that makes sense, for example
4864                  * on the first of each month or on every Monday.
4865                  */
4866                 function setDateTimeTickPositions() {
4867                         tickPositions = [];
4868                         var i,
4869                                 useUTC = defaultOptions.global.useUTC,
4870                                 oneSecond = 1000 / timeFactor,
4871                                 oneMinute = 60000 / timeFactor,
4872                                 oneHour = 3600000 / timeFactor,
4873                                 oneDay = 24 * 3600000 / timeFactor,
4874                                 oneWeek = 7 * 24 * 3600000 / timeFactor,
4875                                 oneMonth = 30 * 24 * 3600000 / timeFactor,
4876                                 oneYear = 31556952000 / timeFactor,
4877                         
4878                                 units = [[
4879                                         'second',                                               // unit name
4880                                         oneSecond,                                              // fixed incremental unit
4881                                         [1, 2, 5, 10, 15, 30]                   // allowed multiples
4882                                 ], [
4883                                         'minute',                                               // unit name
4884                                         oneMinute,                                              // fixed incremental unit
4885                                         [1, 2, 5, 10, 15, 30]                   // allowed multiples
4886                                 ], [
4887                                         'hour',                                                 // unit name
4888                                         oneHour,                                                // fixed incremental unit
4889                                         [1, 2, 3, 4, 6, 8, 12]                  // allowed multiples
4890                                 ], [
4891                                         'day',                                                  // unit name
4892                                         oneDay,                                                 // fixed incremental unit
4893                                         [1, 2]                                                  // allowed multiples
4894                                 ], [
4895                                         'week',                                                 // unit name
4896                                         oneWeek,                                                // fixed incremental unit
4897                                         [1, 2]                                                  // allowed multiples
4898                                 ], [
4899                                         'month',
4900                                         oneMonth,
4901                                         [1, 2, 3, 4, 6]
4902                                 ], [
4903                                         'year',
4904                                         oneYear,
4905                                         null
4906                                 ]],
4907                         
4908                                 unit = units[6], // default unit is years
4909                                 interval = unit[1], 
4910                                 multiples = unit[2];
4911                         
4912                         // loop through the units to find the one that best fits the tickInterval
4913                         for (i = 0; i < units.length; i++)  {
4914                                 unit = units[i];
4915                                 interval = unit[1];
4916                                 multiples = unit[2];
4917                                 
4918                                 
4919                                 if (units[i+1]) {
4920                                         // lessThan is in the middle between the highest multiple and the next unit.
4921                                         var lessThan = (interval * multiples[multiples.length - 1] + 
4922                                                                 units[i + 1][1]) / 2;
4923                                                         
4924                                         // break and keep the current unit
4925                                         if (tickInterval <= lessThan) {
4926                                                 break;
4927                                         }
4928                                 }
4929                         }
4930                         
4931                         // prevent 2.5 years intervals, though 25, 250 etc. are allowed
4932                         if (interval === oneYear && tickInterval < 5 * interval) {
4933                                 multiples = [1, 2, 5];
4934                         }
4935         
4936                         // get the minimum value by flooring the date
4937                         var multitude = normalizeTickInterval(tickInterval / interval, multiples),
4938                                 minYear, // used in months and years as a basis for Date.UTC()
4939                                 minDate = new Date(min * timeFactor);
4940                                 
4941                         minDate.setMilliseconds(0);
4942                         
4943                         if (interval >= oneSecond) { // second
4944                                 minDate.setSeconds(interval >= oneMinute ? 0 :
4945                                         multitude * mathFloor(minDate.getSeconds() / multitude));
4946                         }
4947         
4948                         if (interval >= oneMinute) { // minute
4949                                 minDate[setMinutes](interval >= oneHour ? 0 :
4950                                         multitude * mathFloor(minDate[getMinutes]() / multitude));
4951                         }
4952         
4953                         if (interval >= oneHour) { // hour
4954                                 minDate[setHours](interval >= oneDay ? 0 :
4955                                         multitude * mathFloor(minDate[getHours]() / multitude));
4956                         }
4957         
4958                         if (interval >= oneDay) { // day
4959                                 minDate[setDate](interval >= oneMonth ? 1 :
4960                                         multitude * mathFloor(minDate[getDate]() / multitude));
4961                         }
4962                                         
4963                         if (interval >= oneMonth) { // month
4964                                 minDate[setMonth](interval >= oneYear ? 0 :
4965                                         multitude * mathFloor(minDate[getMonth]() / multitude));
4966                                 minYear = minDate[getFullYear]();
4967                         }
4968                         
4969                         if (interval >= oneYear) { // year
4970                                 minYear -= minYear % multitude;
4971                                 minDate[setFullYear](minYear);
4972                         }
4973                         
4974                         // week is a special case that runs outside the hierarchy
4975                         if (interval === oneWeek) {
4976                                 // get start of current week, independent of multitude
4977                                 minDate[setDate](minDate[getDate]() - minDate[getDay]() + 
4978                                         options.startOfWeek);
4979                         }
4980                         
4981                         
4982                         // get tick positions
4983                         i = 1; // prevent crash just in case
4984                         minYear = minDate[getFullYear]();
4985                         var time = minDate.getTime() / timeFactor,
4986                                 minMonth = minDate[getMonth](),
4987                                 minDateDate = minDate[getDate]();
4988                                 
4989                         // iterate and add tick positions at appropriate values
4990                         while (time < max && i < plotWidth) {
4991                                 tickPositions.push(time);
4992                                 
4993                                 // if the interval is years, use Date.UTC to increase years
4994                                 if (interval === oneYear) {
4995                                         time = makeTime(minYear + i * multitude, 0) / timeFactor;
4996                                 
4997                                 // if the interval is months, use Date.UTC to increase months
4998                                 } else if (interval === oneMonth) {
4999                                         time = makeTime(minYear, minMonth + i * multitude) / timeFactor;
5000                                         
5001                                 // if we're using global time, the interval is not fixed as it jumps
5002                                 // one hour at the DST crossover
5003                                 } else if (!useUTC && (interval === oneDay || interval === oneWeek)) {
5004                                         time = makeTime(minYear, minMonth, minDateDate + 
5005                                                 i * multitude * (interval === oneDay ? 1 : 7));
5006                                         
5007                                 // else, the interval is fixed and we use simple addition
5008                                 } else {
5009                                         time += interval * multitude;
5010                                 }
5011                                 
5012                                 i++;
5013                         }
5014                         // push the last time
5015                         tickPositions.push(time);
5016                         
5017                         
5018                         // dynamic label formatter 
5019                         dateTimeLabelFormat = options.dateTimeLabelFormats[unit[0]];
5020                 }
5021                         
5022                 /**
5023                  * Fix JS round off float errors
5024                  * @param {Number} num
5025                  */
5026                 function correctFloat(num) {
5027                         var invMag, ret = num;
5028                         magnitude = pick(magnitude, math.pow(10, mathFloor(math.log(tickInterval) / math.LN10)));
5029                         
5030                         if (magnitude < 1) {
5031                                 invMag = mathRound(1 / magnitude)  * 10;
5032                                 ret = mathRound(num * invMag) / invMag;
5033                         }
5034                         return ret;
5035                 }
5036                                 
5037                 /**
5038                  * Set the tick positions of a linear axis to round values like whole tens or every five.
5039                  */
5040                 function setLinearTickPositions() {
5041                         
5042                         var i,
5043                                 roundedMin = correctFloat(mathFloor(min / tickInterval) * tickInterval),
5044                                 roundedMax = correctFloat(mathCeil(max / tickInterval) * tickInterval);
5045                                 
5046                         tickPositions = [];
5047                         
5048                         // populate the intermediate values
5049                         i = correctFloat(roundedMin);
5050                         while (i <= roundedMax) {
5051                                 tickPositions.push(i);
5052                                 i = correctFloat(i + tickInterval);
5053                         }
5054                         
5055                 }
5056                 
5057                 /**
5058                  * Set the tick positions to round values and optionally extend the extremes
5059                  * to the nearest tick
5060                  */
5061                 function setTickPositions(secondPass) {
5062                         var length,
5063                                 catPad,
5064                                 linkedParent,
5065                                 linkedParentExtremes,
5066                                 tickIntervalOption = options.tickInterval,
5067                                 tickPixelIntervalOption = options.tickPixelInterval,
5068                                 maxZoom = options.maxZoom || (
5069                                         isXAxis && !defined(options.min) && !defined(options.max) ? 
5070                                                 mathMin(chart.smallestInterval * 5, dataMax - dataMin) : 
5071                                                 null                                    
5072                                 ),
5073                                 zoomOffset;
5074                                 
5075                         
5076                         axisLength = horiz ? plotWidth : plotHeight;
5077                         
5078                         // linked axis gets the extremes from the parent axis
5079                         if (isLinked) {
5080                                 linkedParent = chart[isXAxis ? 'xAxis' : 'yAxis'][options.linkedTo];
5081                                 linkedParentExtremes = linkedParent.getExtremes();
5082                                 min = pick(linkedParentExtremes.min, linkedParentExtremes.dataMin);
5083                                 max = pick(linkedParentExtremes.max, linkedParentExtremes.dataMax);
5084                         }
5085                         
5086                         // initial min and max from the extreme data values
5087                         else {
5088                                 min = pick(userMin, options.min, dataMin);
5089                                 max = pick(userMax, options.max, dataMax);
5090                         }
5091                         
5092                         if (isLog) {
5093                                 min = log2lin(min);
5094                                 max = log2lin(max);
5095                         }
5096                         
5097                         // maxZoom exceeded, just center the selection
5098                         if (max - min < maxZoom) { 
5099                                 zoomOffset = (maxZoom - max + min) / 2;
5100                                 // if min and max options have been set, don't go beyond it
5101                                 min = mathMax(min - zoomOffset, pick(options.min, min - zoomOffset), dataMin);
5102                                 max = mathMin(min + maxZoom, pick(options.max, min + maxZoom), dataMax);
5103                         }
5104                                 
5105                         // pad the values to get clear of the chart's edges
5106                         if (!categories && !usePercentage && !isLinked && defined(min) && defined(max)) {
5107                                 length = (max - min) || 1;
5108                                 if (!defined(options.min) && !defined(userMin) && minPadding && (dataMin < 0 || !ignoreMinPadding)) { 
5109                                         min -= length * minPadding; 
5110                                 }
5111                                 if (!defined(options.max) && !defined(userMax)  && maxPadding && (dataMax > 0 || !ignoreMaxPadding)) { 
5112                                         max += length * maxPadding;
5113                                 }
5114                         }
5116                         // get tickInterval
5117                         if (min === max) {
5118                                 tickInterval = 1;
5119                         } else if (isLinked && !tickIntervalOption &&
5120                                         tickPixelIntervalOption === linkedParent.options.tickPixelInterval) {
5121                                 tickInterval = linkedParent.tickInterval;
5122                         } else {
5123                                 tickInterval = pick(
5124                                         tickIntervalOption,
5125                                         categories ? // for categoried axis, 1 is default, for linear axis use tickPix 
5126                                                 1 : 
5127                                                 (max - min) * tickPixelIntervalOption / axisLength
5128                                 );
5129                         }
5130                         
5131                         if (!isDatetimeAxis && !defined(options.tickInterval)) { // linear
5132                                 tickInterval = normalizeTickInterval(tickInterval);
5133                         }
5134                         axis.tickInterval = tickInterval; // record for linked axis
5135                         
5136                         // get minorTickInterval
5137                         minorTickInterval = options.minorTickInterval === 'auto' && tickInterval ?
5138                                         tickInterval / 5 : options.minorTickInterval;
5139                         
5140                         // find the tick positions
5141                         if (isDatetimeAxis)     {
5142                                 setDateTimeTickPositions();
5143                         } else {
5144                                 setLinearTickPositions();
5145                         }
5146                         
5147                         if (!isLinked) {
5148                                 // pad categorised axis to nearest half unit
5149                                 if (categories || (isXAxis && chart.hasColumn)) {
5150                                         catPad = (categories ? 1 : tickInterval) * 0.5;
5151                                         if (categories || !defined(pick(options.min, userMin))) {
5152                                                 min -= catPad;
5153                                         }
5154                                         if (categories || !defined(pick(options.max, userMax))) {
5155                                                 max += catPad;
5156                                         }
5157                                 }
5158                                 
5159                                 // reset min/max or remove extremes based on start/end on tick
5160                                 var roundedMin = tickPositions[0],
5161                                         roundedMax = tickPositions[tickPositions.length - 1];
5162                                 
5163                                 if (options.startOnTick) {
5164                                         min = roundedMin;
5165                                 } else if (min > roundedMin) {
5166                                         tickPositions.shift();
5167                                 }
5168                                 
5169                                 if (options.endOnTick) {
5170                                         max = roundedMax;
5171                                 } else if (max < roundedMax) {
5172                                         tickPositions.pop();
5173                                 }
5174                         
5175                                 // record the greatest number of ticks for multi axis
5176                                 if (!maxTicks) { // first call, or maxTicks have been reset after a zoom operation
5177                                         maxTicks = {
5178                                                 x: 0,
5179                                                 y: 0
5180                                         };
5181                                 }
5182                                 
5183                                 if (!isDatetimeAxis && tickPositions.length > maxTicks[xOrY]) {
5184                                         maxTicks[xOrY] = tickPositions.length;
5185                                 }
5186                         }
5187                         
5188                         
5189                 }
5190                 
5191                 /**
5192                  * When using multiple axes, adjust the number of ticks to match the highest
5193                  * number of ticks in that group
5194                  */ 
5195                 function adjustTickAmount() {
5196                                         
5197                         if (maxTicks && !isDatetimeAxis && !categories && !isLinked) { // only apply to linear scale
5198                                 var oldTickAmount = tickAmount,
5199                                         calculatedTickAmount = tickPositions.length;
5200                                         
5201                                 // set the axis-level tickAmount to use below
5202                                 tickAmount = maxTicks[xOrY];
5203                                 
5204                                 if (calculatedTickAmount < tickAmount) {
5205                                         while (tickPositions.length < tickAmount) {
5206                                                 tickPositions.push( correctFloat(
5207                                                         tickPositions[tickPositions.length - 1] + tickInterval
5208                                                 ));
5209                                         }
5210                                         transA *= (calculatedTickAmount - 1) / (tickAmount - 1);
5211                                         max = tickPositions[tickPositions.length - 1];
5212                                 
5213                                 }
5214                                 if (defined(oldTickAmount) && tickAmount !== oldTickAmount) {
5215                                         axis.isDirty = true;    
5216                                 }
5217                         }
5218                         
5219                 }
5220         
5221                 /**
5222                  * Set the scale based on data min and max, user set min and max or options
5223                  * 
5224                  */
5225                 function setScale() {
5226                         var type, 
5227                                 i;
5228                                 
5229                         oldMin = min;
5230                         oldMax = max;
5231                                 
5232                         // get data extremes if needed
5233                         getSeriesExtremes();
5234                                         
5235                         // get fixed positions based on tickInterval
5236                         setTickPositions();
5237                         
5238                         // the translation factor used in translate function
5239                         oldTransA = transA;
5240                         transA = axisLength / ((max - min) || 1);
5241                                                         
5242                         // reset stacks
5243                         if (!isXAxis) {
5244                                 for (type in stacks) {
5245                                         for (i in stacks[type]) {
5246                                                 stacks[type][i].cum = stacks[type][i].total;
5247                                         }
5248                                 }
5249                         }
5251                         // mark as dirty if it is not already set to dirty and extremes have changed
5252                         if (!axis.isDirty) {
5253                                 axis.isDirty = (min !== oldMin || max !== oldMax);
5254                         }
5255                         
5256                 }
5257                 
5258                 /**
5259                  * Set the extremes and optionally redraw
5260                  * @param {Number} newMin
5261                  * @param {Number} newMax
5262                  * @param {Boolean} redraw
5263                  * @param {Boolean|Object} animation Whether to apply animation, and optionally animation
5264                  *    configuration
5265                  * 
5266                  */
5267                 function setExtremes(newMin, newMax, redraw, animation) {
5268                         
5269                         redraw = pick(redraw, true); // defaults to true
5270                                 
5271                         fireEvent(axis, 'setExtremes', { // fire an event to enable syncing of multiple charts
5272                                 min: newMin,
5273                                 max: newMax
5274                         }, function() { // the default event handler
5275                                 
5276                                 userMin = newMin;
5277                                 userMax = newMax;
5278                         
5279                                 
5280                                 // redraw
5281                                 if (redraw) {
5282                                         chart.redraw(animation);
5283                                 }
5284                         });
5285                         
5286                 }
5287                 
5288                 /**
5289                  * Get the actual axis extremes
5290                  */
5291                 function getExtremes() {
5292                         return {
5293                                 min: min,
5294                                 max: max,
5295                                 dataMin: dataMin,
5296                                 dataMax: dataMax,
5297                                 userMin: userMin,
5298                                 userMax: userMax
5299                         };
5300                 }
5301                 
5302                 /**
5303                  * Get the zero plane either based on zero or on the min or max value.
5304                  * Used in bar and area plots
5305                  */
5306                 function getThreshold(threshold) {
5307                         if (min > threshold) {
5308                                 threshold = min;
5309                         } else if (max < threshold) {
5310                                 threshold = max;
5311                         }
5312                         
5313                         return translate(threshold, 0, 1);
5314                 }
5315                 
5316                 /**
5317                  * Add a plot band or plot line after render time
5318                  * 
5319                  * @param options {Object} The plotBand or plotLine configuration object
5320                  */
5321                 function addPlotBandOrLine(options) {
5322                         var obj = new PlotLineOrBand(options).render();
5323                         plotLinesAndBands.push(obj);
5324                         return obj;
5325                 }
5326                 
5327                 /**
5328                  * Render the tick labels to a preliminary position to get their sizes
5329                  */
5330                 function getOffset() {
5331                         
5332                         var hasData = associatedSeries.length && defined(min) && defined(max),
5333                                 titleOffset = 0,
5334                                 titleMargin = 0,
5335                                 axisTitleOptions = options.title,
5336                                 labelOptions = options.labels,
5337                                 directionFactor = [-1, 1, 1, -1][side],
5338                                 n;
5339                         
5340                         if (!axisGroup) {
5341                                 axisGroup = renderer.g('axis')
5342                                         .attr({ zIndex: 7 })
5343                                         .add();
5344                                 gridGroup = renderer.g('grid')
5345                                         .attr({ zIndex: 1 })
5346                                         .add();
5347                         }
5348                         
5349                         labelOffset = 0; // reset
5350                         
5351                         if (hasData || isLinked) {
5352                                 each(tickPositions, function(pos) {
5353                                         if (!ticks[pos]) {
5354                                                 ticks[pos] = new Tick(pos);
5355                                         } else {
5356                                                 ticks[pos].addLabel(); // update labels depending on tick interval
5357                                         }
5358                                         
5359                                         // left side must be align: right and right side must have align: left for labels
5360                                         if (side === 0 || side === 2 || { 1: 'left', 3: 'right' }[side] === labelOptions.align) {
5361                                         
5362                                                 // get the highest offset
5363                                                 labelOffset = mathMax(
5364                                                         ticks[pos].getLabelSize(),
5365                                                         labelOffset
5366                                                 );
5367                                         }
5368                         
5369                                 });
5370                                 
5371                                 if (staggerLines) {
5372                                         labelOffset += (staggerLines - 1) * 16;
5373                                 }
5374                         
5375                         } else { // doesn't have data
5376                                 for (n in ticks) {
5377                                         ticks[n].destroy();
5378                                         delete ticks[n];
5379                                 }
5380                         }
5381                         
5382                         if (axisTitleOptions && axisTitleOptions.text) {
5383                                 if (!axis.axisTitle) {
5384                                         axis.axisTitle = renderer.text(
5385                                                 axisTitleOptions.text,
5386                                                 0,
5387                                                 0
5388                                         )
5389                                         .attr({ 
5390                                                 zIndex: 7,
5391                                                 rotation: axisTitleOptions.rotation || 0,
5392                                                 align: 
5393                                                         axisTitleOptions.textAlign || 
5394                                                         { low: 'left', middle: 'center', high: 'right' }[axisTitleOptions.align]
5395                                         })
5396                                         .css(axisTitleOptions.style)
5397                                         .add();
5398                                 }
5399                                 
5400                                 titleOffset = axis.axisTitle.getBBox()[horiz ? 'height' : 'width'];
5401                                 titleMargin = pick(axisTitleOptions.margin, horiz ? 5 : 10);
5402                                 
5403                         }
5404                         
5405                         // handle automatic or user set offset
5406                         offset = directionFactor * (options.offset || axisOffset[side]);
5407                         
5408                         axisTitleMargin = 
5409                                 labelOffset +
5410                                 (side !== 2 && labelOffset && directionFactor * options.labels[horiz ? 'y' : 'x']) + 
5411                                 titleMargin;
5412                         
5413                         axisOffset[side] = mathMax(
5414                                 axisOffset[side], 
5415                                 axisTitleMargin + titleOffset + directionFactor * offset
5416                         );
5417                         
5418                 }
5419                 
5420                 /**
5421                  * Render the axis
5422                  */
5423                 function render() {
5424                         var axisTitleOptions = options.title,
5425                                 stackLabelOptions = options.stackLabels,
5426                                 alternateGridColor = options.alternateGridColor,
5427                                 lineWidth = options.lineWidth,
5428                                 lineLeft,
5429                                 lineTop,
5430                                 linePath,
5431                                 hasRendered = chart.hasRendered,
5432                                 slideInTicks = hasRendered && defined(oldMin) && !isNaN(oldMin),
5433                                 hasData = associatedSeries.length && defined(min) && defined(max);
5434                         
5435                         // update metrics
5436                         axisLength = horiz ? plotWidth : plotHeight;
5437                         transA = axisLength / ((max - min) || 1);
5438                         transB = horiz ? plotLeft : marginBottom; // translation addend
5439                         
5440                         // If the series has data draw the ticks. Else only the line and title
5441                         if (hasData || isLinked) {
5442                                 
5443                                 // minor ticks
5444                                 if (minorTickInterval && !categories) {
5445                                         var pos = min + (tickPositions[0] - min) % minorTickInterval;
5446                                         for (pos; pos <= max; pos += minorTickInterval) {
5447                                                 if (!minorTicks[pos]) {
5448                                                         minorTicks[pos] = new Tick(pos, true);
5449                                                 }
5450                                                 
5451                                                 // render new ticks in old position
5452                                                 if (slideInTicks && minorTicks[pos].isNew) {
5453                                                         minorTicks[pos].render(null, true);
5454                                                 }
5455                                         
5456                                                 
5457                                                 minorTicks[pos].isActive = true;
5458                                                 minorTicks[pos].render();
5459                                         }
5460                                 }
5461                                 
5462                                 // major ticks
5463                                 each(tickPositions, function(pos, i) {
5464                                         // linked axes need an extra check to find out if 
5465                                         if (!isLinked || (pos >= min && pos <= max)) {
5466                                                 
5467                                                 // render new ticks in old position
5468                                                 if (slideInTicks && ticks[pos].isNew) {
5469                                                         ticks[pos].render(i, true);
5470                                                 }
5471                                                 
5472                                                 ticks[pos].isActive = true;
5473                                                 ticks[pos].render(i);
5474                                         }
5475                                 });
5476                                 
5477                                 // alternate grid color
5478                                 if (alternateGridColor) {
5479                                         each(tickPositions, function(pos, i) {
5480                                                 if (i % 2 === 0 && pos < max) {
5481                                                         /*plotLinesAndBands.push(new PlotLineOrBand({
5482                                                                 from: pos,
5483                                                                 to: tickPositions[i + 1] !== UNDEFINED ? tickPositions[i + 1] : max,
5484                                                                 color: alternateGridColor 
5485                                                         }));*/
5486                                                         
5487                                                         if (!alternateBands[pos]) {
5488                                                                 alternateBands[pos] = new PlotLineOrBand();
5489                                                         }
5490                                                         alternateBands[pos].options = {
5491                                                                 from: pos,
5492                                                                 to: tickPositions[i + 1] !== UNDEFINED ? tickPositions[i + 1] : max,
5493                                                                 color: alternateGridColor 
5494                                                         };
5495                                                         alternateBands[pos].render();
5496                                                         alternateBands[pos].isActive = true;
5497                                                 }
5498                                         });
5499                                 }
5500                                 
5501                                 // custom plot bands (behind grid lines)
5502                                 /*if (!hasRendered) { // only first time
5503                                         each(options.plotBands || [], function(plotBandOptions) {
5504                                                 plotLinesAndBands.push(new PlotLineOrBand(
5505                                                         extend({ zIndex: 1 }, plotBandOptions)
5506                                                 ).render());
5507                                         });
5508                                 }*/
5509                                 
5510                                 
5511                                 
5512                                 
5513                                 // custom plot lines and bands
5514                                 if (!hasRendered) { // only first time
5515                                         each((options.plotLines || []).concat(options.plotBands || []), function(plotLineOptions) {
5516                                                 plotLinesAndBands.push(new PlotLineOrBand(plotLineOptions).render());
5517                                         });
5518                                 }
5519                                 
5520                                 
5521                         
5522                         } // end if hasData
5523                         
5524                         // remove inactive ticks
5525                         each([ticks, minorTicks, alternateBands], function(coll) {
5526                                 var pos;
5527                                 for (pos in coll) {
5528                                         if (!coll[pos].isActive) {
5529                                                 coll[pos].destroy();
5530                                                 delete coll[pos];
5531                                         } else {
5532                                                 coll[pos].isActive = false; // reset
5533                                         }
5534                                 }
5535                         });
5536                                 
5537                                 
5538                         
5539                         
5540                         // Static items. As the axis group is cleared on subsequent calls
5541                         // to render, these items are added outside the group.  
5542                         // axis line
5543                         if (lineWidth) {
5544                                 lineLeft = plotLeft + (opposite ? plotWidth : 0) + offset;
5545                                 lineTop = chartHeight - marginBottom - (opposite ? plotHeight : 0) + offset;
5546                                 
5547                                 linePath = renderer.crispLine([
5548                                                 M,
5549                                                 horiz ? 
5550                                                         plotLeft: 
5551                                                         lineLeft,
5552                                                 horiz ? 
5553                                                         lineTop: 
5554                                                         plotTop,
5555                                                 L, 
5556                                                 horiz ? 
5557                                                         chartWidth - marginRight : 
5558                                                         lineLeft,
5559                                                 horiz ? 
5560                                                         lineTop:
5561                                                         chartHeight - marginBottom
5562                                         ], lineWidth);
5563                                 if (!axisLine) {
5564                                         axisLine = renderer.path(linePath)
5565                                                 .attr({ 
5566                                                         stroke: options.lineColor, 
5567                                                         'stroke-width': lineWidth,
5568                                                         zIndex: 7
5569                                                 })
5570                                                 .add();
5571                                 } else {
5572                                         axisLine.animate({ d: linePath });
5573                                 }
5574                                         
5575                         }
5576                         
5577                         if (axis.axisTitle) {
5578                                 // compute anchor points for each of the title align options
5579                                 var margin = horiz ? plotLeft : plotTop,
5580                                         fontSize = pInt(axisTitleOptions.style.fontSize || 12),
5581                                 // the position in the length direction of the axis
5582                                 alongAxis = { 
5583                                         low: margin + (horiz ? 0 : axisLength), 
5584                                         middle: margin + axisLength / 2, 
5585                                         high: margin + (horiz ? axisLength : 0)
5586                                 }[axisTitleOptions.align],
5587                                 
5588                                 // the position in the perpendicular direction of the axis
5589                                 offAxis = (horiz ? plotTop + plotHeight : plotLeft) +
5590                                         (horiz ? 1 : -1) * // horizontal axis reverses the margin
5591                                         (opposite ? -1 : 1) * // so does opposite axes
5592                                         axisTitleMargin +
5593                                         //(isIE ? fontSize / 3 : 0)+ // preliminary fix for vml's centerline
5594                                         (side === 2 ? fontSize : 0);
5595                                 
5596                                 axis.axisTitle[hasRendered ? 'animate' : 'attr']({
5597                                         x: horiz ? 
5598                                                 alongAxis: 
5599                                                 offAxis + (opposite ? plotWidth : 0) + offset +
5600                                                         (axisTitleOptions.x || 0), // x
5601                                         y: horiz ? 
5602                                                 offAxis - (opposite ? plotHeight : 0) + offset: 
5603                                                 alongAxis + (axisTitleOptions.y || 0) // y
5604                                 });
5605                                 
5606                         }
5607                         
5608                         // Stacked totals:
5609                         if (stackLabelOptions && stackLabelOptions.enabled) {
5610                                 var stackKey, oneStack, stackCategory,
5611                                         stackTotalGroup = axis.stackTotalGroup;
5613                                 // Create a separate group for the stack total labels
5614                                 if (!stackTotalGroup) {
5615                                         axis.stackTotalGroup = stackTotalGroup =
5616                                                 renderer.g('stack-labels')
5617                                                         .attr({ 
5618                                                                 visibility: VISIBLE,
5619                                                                 zIndex: 6
5620                                                         })
5621                                                         .translate(plotLeft, plotTop)
5622                                                         .add();
5623                                 }
5625                                 // Render each stack total
5626                                 for (stackKey in stacks) {
5627                                         oneStack = stacks[stackKey];
5628                                         for (stackCategory in oneStack) {
5629                                                 oneStack[stackCategory].render(stackTotalGroup);
5630                                         }
5631                                 }
5632                         }
5633                         // End stacked totals
5634                         
5635                         axis.isDirty = false;
5636                 }
5637                 
5638                 /**
5639                  * Remove a plot band or plot line from the chart by id
5640                  * @param {Object} id
5641                  */
5642                 function removePlotBandOrLine(id) {
5643                         var i = plotLinesAndBands.length;
5644                         while (i--) {
5645                                 if (plotLinesAndBands[i].id === id) {
5646                                         plotLinesAndBands[i].destroy();
5647                                 }
5648                         }
5649                 }
5650                 
5651                 /**
5652                  * Redraw the axis to reflect changes in the data or axis extremes
5653                  */
5654                 function redraw() {
5655                         
5656                         // hide tooltip and hover states
5657                         if (tracker.resetTracker) {
5658                                 tracker.resetTracker();
5659                         }
5660                 
5661                         // render the axis
5662                         render();                       
5663                         
5664                         // move plot lines and bands
5665                         each(plotLinesAndBands, function(plotLine) {
5666                                 plotLine.render();
5667                         });
5668                         
5669                         // mark associated series as dirty and ready for redraw
5670                         each(associatedSeries, function(series) {
5671                                 series.isDirty = true;
5672                         });
5673                                                 
5674                 }
5675                 
5676                 /**
5677                  * Set new axis categories and optionally redraw
5678                  * @param {Array} newCategories
5679                  * @param {Boolean} doRedraw
5680                  */
5681                 function setCategories(newCategories, doRedraw) {
5682                                 // set the categories
5683                                 axis.categories = categories = newCategories;
5684                                 
5685                                 // force reindexing tooltips
5686                                 each(associatedSeries, function(series) {
5687                                         series.translate();
5688                                         series.setTooltipPoints(true);
5689                                 });
5690                                 
5691                                 
5692                                 // optionally redraw
5693                                 axis.isDirty = true;
5694                                 
5695                                 if (pick(doRedraw, true)) {
5696                                         chart.redraw();
5697                                 }
5698                 }
5699                 
5700                 
5701                 
5702                 // Run Axis
5703                         
5704                 // inverted charts have reversed xAxes as default
5705                 if (inverted && isXAxis && reversed === UNDEFINED) {
5706                         reversed = true;
5707                 }
5708                 
5709                         
5710                 // expose some variables
5711                 extend(axis, {
5712                         addPlotBand: addPlotBandOrLine,
5713                         addPlotLine: addPlotBandOrLine,
5714                         adjustTickAmount: adjustTickAmount,
5715                         categories: categories,
5716                         getExtremes: getExtremes,
5717                         getPlotLinePath: getPlotLinePath,
5718                         getThreshold: getThreshold,
5719                         isXAxis: isXAxis,
5720                         options: options,
5721                         plotLinesAndBands: plotLinesAndBands,
5722                         getOffset: getOffset,
5723                         render: render,
5724                         setCategories: setCategories,
5725                         setExtremes: setExtremes,
5726                         setScale: setScale,
5727                         setTickPositions: setTickPositions,
5728                         translate: translate,
5729                         redraw: redraw,
5730                         removePlotBand: removePlotBandOrLine,
5731                         removePlotLine: removePlotBandOrLine,
5732                         reversed: reversed,
5733                         stacks: stacks
5734                 });
5735                 
5736                 // register event listeners
5737                 for (eventType in events) {
5738                         addEvent(axis, eventType, events[eventType]);
5739                 }
5740                 
5741                 // set min and max
5742                 setScale();
5743         
5744         } // end Axis
5745         
5746         
5747         /**
5748          * The toolbar object
5749          * 
5750          * @param {Object} chart 
5751          */
5752         function Toolbar(chart) {
5753                 var buttons = {};
5754                 
5755                 function add(id, text, title, fn) {
5756                         if (!buttons[id]) {
5757                                 var button = renderer.text(
5758                                         text,
5759                                         0,
5760                                         0
5761                                 )
5762                                 .css(options.toolbar.itemStyle)
5763                                 .align({
5764                                         align: 'right',
5765                                         x: - marginRight - 20,
5766                                         y: plotTop + 30
5767                                 })
5768                                 .on('click', fn)
5769                                 /*.on('touchstart', function(e) {
5770                                         e.stopPropagation(); // don't fire the container event
5771                                         fn();
5772                                 })*/
5773                                 .attr({
5774                                         align: 'right', 
5775                                         zIndex: 20
5776                                 })
5777                                 .add();
5778                                 buttons[id] = button;
5779                         }
5780                 }
5781                 function remove(id) {
5782                         discardElement(buttons[id].element);
5783                         buttons[id] = null;
5784                 }
5785                 
5786                 // public
5787                 return {
5788                         add: add,
5789                         remove: remove
5790                 };
5791         }
5792         
5793         /**
5794          * The tooltip object
5795          * @param {Object} options Tooltip options
5796          */
5797         function Tooltip (options) {
5798                 var currentSeries,
5799                         borderWidth = options.borderWidth,
5800                         crosshairsOptions = options.crosshairs,
5801                         crosshairs = [],
5802                         style = options.style,
5803                         shared = options.shared,
5804                         padding = pInt(style.padding),
5805                         boxOffLeft = borderWidth + padding, // off left/top position as IE can't 
5806                                 //properly handle negative positioned shapes
5807                         tooltipIsHidden = true,
5808                         boxWidth,
5809                         boxHeight,
5810                         currentX = 0,                   
5811                         currentY = 0;
5812                         
5813                 // remove padding CSS and apply padding on box instead
5814                 style.padding = 0;
5815                 
5816                 // create the elements
5817                 var group = renderer.g('tooltip')
5818                         .attr({ zIndex: 8 })
5819                         .add(),
5820                         
5821                         box = renderer.rect(boxOffLeft, boxOffLeft, 0, 0, options.borderRadius, borderWidth)
5822                                 .attr({
5823                                         fill: options.backgroundColor,
5824                                         'stroke-width': borderWidth
5825                                 })
5826                                 .add(group)
5827                                 .shadow(options.shadow),
5828                         label = renderer.text('', padding + boxOffLeft, pInt(style.fontSize) + padding + boxOffLeft)
5829                                 .attr({ zIndex: 1 })
5830                                 .css(style)
5831                                 .add(group);
5832                                 
5833                 group.hide();
5834                                 
5835                 /**
5836                  * In case no user defined formatter is given, this will be used
5837                  */
5838                 function defaultFormatter() {
5839                         var pThis = this,
5840                                 items = pThis.points || splat(pThis),
5841                                 xAxis = items[0].series.xAxis,                          
5842                                 x = pThis.x,
5843                                 isDateTime = xAxis && xAxis.options.type === 'datetime',
5844                                 useHeader = isString(x) || isDateTime,
5845                                 series,
5846                                 s;
5847                         
5848                         // build the header     
5849                         s = useHeader ? 
5850                                 ['<span style="font-size: 10px">' +
5851                                 (isDateTime ? dateFormat('%A, %b %e, %Y', x) :  x) +
5852                                 '</span>'] : [];
5853                                                 
5854                         // build the values
5855                         each(items, function(item) {
5856                                 s.push(item.point.tooltipFormatter(useHeader));
5857                         });
5858                         return s.join('<br/>');
5859                 }
5860                 
5861                 /**
5862                  * Provide a soft movement for the tooltip
5863                  * 
5864                  * @param {Number} finalX
5865                  * @param {Number} finalY 
5866                  */
5867                 function move(finalX, finalY) {
5869                         currentX = tooltipIsHidden ? finalX : (2 * currentX + finalX) / 3;
5870                         currentY = tooltipIsHidden ? finalY : (currentY + finalY) / 2;
5871                         
5872                         group.translate(currentX, currentY);
5873                         
5874                         
5875                         // run on next tick of the mouse tracker
5876                         if (mathAbs(finalX - currentX) > 1 || mathAbs(finalY - currentY) > 1) {
5877                                 tooltipTick = function() {
5878                                         move(finalX, finalY);
5879                                 };
5880                         } else {
5881                                 tooltipTick = null;
5882                         }
5883                 }
5884                 
5885                 /**
5886                  * Hide the tooltip
5887                  */
5888                 function hide() {
5889                         if (!tooltipIsHidden) {
5890                                 var hoverPoints = chart.hoverPoints;
5891                                 
5892                                 group.hide();
5893                         
5894                                 each(crosshairs, function(crosshair) {
5895                                         if (crosshair) {
5896                                                 crosshair.hide();
5897                                         }
5898                                 });
5899                         
5900                                 // hide previous hoverPoints and set new
5901                                 if (hoverPoints) {
5902                                         each(hoverPoints, function(point) {
5903                                                 point.setState();
5904                                         });
5905                                 }
5906                                 chart.hoverPoints = null;                                       
5907                                 
5908                                 
5909                                 tooltipIsHidden = true;
5910                         }
5911                                         
5912                 }
5913                 
5914                 /**
5915                  * Refresh the tooltip's text and position. 
5916                  * @param {Object} point
5917                  * 
5918                  */
5919                 function refresh(point) {
5920                         var x,
5921                                 y,
5922                                 boxX,
5923                                 boxY,
5924                                 show,
5925                                 bBox,
5926                                 plotX,
5927                                 plotY = 0,
5928                                 textConfig = {},
5929                                 text,
5930                                 pointConfig = [],
5931                                 tooltipPos = point.tooltipPos,
5932                                 formatter = options.formatter || defaultFormatter,
5933                                 hoverPoints = chart.hoverPoints;
5934                                 
5935                         // shared tooltip, array is sent over
5936                         if (shared) {
5937                                 
5938                                 // hide previous hoverPoints and set new
5939                                 if (hoverPoints) {
5940                                         each(hoverPoints, function(point) {
5941                                                 point.setState();
5942                                         });
5943                                 }
5944                                 chart.hoverPoints = point;
5945                                  
5946                                 each(point, function(item, i) {
5947                                         /*var series = item.series,
5948                                                 hoverPoint = series.hoverPoint;
5949                                         if (hoverPoint) {
5950                                                 hoverPoint.setState();
5951                                         }
5952                                         series.hoverPoint = item;*/
5953                                         item.setState(HOVER_STATE);
5954                                         plotY += item.plotY; // for average
5955                                         
5956                                         pointConfig.push(item.getLabelConfig());
5957                                 });
5958                                 
5959                                 plotX = point[0].plotX;
5960                                 plotY = mathRound(plotY) / point.length; // mathRound because Opera 10 has problems here
5961                                 
5962                                 textConfig = {
5963                                         x: point[0].category
5964                                 };
5965                                 textConfig.points = pointConfig;
5966                                 point = point[0];
5967                                 
5968                         // single point tooltip
5969                         } else {
5970                                 textConfig = point.getLabelConfig();
5971                         }
5972                         text = formatter.call(textConfig);
5973                         
5974                         // register the current series
5975                         currentSeries = point.series;
5976                         
5977                         // get the reference point coordinates (pie charts use tooltipPos)
5978                         plotX = shared ? plotX : point.plotX;
5979                         plotY = shared ? plotY : point.plotY;
5980                         x = mathRound(tooltipPos ? tooltipPos[0] : (inverted ? plotWidth - plotY : plotX));
5981                         y = mathRound(tooltipPos ? tooltipPos[1] : (inverted ? plotHeight - plotX : plotY));
5982                                 
5983                                 
5984                         // hide tooltip if the point falls outside the plot
5985                         show = shared || !point.series.isCartesian || isInsidePlot(x, y);
5986                         
5987                         // update the inner HTML
5988                         if (text === false || !show) { 
5989                                 hide();
5990                         } else {
5991                                 
5992                             // show it
5993                                 if (tooltipIsHidden) {
5994                                         group.show();
5995                                         tooltipIsHidden = false;
5996                                 }
5997                                 
5998                                 // update text
5999                                 label.attr({
6000                                         text: text
6001                                 });
6002                                 
6003                                 // get the bounding box
6004                                 bBox = label.getBBox();
6005                                 boxWidth = bBox.width + 2 * padding;
6006                                 boxHeight = bBox.height + 2 * padding;
6008                                 // set the size of the box
6009                                 box.attr({
6010                                         width: boxWidth,
6011                                         height: boxHeight,
6012                                         stroke: options.borderColor || point.color || currentSeries.color || '#606060'
6013                                 });
6014                                 
6015                                 // keep the box within the chart area
6016                                 boxX = x - boxWidth + plotLeft - 25;
6017                                 boxY = y - boxHeight + plotTop + 10;
6018                                 
6019                                 // it is too far to the left, adjust it
6020                                 if (boxX < 7) {
6021                                         boxX = 7;
6022                                         boxY -= 30;
6023                                 }
6024                                 
6025                                 
6026                                 if (boxY < 5) {
6027                                         boxY = 5; // above
6028                                 } else if (boxY + boxHeight > chartHeight) { 
6029                                         boxY = chartHeight - boxHeight - 5; // below
6030                                 }
6031                                 
6032                                 // do the move
6033                                 move(mathRound(boxX - boxOffLeft), mathRound(boxY - boxOffLeft));
6034                                 
6035                                 
6036                         }
6037                         
6038                         
6039                         // crosshairs
6040                         if (crosshairsOptions) {
6041                                 crosshairsOptions = splat(crosshairsOptions); // [x, y]
6042                                 
6043                                 var path, 
6044                                         i = crosshairsOptions.length,
6045                                         attribs,
6046                                         axis;
6047                                 
6048                                 while (i--) {
6049                                         axis = point.series[i ? 'yAxis' : 'xAxis'];
6050                                         if (crosshairsOptions[i] && axis) {
6051                                                 path = axis
6052                                                         .getPlotLinePath(point[i ? 'y' : 'x'], 1);
6053                                                 if (crosshairs[i]) {
6054                                                         crosshairs[i].attr({ d: path, visibility: VISIBLE });
6055                                                 
6056                                                 } else {
6057                                                         attribs = {
6058                                                                 'stroke-width': crosshairsOptions[i].width || 1,
6059                                                                 stroke: crosshairsOptions[i].color || '#C0C0C0',
6060                                                                 zIndex: 2
6061                                                         };
6062                                                         if (crosshairsOptions[i].dashStyle) {
6063                                                                 attribs.dashstyle = crosshairsOptions[i].dashStyle;
6064                                                         }
6065                                                         crosshairs[i] = renderer.path(path)
6066                                                                 .attr(attribs)
6067                                                                 .add();
6068                                                 }
6069                                         }
6070                                 }                               
6071                         }               
6072                 }
6073                 
6075                 
6076                 // public members
6077                 return {
6078                         shared: shared,
6079                         refresh: refresh,
6080                         hide: hide
6081                 };      
6082         }
6083         
6084         /**
6085          * The mouse tracker object
6086          * @param {Object} chart
6087          * @param {Object} options
6088          */
6089         function MouseTracker (chart, options) {
6091                 
6092                 var mouseDownX, 
6093                         mouseDownY,
6094                         hasDragged,
6095                         selectionMarker,
6096                         zoomType = optionsChart.zoomType,
6097                         zoomX = /x/.test(zoomType),
6098                         zoomY = /y/.test(zoomType),
6099                         zoomHor = (zoomX && !inverted) || (zoomY && inverted),
6100                         zoomVert = (zoomY && !inverted) || (zoomX && inverted);
6101                         
6102                 /**
6103                  * Add crossbrowser support for chartX and chartY
6104                  * @param {Object} e The event object in standard browsers
6105                  */
6106                 function normalizeMouseEvent(e) {
6107                         var ePos,
6108                                 pageZoomFix = isWebKit && doc.width / doc.documentElement.clientWidth - 1,
6109                                 chartPosLeft,
6110                                 chartPosTop,
6111                                 chartX,
6112                                 chartY;
6113                         
6114                         // common IE normalizing
6115                         e = e || win.event;
6116                         if (!e.target) {
6117                                 e.target = e.srcElement;
6118                         }
6119                         
6120                         // iOS
6121                         ePos = e.touches ? e.touches.item(0) : e;
6122                         
6123                         // in certain cases, get mouse position
6124                         if (e.type !== 'mousemove' || win.opera || pageZoomFix) { // only Opera needs position on mouse move, see below
6125                                 chartPosition = getPosition(container);
6126                                 chartPosLeft = chartPosition.left;
6127                                 chartPosTop = chartPosition.top;
6128                         }
6129                         
6130                         // chartX and chartY
6131                         if (isIE) { // IE including IE9 that has chartX but in a different meaning
6132                                 chartX = e.x;
6133                                 chartY = e.y;
6134                         } else {
6135                                 if (ePos.layerX === UNDEFINED) { // Opera and iOS
6136                                         chartX = ePos.pageX - chartPosLeft;
6137                                         chartY = ePos.pageY - chartPosTop;
6138                                 } else {
6139                                         chartX = e.layerX;
6140                                         chartY = e.layerY;
6141                                 }
6142                         }
6143                         
6144                         // correct for page zoom bug in WebKit
6145                         if (pageZoomFix) {
6146                                 chartX += mathRound((pageZoomFix + 1) * chartPosLeft - chartPosLeft);
6147                                 chartY += mathRound((pageZoomFix + 1) * chartPosTop - chartPosTop);
6148                         }
6149                         
6150                         return extend(e, {
6151                                 chartX: chartX,
6152                                 chartY: chartY
6153                         });
6154                 }
6155                 
6156                 /**
6157                  * Get the click position in terms of axis values.
6158                  * 
6159                  * @param {Object} e A mouse event
6160                  */
6161                 function getMouseCoordinates(e) {
6162                         var coordinates = {
6163                                 xAxis: [],
6164                                 yAxis: []
6165                         }; 
6166                         each(axes, function(axis, i) {
6167                                 var translate = axis.translate,
6168                                         isXAxis = axis.isXAxis,
6169                                         isHorizontal = inverted ? !isXAxis : isXAxis;
6170                                         
6171                                 coordinates[isXAxis ? 'xAxis' : 'yAxis'].push({
6172                                         axis: axis,
6173                                         value: translate(
6174                                                 isHorizontal ? 
6175                                                         e.chartX - plotLeft  : 
6176                                                         plotHeight - e.chartY + plotTop,
6177                                                 true
6178                                         )                                                               
6179                                 });
6180                         });
6181                         return coordinates;
6182                 }
6183                 
6184                 /**
6185                  * With line type charts with a single tracker, get the point closest to the mouse
6186                  */
6187                 function onmousemove (e) {
6188                         var point,
6189                                 points,
6190                                 hoverPoint = chart.hoverPoint,
6191                                 hoverSeries = chart.hoverSeries,
6192                                 i,
6193                                 j,
6194                                 distance = chartWidth,
6195                                 index = inverted ? e.chartY : e.chartX - plotLeft; // wtf?
6196                                 
6197                         // shared tooltip
6198                         if (tooltip && options.shared) {
6199                                 points = [];
6200                                 
6201                                 // loop over all series and find the ones with points closest to the mouse
6202                                 i = series.length;
6203                                 for (j = 0; j < i; j++) {
6204                                         if (series[j].visible && series[j].tooltipPoints.length) {
6205                                                 point = series[j].tooltipPoints[index];
6206                                                 point._dist = mathAbs(index - point.plotX);
6207                                                 distance = mathMin(distance, point._dist);
6208                                                 points.push(point);
6209                                         }
6210                                 }
6211                                 // remove furthest points
6212                                 i = points.length;
6213                                 while (i--) {
6214                                         if (points[i]._dist > distance) {
6215                                                 points.splice(i, 1);
6216                                         }
6217                                 }
6218                                 // refresh the tooltip if necessary
6219                                 if (points.length && (points[0].plotX !== hoverX)) {
6220                                         tooltip.refresh(points);
6221                                         hoverX = points[0].plotX;
6222                                 }
6223                         }
6224                         
6225                         // separate tooltip and general mouse events
6226                         if (hoverSeries && hoverSeries.tracker) { // only use for line-type series with common tracker
6227                 
6228                                 // get the point
6229                                 point = hoverSeries.tooltipPoints[index];
6230                                 
6231                                 // a new point is hovered, refresh the tooltip
6232                                 if (point && point !== hoverPoint) {
6233                                         
6234                                         // trigger the events
6235                                         point.onMouseOver();
6236                                         
6237                                 }                               
6238                         }
6239                 }
6240                                 
6241                 
6242                 
6243                 /**
6244                  * Reset the tracking by hiding the tooltip, the hover series state and the hover point
6245                  */
6246                 function resetTracker() {
6247                         var hoverSeries = chart.hoverSeries,
6248                                 hoverPoint = chart.hoverPoint;                          
6250                         if (hoverPoint) {
6251                                 hoverPoint.onMouseOut();
6252                         }
6253                         
6254                         if (hoverSeries) {
6255                                 hoverSeries.onMouseOut();
6256                         }
6257                         
6258                         if (tooltip) {
6259                                 tooltip.hide();
6260                         }
6261                         
6262                         hoverX = null;
6263                 }
6264                 
6265                 /**
6266                  * Mouse up or outside the plot area
6267                  */
6268                 function drop() {
6269                         if (selectionMarker) {
6270                                 var selectionData = {
6271                                                 xAxis: [],
6272                                                 yAxis: []
6273                                         },
6274                                         selectionBox = selectionMarker.getBBox(),
6275                                         selectionLeft = selectionBox.x - plotLeft,
6276                                         selectionTop = selectionBox.y - plotTop;
6277                                 
6278                                         
6279                                 // a selection has been made
6280                                 if (hasDragged) {
6281                                         
6282                                         // record each axis' min and max
6283                                         each(axes, function(axis, i) {
6284                                                 var translate = axis.translate,
6285                                                         isXAxis = axis.isXAxis,
6286                                                         isHorizontal = inverted ? !isXAxis : isXAxis,
6287                                                         selectionMin = translate(
6288                                                                 isHorizontal ? 
6289                                                                         selectionLeft : 
6290                                                                         plotHeight - selectionTop - selectionBox.height, 
6291                                                                 true,
6292                                                                 0,
6293                                                                 0,
6294                                                                 1
6295                                                         ),
6296                                                         selectionMax = translate(
6297                                                                 isHorizontal ? 
6298                                                                         selectionLeft + selectionBox.width : 
6299                                                                         plotHeight - selectionTop, 
6300                                                                 true,
6301                                                                 0,
6302                                                                 0,
6303                                                                 1
6304                                                         );
6305                                                                 
6306                                                         selectionData[isXAxis ? 'xAxis' : 'yAxis'].push({
6307                                                                 axis: axis,
6308                                                                 min: mathMin(selectionMin, selectionMax), // for reversed axes,
6309                                                                 max: mathMax(selectionMin, selectionMax)
6310                                                         });
6311                                                         
6312                                         });
6313                                         fireEvent(chart, 'selection', selectionData, zoom);
6315                                 }
6316                                 selectionMarker = selectionMarker.destroy();
6317                         }
6318                         
6319                         chart.mouseIsDown = mouseIsDown = hasDragged = false;
6320                         removeEvent(doc, hasTouch ? 'touchend' : 'mouseup', drop);
6322                 }
6323                 
6324                 /**
6325                  * Set the JS events on the container element
6326                  */
6327                 function setDOMEvents () {
6328                         var lastWasOutsidePlot = true;
6329                         
6330                         /*
6331                          * Record the starting position of a dragoperation
6332                          */
6333                         container.onmousedown = function(e) {
6334                                 e = normalizeMouseEvent(e);
6335                                 
6336                                 // record the start position
6337                                 //e.preventDefault && e.preventDefault();
6338                                 
6339                                 chart.mouseIsDown = mouseIsDown = true;
6340                                 mouseDownX = e.chartX;
6341                                 mouseDownY = e.chartY;
6342                                 
6343                                 addEvent(doc, hasTouch ? 'touchend' : 'mouseup', drop);
6344                         };
6345                                                 
6346                         // The mousemove, touchmove and touchstart event handler
6347                         var mouseMove = function(e) {
6349                                 // let the system handle multitouch operations like two finger scroll
6350                                 // and pinching
6351                                 if (e && e.touches && e.touches.length > 1) {
6352                                         return;
6353                                 }
6354                                 
6355                                 // normalize
6356                                 e = normalizeMouseEvent(e);
6357                                 if (!hasTouch) { // not for touch devices
6358                                         e.returnValue = false;
6359                                 }
6360                                 
6361                                 var chartX = e.chartX,
6362                                         chartY = e.chartY,
6363                                         isOutsidePlot = !isInsidePlot(chartX - plotLeft, chartY - plotTop);
6364                                         
6365                                 // on touch devices, only trigger click if a handler is defined
6366                                 if (hasTouch && e.type === 'touchstart') {
6367                                         if (attr(e.target, 'isTracker')) {
6368                                                 if (!chart.runTrackerClick) {
6369                                                         e.preventDefault();
6370                                                 }       
6371                                         } else if (!runChartClick && !isOutsidePlot) {
6372                                                 e.preventDefault();
6373                                         }
6374                                 }
6375                                 
6376                                 // cancel on mouse outside
6377                                 if (isOutsidePlot) {
6378                                 
6379                                         if (!lastWasOutsidePlot) {
6380                                                 // reset the tracker                                    
6381                                                 resetTracker(); 
6382                                         }
6383                                         
6384                                         // drop the selection if any and reset mouseIsDown and hasDragged
6385                                         //drop();
6386                                         if (chartX < plotLeft) {
6387                                                 chartX = plotLeft;
6388                                         } else if (chartX > plotLeft + plotWidth) {
6389                                                 chartX = plotLeft + plotWidth;
6390                                         }
6391                                         
6392                                         if (chartY < plotTop) {
6393                                                 chartY = plotTop;
6394                                         } else if (chartY > plotTop + plotHeight) {
6395                                                 chartY = plotTop + plotHeight;
6396                                         }       
6397                                         
6398                                 }       
6399                                         
6400                                 if (mouseIsDown && e.type !== 'touchstart') { // make selection
6401                                         
6402                                         // determine if the mouse has moved more than 10px
6403                                         hasDragged = Math.sqrt(
6404                                                 Math.pow(mouseDownX - chartX, 2) + 
6405                                                 Math.pow(mouseDownY - chartY, 2));
6406                                         if (hasDragged > 10) {
6407                                         
6408                                                 // make a selection
6409                                                 if (hasCartesianSeries && (zoomX || zoomY) && 
6410                                                                 isInsidePlot(mouseDownX - plotLeft, mouseDownY - plotTop)) {
6411                                                         if (!selectionMarker) {
6412                                                                 selectionMarker = renderer.rect(
6413                                                                         plotLeft,
6414                                                                         plotTop,
6415                                                                         zoomHor ? 1 : plotWidth,
6416                                                                         zoomVert ? 1 : plotHeight,
6417                                                                         0
6418                                                                 )
6419                                                                 .attr({
6420                                                                         fill: 'rgba(69,114,167,0.25)',
6421                                                                         zIndex: 7
6422                                                                 })
6423                                                                 .add();
6424                                                         }
6425                                                 }
6426                                                 
6427                                                 // adjust the width of the selection marker
6428                                                 if (selectionMarker && zoomHor) {
6429                                                         var xSize = chartX - mouseDownX;
6430                                                         selectionMarker.attr({
6431                                                                 width: mathAbs(xSize),
6432                                                                 x: (xSize > 0 ? 0 : xSize) + mouseDownX
6433                                                         });
6434                                                 }
6435                                                 // adjust the height of the selection marker
6436                                                 if (selectionMarker && zoomVert) {
6437                                                         var ySize = chartY - mouseDownY;
6438                                                         selectionMarker.attr({
6439                                                                 height: mathAbs(ySize),
6440                                                                 y: (ySize > 0 ? 0 : ySize) + mouseDownY
6441                                                         });
6442                                                 }
6443                                         }
6444                                         
6445                                 } else if (!isOutsidePlot) {
6446                                         // show the tooltip
6447                                         onmousemove(e);
6448                                 }
6449                                 
6450                                 lastWasOutsidePlot = isOutsidePlot;
6451                                 
6452                                 // when outside plot, allow touch-drag by returning true
6453                                 return isOutsidePlot || !hasCartesianSeries;
6454                         };
6455                         
6456                         /*
6457                          * When the mouse enters the container, run mouseMove
6458                          */
6459                         container.onmousemove = mouseMove;
6460                         
6461                         /*
6462                          * When the mouse leaves the container, hide the tracking (tooltip).
6463                          */
6464                         addEvent(container, 'mouseleave', resetTracker);
6465                         
6466                         
6467                         container.ontouchstart = function(e) {
6468                                 // For touch devices, use touchmove to zoom
6469                                 if (zoomX || zoomY) {
6470                                         container.onmousedown(e);
6471                                 }
6472                                 // Show tooltip and prevent the lower mouse pseudo event
6473                                 mouseMove(e);
6474                         };
6475                         
6476                         /*
6477                          * Allow dragging the finger over the chart to read the values on touch 
6478                          * devices
6479                          */
6480                         container.ontouchmove = mouseMove;
6481                         
6482                         /*
6483                          * Allow dragging the finger over the chart to read the values on touch 
6484                          * devices
6485                          */
6486                         container.ontouchend = function() {
6487                                 if (hasDragged) {
6488                                         resetTracker();
6489                                 }
6490                         };                      
6491                         
6492                         
6493                         // MooTools 1.2.3 doesn't fire this in IE when using addEvent
6494                         container.onclick = function(e) {
6495                                 var hoverPoint = chart.hoverPoint;
6496                                 e = normalizeMouseEvent(e);
6497                                  
6498                                 e.cancelBubble = true; // IE specific
6499                                 
6500                                 
6501                                 if (!hasDragged) {
6502                                         if (hoverPoint && attr(e.target, 'isTracker')) {
6503                                                 var plotX = hoverPoint.plotX,
6504                                                         plotY = hoverPoint.plotY;
6505                                                         
6506                                                 // add page position info
6507                                                 extend(hoverPoint, {
6508                                                         pageX: chartPosition.left + plotLeft + 
6509                                                                 (inverted ? plotWidth - plotY : plotX),
6510                                                         pageY: chartPosition.top + plotTop + 
6511                                                                 (inverted ? plotHeight - plotX : plotY)
6512                                                 });
6513                                                 
6514                                                 // the series click event
6515                                                 fireEvent(hoverPoint.series, 'click', extend(e, {
6516                                                         point: hoverPoint
6517                                                 }));
6518                                                 
6519                                                 // the point click event
6520                                                 hoverPoint.firePointEvent('click', e);
6521                                         
6522                                         } else { 
6523                                                 extend(e, getMouseCoordinates(e));
6524                                                 
6525                                                 // fire a click event in the chart
6526                                                 if (isInsidePlot(e.chartX - plotLeft, e.chartY - plotTop)) {
6527                                                         fireEvent(chart, 'click', e);
6528                                                 }
6529                                         }
6530                                         
6531                                         
6532                                 }
6533                                 // reset mouseIsDown and hasDragged
6534                                 hasDragged = false;
6535                         };
6536                         
6537                 }
6538                 
6539                 /**
6540                  * Create the image map that listens for mouseovers
6541                  */
6542                 placeTrackerGroup = function() {
6543                         
6544                         // first create - plot positions is not final at this stage
6545                         if (!trackerGroup) {
6546                                 chart.trackerGroup = trackerGroup = renderer.g('tracker')
6547                                         .attr({ zIndex: 9 })
6548                                         .add();
6549                         
6550                         // then position - this happens on load and after resizing and changing
6551                         // axis or box positions
6552                         } else {                                
6553                                 trackerGroup.translate(plotLeft, plotTop);
6554                                 if (inverted) {
6555                                         trackerGroup.attr({
6556                                                 width: chart.plotWidth,
6557                                                 height: chart.plotHeight
6558                                         }).invert();
6559                                 }
6560                         }                       
6561                 };
6562                 
6563                 
6564                 // Run MouseTracker
6565                 placeTrackerGroup();
6566                 if (options.enabled) {
6567                         chart.tooltip = tooltip = Tooltip(options);
6568                 }
6569                 
6570                 setDOMEvents();
6571                 
6572                 // set the fixed interval ticking for the smooth tooltip
6573                 tooltipInterval = setInterval(function() {
6574                         if (tooltipTick) {
6575                                 tooltipTick();
6576                         }
6577                 }, 32);
6578                 
6579                 // expose properties
6580                 extend(this, {
6581                         zoomX: zoomX,
6582                         zoomY: zoomY,
6583                         resetTracker: resetTracker
6584                 });
6585         }
6586         
6587         
6588         
6589         /**
6590          * The overview of the chart's series
6591          * @param {Object} chart
6592          */
6593         var Legend = function(chart) {
6595                 var options = chart.options.legend;
6596                         
6597                 if (!options.enabled) {
6598                         return;
6599                 }
6600                 
6601                 var horizontal = options.layout === 'horizontal',
6602                         symbolWidth = options.symbolWidth,
6603                         symbolPadding = options.symbolPadding,
6604                         allItems,
6605                         style = options.style,
6606                         itemStyle = options.itemStyle,
6607                         itemHoverStyle = options.itemHoverStyle,
6608                         itemHiddenStyle = options.itemHiddenStyle,
6609                         padding = pInt(style.padding),
6610                         rightPadding = 20,
6611                         //lineHeight = options.lineHeight || 16,
6612                         y = 18,
6613                         initialItemX = 4 + padding + symbolWidth + symbolPadding,
6614                         itemX,
6615                         itemY,
6616                         lastItemY,
6617                         itemHeight = 0,
6618                         box,
6619                         legendBorderWidth = options.borderWidth,
6620                         legendBackgroundColor = options.backgroundColor,
6621                         legendGroup,
6622                         offsetWidth,
6623                         widthOption = options.width,
6624                         series = chart.series,
6625                         reversedLegend = options.reversed;
6626                         
6627                         
6628                 
6629                 /**
6630                  * Set the colors for the legend item
6631                  * @param {Object} item A Series or Point instance
6632                  * @param {Object} visible Dimmed or colored
6633                  */
6634                 function colorizeItem(item, visible) {
6635                         var legendItem = item.legendItem,
6636                                 legendLine = item.legendLine,
6637                                 legendSymbol = item.legendSymbol,
6638                                 hiddenColor = itemHiddenStyle.color,
6639                                 textColor = visible ? options.itemStyle.color : hiddenColor,
6640                                 lineColor = visible ? item.color : hiddenColor,
6641                                 symbolAttr = visible ? item.pointAttr[NORMAL_STATE] : {
6642                                         stroke: hiddenColor,
6643                                         fill: hiddenColor
6644                                 };
6645                                         
6646                         if (legendItem) {
6647                                 legendItem.css({ fill: textColor });
6648                         }
6649                         if (legendLine) {
6650                                 legendLine.attr({ stroke: lineColor });
6651                         }
6652                         if (legendSymbol) {
6653                                 legendSymbol.attr(symbolAttr);
6654                         }
6655                         
6656                 }
6657                 
6658                 /**
6659                  * Position the legend item
6660                  * @param {Object} item A Series or Point instance
6661                  * @param {Object} visible Dimmed or colored
6662                  */
6663                 function positionItem(item, itemX, itemY) {
6664                         var legendItem = item.legendItem,
6665                                 legendLine = item.legendLine,
6666                                 legendSymbol = item.legendSymbol,
6667                                 checkbox = item.checkbox;
6668                         if (legendItem) {
6669                                 legendItem.attr({ 
6670                                         x: itemX,
6671                                         y: itemY
6672                                 });
6673                         }
6674                         if (legendLine) {
6675                                 legendLine.translate(itemX, itemY - 4);
6676                         }
6677                         if (legendSymbol) {
6678                                 legendSymbol.attr({
6679                                         x: itemX + legendSymbol.xOff, 
6680                                         y: itemY + legendSymbol.yOff
6681                                 });
6682                         }
6683                         if (checkbox) {
6684                                 checkbox.x = itemX;
6685                                 checkbox.y = itemY;
6686                         }
6687                 }
6688                 
6689                 /**
6690                  * Destroy a single legend item
6691                  * @param {Object} item The series or point
6692                  */
6693                 function destroyItem(item) {
6694                         var checkbox = item.checkbox;
6695                                 
6696                         // pull out from the array
6697                         //erase(allItems, item);
6698                                 
6699                         // destroy SVG elements
6700                         each(['legendItem', 'legendLine', 'legendSymbol'], function(key) {
6701                                 if (item[key]) {
6702                                         item[key].destroy();
6703                                 }
6704                         });
6705                         
6706                         if (checkbox) {
6707                                 discardElement(item.checkbox);
6708                         }
6709                         
6710                         
6711                 }
6712                 
6713                 
6714                 /**
6715                  * Position the checkboxes after the width is determined
6716                  */ 
6717                 function positionCheckboxes() {
6718                         each(allItems, function(item) {
6719                                 var checkbox = item.checkbox,
6720                                         alignAttr = legendGroup.alignAttr;
6721                                 if (checkbox) {
6722                                         css(checkbox, {
6723                                                 left: (alignAttr.translateX + item.legendItemWidth + checkbox.x - 40) +PX,
6724                                                 top: (alignAttr.translateY + checkbox.y - 11) + PX 
6725                                         });
6726                                 }
6727                         });
6728                 }
6729                 
6730                 /**
6731                  * Render a single specific legend item
6732                  * @param {Object} item A series or point
6733                  */
6734                 function renderItem(item) {
6735                         var bBox,
6736                                 itemWidth,
6737                                 legendSymbol,
6738                                 symbolX,
6739                                 symbolY,
6740                                 attribs,
6741                                 simpleSymbol,
6742                                 li = item.legendItem,
6743                                 series = item.series || item,
6744                                 i = allItems.length,
6745                                 itemOptions = series.options,
6746                                 strokeWidth = (itemOptions && itemOptions.borderWidth) || 0;                            
6747                         
6748                         if (!li) { // generate it once, later move it
6749                         
6750                                 // let these series types use a simple symbol
6751                                 simpleSymbol = /^(bar|pie|area|column)$/.test(series.type);
6752                                 
6753                                 // generate the list item text
6754                                 item.legendItem = li = renderer.text(
6755                                                 options.labelFormatter.call(item),
6756                                                 0, 
6757                                                 0
6758                                         )
6759                                         .css(item.visible ? itemStyle : itemHiddenStyle)
6760                                         .on('mouseover', function() {
6761                                                 item.setState(HOVER_STATE);
6762                                                 li.css(itemHoverStyle);
6763                                         })
6764                                         .on('mouseout', function() {
6765                                                 li.css(item.visible ? itemStyle : itemHiddenStyle);
6766                                                 item.setState();
6767                                         })
6768                                         .on('click', function(event) {
6769                                                 var strLegendItemClick = 'legendItemClick',
6770                                                         fnLegendItemClick = function() {
6771                                                                 item.setVisible();
6772                                                         };
6773                                                 
6774                                                 // click the name or symbol
6775                                                 if (item.firePointEvent) { // point
6776                                                         item.firePointEvent(strLegendItemClick, null, fnLegendItemClick);
6777                                                 } else {
6778                                                         fireEvent(item, strLegendItemClick, null, fnLegendItemClick);
6779                                                 }
6780                                         })
6781                                         .attr({ zIndex: 2 })
6782                                         .add(legendGroup);
6783                                 
6784                                 // draw the line
6785                                 if (!simpleSymbol && itemOptions && itemOptions.lineWidth) {
6786                                         var attrs = {
6787                                                         'stroke-width': itemOptions.lineWidth,
6788                                                         zIndex: 2
6789                                                 };
6790                                         if (itemOptions.dashStyle) {
6791                                                 attrs.dashstyle = itemOptions.dashStyle;
6792                                         }
6793                                         item.legendLine = renderer.path([
6794                                                 M,
6795                                                 -symbolWidth - symbolPadding, 
6796                                                 0,
6797                                                 L, 
6798                                                 -symbolPadding, 
6799                                                 0
6800                                         ])
6801                                         .attr(attrs)
6802                                         .add(legendGroup);
6803                                 }
6804                                         
6805                                 // draw a simple symbol
6806                                 if (simpleSymbol) { // bar|pie|area|column
6807                                         
6808                                         legendSymbol = renderer.rect(
6809                                                 (symbolX = -symbolWidth - symbolPadding),
6810                                                 (symbolY = -11),
6811                                                 symbolWidth,
6812                                                 12,
6813                                                 2
6814                                         ).attr({
6815                                                 //'stroke-width': 0,
6816                                                 zIndex: 3
6817                                         }).add(legendGroup);
6818                                 }
6819                                         
6820                                 // draw the marker
6821                                 else if (itemOptions && itemOptions.marker && itemOptions.marker.enabled) {
6822                                         legendSymbol = renderer.symbol(
6823                                                 item.symbol,
6824                                                 (symbolX = -symbolWidth / 2 - symbolPadding), 
6825                                                 (symbolY = -4),
6826                                                 itemOptions.marker.radius
6827                                         )
6828                                         //.attr(item.pointAttr[NORMAL_STATE])
6829                                         .attr({ zIndex: 3 })
6830                                         .add(legendGroup);
6831                                 
6832                                 }
6833                                 if (legendSymbol) {
6834                                         legendSymbol.xOff = symbolX + (strokeWidth % 2 / 2);
6835                                         legendSymbol.yOff = symbolY + (strokeWidth % 2 / 2);
6836                                 }
6837                                 
6838                                 item.legendSymbol = legendSymbol;
6839                                         
6840                                 // colorize the items
6841                                 colorizeItem(item, item.visible);
6842                                 
6843                                 
6844                                 // add the HTML checkbox on top
6845                                 if (itemOptions && itemOptions.showCheckbox) {
6846                                         item.checkbox = createElement('input', {
6847                                                 type: 'checkbox',
6848                                                 checked: item.selected,
6849                                                 defaultChecked: item.selected // required by IE7                                                
6850                                         }, options.itemCheckboxStyle, container);
6851                                         
6852                                         addEvent(item.checkbox, 'click', function(event) {
6853                                                 var target = event.target;
6854                                                 fireEvent(item, 'checkboxClick', { 
6855                                                                 checked: target.checked 
6856                                                         }, 
6857                                                         function() {
6858                                                                 item.select();
6859                                                         }
6860                                                 );
6861                                         });
6862                                 }
6863                         }
6864                         
6865                         
6866                         // calculate the positions for the next line
6867                         bBox = li.getBBox();
6868                         
6869                         itemWidth = item.legendItemWidth =  
6870                                 options.itemWidth || symbolWidth + symbolPadding + bBox.width + rightPadding;
6871                         itemHeight = bBox.height;
6872                         
6873                         // if the item exceeds the width, start a new line
6874                         if (horizontal && itemX - initialItemX + itemWidth > 
6875                                         (widthOption || (chartWidth - 2 * padding - initialItemX))) {
6876                                 itemX = initialItemX;
6877                                 itemY += itemHeight;
6878                         }               
6879                         lastItemY = itemY;
6880                         
6881                         // position the newly generated or reordered items
6882                         positionItem(item, itemX, itemY);
6883                         
6884                         // advance
6885                         if (horizontal)  {
6886                                 itemX += itemWidth;
6887                         } else {
6888                                 itemY += itemHeight;
6889                         }
6890                         
6891                         // the width of the widest item
6892                         offsetWidth = widthOption || mathMax(
6893                                 horizontal ? itemX - initialItemX : itemWidth, 
6894                                 offsetWidth
6895                         );
6896                         
6897                                         
6898                         
6899                         // add it all to an array to use below
6900                         //allItems.push(item);
6901                 }
6903                 /**
6904                  * Render the legend. This method can be called both before and after
6905                  * chart.render. If called after, it will only rearrange items instead
6906                  * of creating new ones.
6907                  */
6908                 function renderLegend() {
6909                         itemX = initialItemX;
6910                         itemY = y;
6911                         offsetWidth = 0;
6912                         lastItemY = 0;
6913                         
6914                         if (!legendGroup) {
6915                                 legendGroup = renderer.g('legend')
6916                                         .attr({ zIndex: 7 })
6917                                         .add();
6918                         }
6919                         
6920                         
6921                         // add each series or point
6922                         allItems = [];
6923                         each(series, function(serie) {
6924                                 var seriesOptions = serie.options;
6925                                 
6926                                 if (!seriesOptions.showInLegend) {
6927                                         return;
6928                                 }
6929                                 
6930                                 // use points or series for the legend item depending on legendType
6931                                 allItems = allItems.concat(seriesOptions.legendType === 'point' ?
6932                                         serie.data : 
6933                                         serie
6934                                 );
6935                                 
6936                         });
6937                         
6938                         // sort by legendIndex
6939                         allItems.sort(function(a, b) {
6940                                 return (a.options.legendIndex || 0) - (b.options.legendIndex || 0);
6941                         });
6942                         
6943                         // reversed legend
6944                         if (reversedLegend) {
6945                                 allItems.reverse();
6946                         }
6947                         
6948                         // render the items
6949                         each(allItems, renderItem);
6950                         
6951                         
6952                         
6953                         // Draw the border
6954                         legendWidth = widthOption || offsetWidth;
6955                         legendHeight = lastItemY - y + itemHeight;
6956                         
6957                         if (legendBorderWidth || legendBackgroundColor) {
6958                                 legendWidth += 2 * padding;
6959                                 legendHeight += 2 * padding;
6960                                 
6961                                 if (!box) {
6962                                         box = renderer.rect(
6963                                                 0, 
6964                                                 0,
6965                                                 legendWidth,
6966                                                 legendHeight,
6967                                                 options.borderRadius,
6968                                                 legendBorderWidth || 0
6969                                         ).attr({
6970                                                 stroke: options.borderColor,
6971                                                 'stroke-width': legendBorderWidth || 0,
6972                                                 fill: legendBackgroundColor || NONE
6973                                         })
6974                                         .add(legendGroup)
6975                                         .shadow(options.shadow);
6976                                 
6977                                 } else if (legendWidth > 0 && legendHeight > 0) {
6978                                         box.animate(
6979                                                 box.crisp(null, null, null, legendWidth, legendHeight)
6980                                         );
6981                                 }
6982                                 
6983                                 // hide the border if no items
6984                                 box[allItems.length ? 'show' : 'hide']();
6985                         }
6986                         
6987                         // 1.x compatibility: positioning based on style
6988                         var props = ['left', 'right', 'top', 'bottom'],
6989                                 prop,
6990                                 i = 4;
6991                         while(i--) {
6992                                 prop = props[i];
6993                                 if (style[prop] && style[prop] !== 'auto') {
6994                                         options[i < 2 ? 'align' : 'verticalAlign'] = prop;
6995                                         options[i < 2 ? 'x' : 'y'] = pInt(style[prop]) * (i % 2 ? -1 : 1);
6996                                 }
6997                         }
6998                         
6999                         legendGroup.align(extend(options, {
7000                                 width: legendWidth,
7001                                 height: legendHeight
7002                         }), true, spacingBox);
7003                         
7004                         if (!isResizing) {
7005                                 positionCheckboxes();
7006                         }
7007                 }
7008                 
7009                 
7010                 // run legend
7011                 renderLegend();
7012                 
7013                 // move checkboxes
7014                 addEvent(chart, 'endResize', positionCheckboxes);
7015                 
7016                 // expose 
7017                 return {
7018                         colorizeItem: colorizeItem,
7019                         destroyItem: destroyItem,
7020                         renderLegend: renderLegend
7021                 };
7022         };
7023         
7024         
7025         
7026                 
7027         
7029         /** 
7030          * Initialize an individual series, called internally before render time
7031          */
7032         function initSeries(options) {
7033                 var type = options.type || optionsChart.type || optionsChart.defaultSeriesType,
7034                         typeClass = seriesTypes[type],
7035                         serie,
7036                         hasRendered = chart.hasRendered;
7037                         
7038                 // an inverted chart can't take a column series and vice versa
7039                 if (hasRendered) {
7040                         if (inverted && type === 'column') {
7041                                 typeClass = seriesTypes.bar;
7042                         } else if (!inverted && type === 'bar') {
7043                                 typeClass = seriesTypes.column;
7044                         }
7045                 }
7046                 
7047                 serie = new typeClass();
7048                 
7049                 serie.init(chart, options);
7050                 
7051                 // set internal chart properties
7052                 if (!hasRendered && serie.inverted) {
7053                         inverted = true;
7054                 }
7055                 if (serie.isCartesian) {
7056                         hasCartesianSeries = serie.isCartesian;
7057                 }
7058                 
7059                 series.push(serie);
7060                 
7061                 return serie;
7062         }
7064         /**
7065          * Add a series dynamically after  time
7066          * 
7067          * @param {Object} options The config options
7068          * @param {Boolean} redraw Whether to redraw the chart after adding. Defaults to true.
7069          * @param {Boolean|Object} animation Whether to apply animation, and optionally animation
7070          *    configuration
7071          * 
7072          * @return {Object} series The newly created series object
7073          */
7074         function addSeries(options, redraw, animation) {
7075                 var series;
7076                 
7077                 if (options) {
7078                         setAnimation(animation, chart);
7079                         redraw = pick(redraw, true); // defaults to true
7080                         
7081                         fireEvent(chart, 'addSeries', { options: options }, function() {
7082                                 series = initSeries(options);
7083                                 series.isDirty = true;
7084                                 
7085                                 chart.isDirtyLegend = true; // the series array is out of sync with the display
7086                                 if (redraw) {
7087                                         chart.redraw();
7088                                 }
7089                         });
7090                 }
7091                 
7092                 return series;
7093         }
7094         
7095         /**
7096          * Check whether a given point is within the plot area
7097          * 
7098          * @param {Number} x Pixel x relative to the coordinateSystem
7099          * @param {Number} y Pixel y relative to the coordinateSystem
7100          */
7101         isInsidePlot = function(x, y) {
7102                 return x >= 0 &&
7103                         x <= plotWidth &&
7104                         y >= 0 &&
7105                         y <= plotHeight;
7106         };
7107                 
7108         /**
7109          * Adjust all axes tick amounts
7110          */
7111         function adjustTickAmounts() {
7112                 if (optionsChart.alignTicks !== false) {
7113                         each(axes, function(axis) {
7114                                 axis.adjustTickAmount();
7115                         });
7116                 }
7117                 maxTicks = null;
7118         }
7120         /**
7121          * Redraw legend, axes or series based on updated data
7122          * 
7123          * @param {Boolean|Object} animation Whether to apply animation, and optionally animation
7124          *    configuration
7125          */
7126         function redraw(animation) {
7127                 var redrawLegend = chart.isDirtyLegend,
7128                         hasStackedSeries,
7129                         isDirtyBox = chart.isDirtyBox, // todo: check if it has actually changed?
7130                         seriesLength = series.length,
7131                         i = seriesLength,
7132                         clipRect = chart.clipRect,
7133                         serie;
7134                         
7135                 setAnimation(animation, chart);
7136                 
7137                 // link stacked series
7138                 while (i--) {
7139                         serie = series[i];
7140                         if (serie.isDirty && serie.options.stacking) {
7141                                 hasStackedSeries = true;
7142                                 break;
7143                         }
7144                 }
7145                 if (hasStackedSeries) { // mark others as dirty
7146                         i = seriesLength;
7147                         while (i--) {
7148                                 serie = series[i];
7149                                 if (serie.options.stacking) {
7150                                         serie.isDirty = true;
7151                                 }
7152                         }
7153                 }
7154                 
7155                 // handle updated data in the series            
7156                 each(series, function(serie) {
7157                         if (serie.isDirty) { // prepare the data so axis can read it
7158                                 serie.cleanData();
7159                                 serie.getSegments();
7160                                 
7161                                 if (serie.options.legendType === 'point') {
7162                                         redrawLegend = true;
7163                                 }
7164                         }
7165                 });
7166                 
7167                 // handle added or removed series
7168                 if (redrawLegend && legend.renderLegend) { // series or pie points are added or removed
7169                         // draw legend graphics
7170                         legend.renderLegend();
7171                         
7172                         chart.isDirtyLegend = false;
7173                 }
7174                                 
7175                 if (hasCartesianSeries) {
7176                         if (!isResizing) {
7177                                 
7178                                 // reset maxTicks
7179                                 maxTicks = null;                                
7180                                 
7181                                 // set axes scales
7182                                 each(axes, function(axis) {
7183                                         axis.setScale();
7184                                 });
7185                         }
7186                         adjustTickAmounts();
7187                         getMargins();
7188         
7189                         // redraw axes
7190                         each(axes, function(axis) {
7191                                 if (axis.isDirty || isDirtyBox) {
7192                                         axis.redraw();
7193                                         isDirtyBox = true; // always redraw box to reflect changes in the axis labels 
7194                                 }
7195                         });
7196                         
7197                         
7198                 }
7199                 
7200                 // the plot areas size has changed
7201                 if (isDirtyBox) {
7202                         drawChartBox();
7203                         placeTrackerGroup();
7204                         
7205                         // move clip rect
7206                         if (clipRect) {
7207                                 stop(clipRect);
7208                                 clipRect.animate({ // for chart resize
7209                                         width: chart.plotSizeX,
7210                                         height: chart.plotSizeY
7211                                 });
7212                         }
7213                 
7214                 }
7215                 
7216                                         
7217                 // redraw affected series
7218                 each(series, function(serie) {
7219                         if (serie.isDirty && serie.visible && 
7220                                         (!serie.isCartesian || serie.xAxis)) { // issue #153 
7221                                 serie.redraw();
7222                         }
7223                 });
7224                 
7225                 
7226                 // hide tooltip and hover states
7227                 if (tracker && tracker.resetTracker) {
7228                         tracker.resetTracker();
7229                 }
7230                 
7231                 // fire the event
7232                 fireEvent(chart, 'redraw');
7233         }
7234         
7235         
7236         
7237         /**
7238          * Dim the chart and show a loading text or symbol
7239          * @param {String} str An optional text to show in the loading label instead of the default one
7240          */
7241         function showLoading(str) {
7242                 var loadingOptions = options.loading;
7244                 // create the layer at the first call
7245                 if (!loadingDiv) {
7246                         loadingDiv = createElement(DIV, {
7247                                 className: 'highcharts-loading'
7248                         }, extend(loadingOptions.style, {
7249                                 left: plotLeft + PX,
7250                                 top: plotTop + PX,
7251                                 width: plotWidth + PX,
7252                                 height: plotHeight + PX,
7253                                 zIndex: 10,
7254                                 display: NONE
7255                         }), container);
7256                         
7257                         loadingSpan = createElement(
7258                                 'span', 
7259                                 null, 
7260                                 loadingOptions.labelStyle, 
7261                                 loadingDiv
7262                         );
7264                 }
7265                 
7266                 // update text
7267                 loadingSpan.innerHTML = str || options.lang.loading;
7268                 
7269                 // show it
7270                 if (!loadingShown) {
7271                         css(loadingDiv, { opacity: 0, display: '' });
7272                         animate(loadingDiv, {
7273                                 opacity: loadingOptions.style.opacity
7274                         }, {
7275                                 duration: loadingOptions.showDuration
7276                         });
7277                         loadingShown = true;
7278                 }
7279         }
7280         /**
7281          * Hide the loading layer
7282          */
7283         function hideLoading() {
7284                 animate(loadingDiv, {
7285                         opacity: 0
7286                 }, {
7287                         duration: options.loading.hideDuration, 
7288                         complete: function() {
7289                                 css(loadingDiv, { display: NONE });
7290                         }
7291                 });
7292                 loadingShown = false;
7293         }
7294         
7295         /**
7296          * Get an axis, series or point object by id.
7297          * @param id {String} The id as given in the configuration options
7298          */
7299         function get(id) {
7300                 var i,
7301                         j,
7302                         data;
7303                 
7304                 // search axes
7305                 for (i = 0; i < axes.length; i++) {
7306                         if (axes[i].options.id === id) {
7307                                 return axes[i];
7308                         }
7309                 }
7310                 
7311                 // search series
7312                 for (i = 0; i < series.length; i++) {
7313                         if (series[i].options.id === id) {
7314                                 return series[i];
7315                         }
7316                 }
7317                 
7318                 // search points
7319                 for (i = 0; i < series.length; i++) {
7320                         data = series[i].data;
7321                         for (j = 0; j < data.length; j++) {
7322                                 if (data[j].id === id) {
7323                                         return data[j];
7324                                 }
7325                         }
7326                 }
7327                 return null;    
7328         }
7329         
7330         /** 
7331          * Create the Axis instances based on the config options
7332          */
7333         function getAxes() {
7334                 var xAxisOptions = options.xAxis || {},
7335                         yAxisOptions = options.yAxis || {},
7336                         axis;
7337                         
7338                 // make sure the options are arrays and add some members
7339                 xAxisOptions = splat(xAxisOptions);
7340                 each(xAxisOptions, function(axis, i) {
7341                         axis.index = i; 
7342                         axis.isX = true;
7343                 });
7344                 
7345                 yAxisOptions = splat(yAxisOptions);
7346                 each(yAxisOptions, function(axis, i) {
7347                         axis.index = i;
7348                 });
7349                 
7350                 // concatenate all axis options into one array
7351                 axes = xAxisOptions.concat(yAxisOptions);
7352                 
7353                 // loop the options and construct axis objects
7354                 chart.xAxis = [];
7355                 chart.yAxis = [];
7356                 axes = map(axes, function(axisOptions) {
7357                         axis = new Axis(chart, axisOptions);
7358                         chart[axis.isXAxis ? 'xAxis' : 'yAxis'].push(axis);
7359                         
7360                         return axis;
7361                 });
7362                 
7363                 adjustTickAmounts();
7364         }
7366         
7367         /**
7368          * Get the currently selected points from all series
7369          */
7370         function getSelectedPoints() {
7371                 var points = [];
7372                 each(series, function(serie) {
7373                         points = points.concat( grep( serie.data, function(point) {
7374                                 return point.selected;
7375                         }));
7376                 });
7377                 return points;
7378         }
7379         
7380         /**
7381          * Get the currently selected series
7382          */
7383         function getSelectedSeries() {
7384                 return grep(series, function (serie) {
7385                         return serie.selected;
7386                 });
7387         }
7388         
7389         /**
7390          * Zoom out to 1:1
7391          */
7392         zoomOut = function () {
7393                 fireEvent(chart, 'selection', { resetSelection: true }, zoom);
7394                 chart.toolbar.remove('zoom');
7396         };
7397         /**
7398          * Zoom into a given portion of the chart given by axis coordinates
7399          * @param {Object} event
7400          */
7401         zoom = function (event) {
7402                 
7403                 // add button to reset selection
7404                 var lang = defaultOptions.lang,
7405                         animate = chart.pointCount < 100;
7406                 chart.toolbar.add('zoom', lang.resetZoom, lang.resetZoomTitle, zoomOut);
7407                 
7408                 // if zoom is called with no arguments, reset the axes
7409                 if (!event || event.resetSelection) {
7410                         each(axes, function(axis) {
7411                                 axis.setExtremes(null, null, false, animate);
7412                         });
7413                 }
7414                         
7415                 // else, zoom in on all axes
7416                 else {
7417                         each(event.xAxis.concat(event.yAxis), function(axisData) {
7418                                 var axis = axisData.axis;
7419                                 
7420                                 // don't zoom more than maxZoom
7421                                 if (chart.tracker[axis.isXAxis ? 'zoomX' : 'zoomY']) {
7422                                         axis.setExtremes(axisData.min, axisData.max, false, animate);
7423                                 }
7424                         });
7425                 }
7426                 
7427                 // redraw chart
7428                 redraw();
7429         };
7430         
7431         /**
7432          * Show the title and subtitle of the chart
7433          * 
7434          * @param titleOptions {Object} New title options
7435          * @param subtitleOptions {Object} New subtitle options
7436          * 
7437          */
7438         function setTitle (titleOptions, subtitleOptions) {
7439                 
7440                 chartTitleOptions = merge(options.title, titleOptions);
7441                 chartSubtitleOptions = merge(options.subtitle, subtitleOptions);
7442                 
7443                 // add title and subtitle
7444                 each([
7445                         ['title', titleOptions, chartTitleOptions],
7446                         ['subtitle', subtitleOptions, chartSubtitleOptions]
7447                 ], function(arr) {
7448                         var name = arr[0],
7449                                 title = chart[name],
7450                                 titleOptions = arr[1],
7451                                 chartTitleOptions = arr[2];
7452                                 
7453                         if (title && titleOptions) {
7454                                 title.destroy(); // remove old
7455                                 title = null;
7456                         }
7457                         if (chartTitleOptions && chartTitleOptions.text && !title) {
7458                                 chart[name] = renderer.text(
7459                                         chartTitleOptions.text, 
7460                                         0,
7461                                         0
7462                                 )
7463                                 .attr({
7464                                         align: chartTitleOptions.align,
7465                                         'class': 'highcharts-'+ name,
7466                                         zIndex: 1
7467                                 })
7468                                 .css(chartTitleOptions.style)
7469                                 .add()
7470                                 .align(chartTitleOptions, false, spacingBox);
7471                         }
7472                 });
7473                 
7474         }
7475         
7476         /**
7477          * Get chart width and height according to options and container size
7478          */
7479         function getChartSize() {
7481                 containerWidth = (renderToClone || renderTo).offsetWidth;
7482                 containerHeight = (renderToClone || renderTo).offsetHeight;
7483                 chart.chartWidth = chartWidth = optionsChart.width || containerWidth || 600;
7484                 chart.chartHeight = chartHeight = optionsChart.height || 
7485                         // the offsetHeight of an empty container is 0 in standard browsers, but 19 in IE7:
7486                         (containerHeight > 19 ? containerHeight : 400);
7487         }
7489         
7490         /**
7491          * Get the containing element, determine the size and create the inner container
7492          * div to hold the chart
7493          */
7494         function getContainer() {
7495                 renderTo = optionsChart.renderTo;
7496                 containerId = PREFIX + idCounter++;
7497         
7498                 if (isString(renderTo)) {
7499                         renderTo = doc.getElementById(renderTo);
7500                 }
7501         
7502                 // remove previous chart
7503                 renderTo.innerHTML = '';
7504                 
7505                 // If the container doesn't have an offsetWidth, it has or is a child of a node
7506                 // that has display:none. We need to temporarily move it out to a visible
7507                 // state to determine the size, else the legend and tooltips won't render
7508                 // properly 
7509                 if (!renderTo.offsetWidth) {
7510                         renderToClone = renderTo.cloneNode(0);
7511                         css(renderToClone, {
7512                                 position: ABSOLUTE,
7513                                 top: '-9999px',
7514                                 display: ''
7515                         });
7516                         doc.body.appendChild(renderToClone);
7517                 }
7518                 
7519                 // get the width and height
7520                 getChartSize();
7521                 
7522                 // create the inner container
7523                 chart.container = container = createElement(DIV, {
7524                                 className: 'highcharts-container' + 
7525                                         (optionsChart.className ? ' '+ optionsChart.className : ''),
7526                                 id: containerId
7527                         }, extend({
7528                                 position: RELATIVE,
7529                                 overflow: HIDDEN, // needed for context menu (avoid scrollbars) and  
7530                                         // content overflow in IE
7531                                 width: chartWidth + PX,
7532                                 height: chartHeight + PX,
7533                                 textAlign: 'left'
7534                         }, optionsChart.style),
7535                         renderToClone || renderTo
7536                 );
7537                 
7538                 chart.renderer = renderer = 
7539                         optionsChart.forExport ? // force SVG, used for SVG export
7540                                 new SVGRenderer(container, chartWidth, chartHeight, true) : 
7541                                 new Renderer(container, chartWidth, chartHeight);
7542                                 
7543                 // Issue 110 workaround:
7544                 // In Firefox, if a div is positioned by percentage, its pixel position may land
7545                 // between pixels. The container itself doesn't display this, but an SVG element
7546                 // inside this container will be drawn at subpixel precision. In order to draw
7547                 // sharp lines, this must be compensated for. This doesn't seem to work inside
7548                 // iframes though (like in jsFiddle).
7549                 var subPixelFix, rect;
7550                 if (isFirefox && container.getBoundingClientRect) {
7551                         subPixelFix = function() {
7552                                 css(container, { left: 0, top: 0 });
7553                                 rect = container.getBoundingClientRect();
7554                                 css(container, {
7555                                         left: (-(rect.left - pInt(rect.left))) + PX,
7556                                         top: (-(rect.top - pInt(rect.top))) + PX
7557                                 });
7558                         };
7559                         
7560                         // run the fix now
7561                         subPixelFix();
7562                         
7563                         // run it on resize
7564                         addEvent(win, 'resize', subPixelFix);
7565                         
7566                         // remove it on chart destroy
7567                         addEvent(chart, 'destroy', function() {
7568                                 removeEvent(win, 'resize', subPixelFix);
7569                         });
7570                 }
7571         }
7572         
7573         /**
7574          * Calculate margins by rendering axis labels in a preliminary position. Title,
7575          * subtitle and legend have already been rendered at this stage, but will be 
7576          * moved into their final positions
7577          */
7578         getMargins = function() {
7579                 var legendOptions = options.legend,
7580                         legendMargin = pick(legendOptions.margin, 10),
7581                         legendX = legendOptions.x,
7582                         legendY = legendOptions.y,
7583                         align = legendOptions.align,
7584                         verticalAlign = legendOptions.verticalAlign,
7585                         titleOffset;
7587                 resetMargins();
7589                 // adjust for title and subtitle
7590                 if ((chart.title || chart.subtitle) && !defined(optionsMarginTop)) {
7591                         titleOffset = mathMax(
7592                                 (chart.title && !chartTitleOptions.floating && !chartTitleOptions.verticalAlign && chartTitleOptions.y) || 0, 
7593                                 (chart.subtitle && !chartSubtitleOptions.floating && !chartSubtitleOptions.verticalAlign && chartSubtitleOptions.y) || 0                                
7594                         );
7595                         if (titleOffset) {
7596                                 plotTop = mathMax(plotTop, titleOffset + pick(chartTitleOptions.margin, 15) + spacingTop);
7597                         }
7598                 }
7599                 // adjust for legend
7600                 if (legendOptions.enabled && !legendOptions.floating) {
7601                         if (align === 'right') { // horizontal alignment handled first
7602                                 if (!defined(optionsMarginRight)) {
7603                                         marginRight = mathMax(
7604                                                 marginRight,
7605                                                 legendWidth - legendX + legendMargin + spacingRight
7606                                         );
7607                                 }
7608                         } else if (align === 'left') {
7609                                 if (!defined(optionsMarginLeft)) {
7610                                         plotLeft = mathMax(
7611                                                 plotLeft,
7612                                                 legendWidth + legendX + legendMargin + spacingLeft
7613                                         );
7614                                 }
7615                                 
7616                         } else if (verticalAlign === 'top') {
7617                                 if (!defined(optionsMarginTop)) {
7618                                         plotTop = mathMax(
7619                                                 plotTop, 
7620                                                 legendHeight + legendY + legendMargin + spacingTop
7621                                         );
7622                                 }
7623                         
7624                         } else if (verticalAlign === 'bottom') {
7625                                 if (!defined(optionsMarginBottom)) {
7626                                         marginBottom = mathMax(
7627                                                 marginBottom, 
7628                                                 legendHeight - legendY + legendMargin + spacingBottom
7629                                         );
7630                                 }
7631                         }
7632                 }
7633                 
7634                 // pre-render axes to get labels offset width
7635                 if (hasCartesianSeries) {
7636                         each(axes, function(axis) {
7637                                 axis.getOffset();
7638                         });
7639                 }
7640                 
7641                 if (!defined(optionsMarginLeft)) {
7642                         plotLeft += axisOffset[3];
7643                 }
7644                 if (!defined(optionsMarginTop)) {
7645                         plotTop += axisOffset[0];
7646                 }
7647                 if (!defined(optionsMarginBottom)) {
7648                         marginBottom += axisOffset[2];
7649                 }
7650                 if (!defined(optionsMarginRight)) {
7651                         marginRight += axisOffset[1];
7652                 }
7653                 
7654                 setChartSize();
7655                 
7656         };
7657         
7658         /**
7659          * Add the event handlers necessary for auto resizing
7660          * 
7661          */
7662         function initReflow() {
7663                 var reflowTimeout;
7664                 function reflow() {
7665                         var width = optionsChart.width || renderTo.offsetWidth,
7666                                 height = optionsChart.height || renderTo.offsetHeight;
7667                         
7668                         if (width && height) { // means container is display:none
7669                                 if (width !== containerWidth || height !== containerHeight) {
7670                                         clearTimeout(reflowTimeout);
7671                                         reflowTimeout = setTimeout(function() {
7672                                                 resize(width, height, false);
7673                                         }, 100);
7674                                 }
7675                                 containerWidth = width;
7676                                 containerHeight = height;
7677                         }
7678                 }
7679                 addEvent(win, 'resize', reflow);
7680                 addEvent(chart, 'destroy', function() {
7681                         removeEvent(win, 'resize', reflow);
7682                 });
7683         }
7684         
7685         /**
7686          * Resize the chart to a given width and height
7687          * @param {Number} width
7688          * @param {Number} height
7689          * @param {Object|Boolean} animation
7690          */
7691         resize = function(width, height, animation) {
7692                 var chartTitle = chart.title,
7693                         chartSubtitle = chart.subtitle;
7694                 
7695                 isResizing += 1;
7696                 
7697                 // set the animation for the current process
7698                 setAnimation(animation, chart);
7699                 
7700                 oldChartHeight = chartHeight;
7701                 oldChartWidth = chartWidth;
7702                 chart.chartWidth = chartWidth = mathRound(width);
7703                 chart.chartHeight = chartHeight = mathRound(height);
7704                 
7705                 css(container, {
7706                         width: chartWidth + PX,
7707                         height: chartHeight + PX
7708                 });
7709                 renderer.setSize(chartWidth, chartHeight, animation);
7710                 
7711                 // update axis lengths for more correct tick intervals:
7712                 plotWidth = chartWidth - plotLeft - marginRight; 
7713                 plotHeight = chartHeight - plotTop - marginBottom;
7714                 
7715                 // handle axes
7716                 maxTicks = null;
7717                 each(axes, function(axis) {
7718                         axis.isDirty = true;
7719                         axis.setScale();
7720                 });
7721                 
7722                 // make sure non-cartesian series are also handled
7723                 each(series, function(serie) {
7724                         serie.isDirty = true;
7725                 });
7726                 
7727                 chart.isDirtyLegend = true; // force legend redraw
7728                 chart.isDirtyBox = true; // force redraw of plot and chart border
7729                 
7730                 getMargins();
7731                  
7732                 // move titles
7733                 if (chartTitle) {
7734                         chartTitle.align(null, null, spacingBox);
7735                 }
7736                 if (chartSubtitle) {
7737                         chartSubtitle.align(null, null, spacingBox);
7738                 }
7739                 
7740                 redraw(animation);
7741                 
7742                 
7743                 oldChartHeight = null;
7744                 fireEvent(chart, 'resize');
7745                 
7746                 // fire endResize and set isResizing back 
7747                 setTimeout(function() {
7748                         fireEvent(chart, 'endResize', null, function() {
7749                                 isResizing -= 1;
7750                         });
7751                 }, (globalAnimation && globalAnimation.duration) || 500);
7752         };
7753         
7754         /**
7755          * Set the public chart properties. This is done before and after the pre-render
7756          * to determine margin sizes
7757          */
7758         setChartSize = function() {
7759                 
7760                 chart.plotLeft = plotLeft = mathRound(plotLeft);
7761                 chart.plotTop = plotTop = mathRound(plotTop);
7762                 chart.plotWidth = plotWidth = mathRound(chartWidth - plotLeft - marginRight);
7763                 chart.plotHeight = plotHeight = mathRound(chartHeight - plotTop - marginBottom);
7764                 
7765                 chart.plotSizeX = inverted ? plotHeight : plotWidth;
7766                 chart.plotSizeY = inverted ? plotWidth : plotHeight;
7767                 
7768                 spacingBox = {
7769                         x: spacingLeft,
7770                         y: spacingTop,
7771                         width: chartWidth - spacingLeft - spacingRight,
7772                         height: chartHeight - spacingTop - spacingBottom
7773                 };
7774         };
7775         
7776         /**
7777          * Initial margins before auto size margins are applied
7778          */
7779         resetMargins = function() {
7780                 plotTop = pick(optionsMarginTop, spacingTop);
7781                 marginRight = pick(optionsMarginRight, spacingRight);
7782                 marginBottom = pick(optionsMarginBottom, spacingBottom);
7783                 plotLeft = pick(optionsMarginLeft, spacingLeft);
7784                 axisOffset = [0, 0, 0, 0]; // top, right, bottom, left
7785         };
7786         
7787         /**
7788          * Draw the borders and backgrounds for chart and plot area
7789          */
7790         drawChartBox = function() {
7791                 var chartBorderWidth = optionsChart.borderWidth || 0,
7792                         chartBackgroundColor = optionsChart.backgroundColor,
7793                         plotBackgroundColor = optionsChart.plotBackgroundColor,
7794                         plotBackgroundImage = optionsChart.plotBackgroundImage,
7795                         mgn,
7796                         plotSize = {
7797                                 x: plotLeft,
7798                                 y: plotTop,
7799                                 width: plotWidth,
7800                                 height: plotHeight
7801                         };
7803                 // Chart area
7804                 mgn = chartBorderWidth + (optionsChart.shadow ? 8 : 0);
7805                         
7806                 if (chartBorderWidth || chartBackgroundColor) {
7807                         if (!chartBackground) {
7808                                 chartBackground = renderer.rect(mgn / 2, mgn / 2, chartWidth - mgn, chartHeight - mgn, 
7809                                                 optionsChart.borderRadius, chartBorderWidth)
7810                                         .attr({ 
7811                                                 stroke: optionsChart.borderColor,
7812                                                 'stroke-width': chartBorderWidth,
7813                                                 fill: chartBackgroundColor || NONE
7814                                         })
7815                                         .add()
7816                                         .shadow(optionsChart.shadow);
7817                         } else { // resize
7818                                 chartBackground.animate(
7819                                         chartBackground.crisp(null, null, null, chartWidth - mgn, chartHeight - mgn)
7820                                 );
7821                         }
7822                 }
7823                 
7824                 
7825                 // Plot background
7826                 if (plotBackgroundColor) {
7827                         if (!plotBackground) {
7828                                 plotBackground = renderer.rect(plotLeft, plotTop, plotWidth, plotHeight, 0)
7829                                         .attr({
7830                                                 fill: plotBackgroundColor
7831                                         })
7832                                         .add()
7833                                         .shadow(optionsChart.plotShadow);
7834                         } else {
7835                                 plotBackground.animate(plotSize);
7836                         }
7837                 }
7838                 if (plotBackgroundImage) {
7839                         if (!plotBGImage) {
7840                                 plotBGImage = renderer.image(plotBackgroundImage, plotLeft, plotTop, plotWidth, plotHeight)
7841                                         .add();
7842                         } else {
7843                                 plotBGImage.animate(plotSize);
7844                         }
7845                 }
7846                 
7847                 // Plot area border
7848                 if (optionsChart.plotBorderWidth) {
7849                         if (!plotBorder) {
7850                                 plotBorder = renderer.rect(plotLeft, plotTop, plotWidth, plotHeight, 0, optionsChart.plotBorderWidth)
7851                                         .attr({
7852                                                 stroke: optionsChart.plotBorderColor,
7853                                                 'stroke-width': optionsChart.plotBorderWidth,
7854                                                 zIndex: 4
7855                                         })
7856                                         .add();
7857                         } else {
7858                                 plotBorder.animate(
7859                                         plotBorder.crisp(null, plotLeft, plotTop, plotWidth, plotHeight)
7860                                 );
7861                         }
7862                 }
7863                 
7864                 // reset
7865                 chart.isDirtyBox = false;
7866         };
7867         
7868         /**
7869          * Render all graphics for the chart
7870          */
7871         function render () {
7872                 var labels = options.labels,
7873                         credits = options.credits,
7874                         creditsHref;
7875                 
7876                 // Title
7877                 setTitle();
7878                 
7879                 
7880                 // Legend
7881                 legend = chart.legend = new Legend(chart);
7882                 
7883                 // Get margins by pre-rendering axes
7884                 getMargins();
7885                 each(axes, function(axis) {
7886                         axis.setTickPositions(true); // update to reflect the new margins 
7887                 });
7888                 adjustTickAmounts();
7889                 getMargins(); // second pass to check for new labels
7890                 
7891                 
7892                 // Draw the borders and backgrounds
7893                 drawChartBox();
7894                                                 
7895                 // Axes
7896                 if (hasCartesianSeries) {
7897                         each(axes, function(axis) { 
7898                                 axis.render();
7899                         });
7900                 }
7901                 
7902                 
7903                 // The series
7904                 if (!chart.seriesGroup) {
7905                         chart.seriesGroup = renderer.g('series-group')
7906                                 .attr({ zIndex: 3 })
7907                                 .add();
7908                 }
7909                 each(series, function(serie) {
7910                         serie.translate();
7911                         serie.setTooltipPoints();
7912                         serie.render();
7913                 });
7914                 
7915                 
7916                 // Labels
7917                 if (labels.items) {
7918                         each(labels.items, function() {
7919                                 var style = extend(labels.style, this.style),
7920                                         x = pInt(style.left) + plotLeft,
7921                                         y = pInt(style.top) + plotTop + 12;
7922                                 
7923                                 // delete to prevent rewriting in IE
7924                                 delete style.left;
7925                                 delete style.top;
7926                                 
7927                                 renderer.text(
7928                                         this.html,
7929                                         x,
7930                                         y
7931                                 )
7932                                 .attr({ zIndex: 2 })
7933                                 .css(style)
7934                                 .add();
7935                                         
7936                         });
7937                 }
7938                 
7939                 // Toolbar (don't redraw)
7940                 if (!chart.toolbar) {
7941                         chart.toolbar = Toolbar(chart);
7942                 }
7943                 
7944                 // Credits
7945                 if (credits.enabled && !chart.credits) {
7946                         creditsHref = credits.href;
7947                         renderer.text(
7948                                 credits.text,
7949                                 0,
7950                                 0
7951                         )
7952                         .on('click', function() {
7953                                 if (creditsHref) {
7954                                         location.href = creditsHref;
7955                                 }
7956                         })
7957                         .attr({
7958                                 align: credits.position.align, 
7959                                 zIndex: 8
7960                         })
7961                         .css(credits.style)
7962                         .add()
7963                         .align(credits.position); 
7964                 }
7965                 
7966                 placeTrackerGroup();
7968                 // Set flag
7969                 chart.hasRendered = true;
7970                 
7971                 // If the chart was rendered outside the top container, put it back in
7972                 if (renderToClone) {
7973                         renderTo.appendChild(container);
7974                         discardElement(renderToClone);
7975                         //updatePosition(container);
7976                 }
7977         }
7978         
7979         /**
7980          * Clean up memory usage
7981          */
7982         function destroy() {
7983                 var i = series.length,
7984                         parentNode = container && container.parentNode;
7985                 
7986                 // fire the chart.destoy event
7987                 fireEvent(chart, 'destroy');
7989                 // remove events
7990                 removeEvent(win, 'unload', destroy);
7991                 removeEvent(chart);
7992                 
7993                 each(axes, function(axis) {
7994                         removeEvent(axis);
7995                 });
7997                 // destroy each series
7998                 while (i--) {
7999                         series[i].destroy();
8000                 }
8001                 
8002                 // remove container and all SVG
8003                 if (container) { // can break in IE when destroyed before finished loading
8004                         container.innerHTML = '';
8005                         removeEvent(container);
8006                         if (parentNode) {
8007                                 parentNode.removeChild(container);
8008                         }
8009                         
8010                         // IE6 leak 
8011                         container =     null;
8012                 }
8013                 
8014                 // IE7 leak
8015                 if (renderer) { // can break in IE when destroyed before finished loading
8016                         renderer.alignedObjects = null;
8017                 }
8018                         
8019                 // memory and CPU leak
8020                 clearInterval(tooltipInterval);
8021                 
8022                 // clean it all up
8023                 for (i in chart) {
8024                         delete chart[i];
8025                 }
8026                 
8027         }
8028         /**
8029          * Prepare for first rendering after all data are loaded
8030          */
8031         function firstRender() {
8033                 // VML namespaces can't be added until after complete. Listening
8034                 // for Perini's doScroll hack is not enough.
8035                 var ONREADYSTATECHANGE = 'onreadystatechange',
8036                         COMPLETE = 'complete';
8037                 // Note: in spite of JSLint's complaints, win == win.top is required
8038                 if (!hasSVG && win == win.top && doc.readyState !== COMPLETE) {
8039                         doc.attachEvent(ONREADYSTATECHANGE, function() {
8040                                 doc.detachEvent(ONREADYSTATECHANGE, firstRender);
8041                                 if (doc.readyState === COMPLETE) {
8042                                         firstRender();
8043                                 }
8044                         });
8045                         return;
8046                 }
8048                 // Set to zero for each new chart
8049                 colorCounter = 0;
8050                 symbolCounter = 0;
8052                 // create the container
8053                 getContainer();
8054                 
8055                 resetMargins();
8056                 setChartSize();
8057                 
8058                 // Initialize the series
8059                 each(options.series || [], function(serieOptions) {
8060                         initSeries(serieOptions);
8061                 });
8062         
8063                 // Set the common inversion and transformation for inverted series after initSeries
8064                 chart.inverted = inverted = pick(inverted, options.chart.inverted); 
8065                         
8066                 
8067                 getAxes();
8068                 
8069                 
8070                 chart.render = render;
8071                 
8072                 // depends on inverted and on margins being set 
8073                 chart.tracker = tracker = new MouseTracker(chart, options.tooltip);
8074                 
8075                 //globalAnimation = false;
8076                 render();
8077                 
8078                 fireEvent(chart, 'load');
8079                 
8080                 //globalAnimation = true;
8081                 
8082                 // run callbacks
8083                 if (callback) {
8084                         callback.apply(chart, [chart]);
8085                 }
8086                 each(chart.callbacks, function(fn) {
8087                         fn.apply(chart, [chart]);
8088                 });
8089         }
8090         
8091         // Run chart
8092                 
8093         
8094         // Destroy the chart and free up memory. 
8095         addEvent(win, 'unload', destroy);
8096         
8097         // Set up auto resize
8098         if (optionsChart.reflow !== false) {
8099                 addEvent(chart, 'load', initReflow);
8100         }
8101         
8102         // Chart event handlers
8103         if (chartEvents) {
8104                 for (eventType in chartEvents) { 
8105                         addEvent(chart, eventType, chartEvents[eventType]);
8106                 }
8107         }
8108         
8109         
8110         chart.options = options;
8111         chart.series = series;
8113         
8114         
8115         
8116         
8117         
8118         // Expose methods and variables
8119         chart.addSeries = addSeries;
8120         chart.animation = pick(optionsChart.animation, true);
8121         chart.destroy = destroy;
8122         chart.get = get;
8123         chart.getSelectedPoints = getSelectedPoints;
8124         chart.getSelectedSeries = getSelectedSeries;
8125         chart.hideLoading = hideLoading;
8126         chart.isInsidePlot = isInsidePlot;
8127         chart.redraw = redraw;
8128         chart.setSize = resize;
8129         chart.setTitle = setTitle;
8130         chart.showLoading = showLoading;        
8131         chart.pointCount = 0;
8132         /*
8133         if ($) $(function() {
8134                 $container = $('#container');
8135                 var origChartWidth,
8136                         origChartHeight;
8137                 if ($container) {
8138                         $('<button>+</button>')
8139                                 .insertBefore($container)
8140                                 .click(function() {
8141                                         if (origChartWidth === UNDEFINED) {
8142                                                 origChartWidth = chartWidth;
8143                                                 origChartHeight = chartHeight;
8144                                         }                               
8145                                         chart.resize(chartWidth *= 1.1, chartHeight *= 1.1);
8146                                 });
8147                         $('<button>-</button>')
8148                                 .insertBefore($container)
8149                                 .click(function() {
8150                                         if (origChartWidth === UNDEFINED) {
8151                                                 origChartWidth = chartWidth;
8152                                                 origChartHeight = chartHeight;
8153                                         }                                                       
8154                                         chart.resize(chartWidth *= 0.9, chartHeight *= 0.9);
8155                                 });
8156                         $('<button>1:1</button>')
8157                                 .insertBefore($container)
8158                                 .click(function() {                             
8159                                         if (origChartWidth === UNDEFINED) {
8160                                                 origChartWidth = chartWidth;
8161                                                 origChartHeight = chartHeight;
8162                                         }                                                       
8163                                         chart.resize(origChartWidth, origChartHeight);
8164                                 });
8165                 }
8166         })
8167         */
8168         
8169         
8170         
8171                 
8172         firstRender();
8173         
8174         
8175 } // end Chart
8177 // Hook for exporting module
8178 Chart.prototype.callbacks = [];
8180  * The Point object and prototype. Inheritable and used as base for PiePoint
8181  */
8182 var Point = function() {};
8183 Point.prototype = {
8185         /**
8186          * Initialize the point
8187          * @param {Object} series The series object containing this point
8188          * @param {Object} options The data in either number, array or object format
8189          */
8190         init: function(series, options) {
8191                 var point = this,
8192                         defaultColors;
8193                 point.series = series;
8194                 point.applyOptions(options);
8195                 point.pointAttr = {};
8196                 
8197                 if (series.options.colorByPoint) {
8198                         defaultColors = series.chart.options.colors;
8199                         if (!point.options) {
8200                                 point.options = {};
8201                         }
8202                         point.color = point.options.color = point.color || defaultColors[colorCounter++];
8203                         
8204                         // loop back to zero
8205                         if (colorCounter >= defaultColors.length) {
8206                                 colorCounter = 0;
8207                         }
8208                 }
8209                 
8210                 series.chart.pointCount++;
8211                 return point;
8212         },
8213         /**
8214          * Apply the options containing the x and y data and possible some extra properties.
8215          * This is called on point init or from point.update.
8216          * 
8217          * @param {Object} options
8218          */
8219         applyOptions: function(options) {
8220                 var point = this,
8221                         series = point.series;
8222         
8223                 point.config = options;
8224                 
8225                 // onedimensional array input
8226                 if (isNumber(options) || options === null) {
8227                         point.y = options;      
8228                 }
8229                 
8230                 // object input
8231                 else if (isObject(options) && !isNumber(options.length)) {
8232                         
8233                         // copy options directly to point
8234                         extend(point, options);
8235                         point.options = options;
8236                 }
8237                 
8238                 // categorized data with name in first position
8239                 else if (isString(options[0])) {
8240                         point.name = options[0];
8241                         point.y = options[1];
8242                 }
8243                 
8244                 // two-dimentional array
8245                 else if (isNumber(options[0])) {
8246                         point.x = options[0];
8247                         point.y = options[1];
8248                 }
8249                 
8250                 /* 
8251                  * If no x is set by now, get auto incremented value. All points must have an
8252                  * x value, however the y value can be null to create a gap in the series
8253                  */
8254                 if (point.x === UNDEFINED) {
8255                         point.x = series.autoIncrement();
8256                 }
8257                 
8258         },
8259         
8260         /**
8261          * Destroy a point to clear memory. Its reference still stays in series.data.
8262          */
8263         destroy: function() {
8264                 var point = this,
8265                         series = point.series,
8266                         prop;
8267                         
8268                 series.chart.pointCount--;
8269                         
8270                 if (point === series.chart.hoverPoint) {
8271                         point.onMouseOut();
8272                 }
8273                 series.chart.hoverPoints = null; // remove reference
8274                 
8275                 // remove all events
8276                 removeEvent(point);
8277                 
8278                 each(['graphic', 'tracker', 'group', 'dataLabel', 'connector'], function(prop) {
8279                         if (point[prop]) {
8280                                 point[prop].destroy();
8281                         }
8282                 });             
8283                 
8284                 if (point.legendItem) { // pies have legend items
8285                         point.series.chart.legend.destroyItem(point);
8286                 }
8287                 
8288                 for (prop in point) {
8289                         point[prop] = null;
8290                 }
8291                 
8292                 
8293         },
8294         
8295         /**
8296          * Return the configuration hash needed for the data label and tooltip formatters
8297          */
8298         getLabelConfig: function() {
8299                 var point = this;
8300                 return {
8301                         x: point.category,
8302                         y: point.y,
8303                         series: point.series,
8304                         point: point,
8305                         percentage: point.percentage,
8306                         total: point.total || point.stackTotal
8307                 };
8308         },
8309                 
8310         /**
8311          * Toggle the selection status of a point
8312          * @param {Boolean} selected Whether to select or unselect the point.
8313          * @param {Boolean} accumulate Whether to add to the previous selection. By default,
8314          *     this happens if the control key (Cmd on Mac) was pressed during clicking.
8315          */
8316         select: function(selected, accumulate) {
8317                 var point = this,
8318                         series = point.series,
8319                         chart = series.chart;
8320                         
8321                 point.selected = selected = pick(selected, !point.selected);
8322                 
8323                 //series.isDirty = true;
8324                 point.firePointEvent(selected ? 'select' : 'unselect');
8325                 point.setState(selected && SELECT_STATE);
8326                 
8327                 // unselect all other points unless Ctrl or Cmd + click
8328                 if (!accumulate) {
8329                         each(chart.getSelectedPoints(), function (loopPoint) {
8330                                 if (loopPoint.selected && loopPoint !== point) {
8331                                         loopPoint.selected = false;
8332                                         loopPoint.setState(NORMAL_STATE);
8333                                         loopPoint.firePointEvent('unselect');
8334                                 }
8335                         });
8336                 }
8337                 
8338         },
8339         
8340         onMouseOver: function() {
8341                 var point = this,
8342                         chart = point.series.chart,
8343                         tooltip = chart.tooltip,
8344                         hoverPoint = chart.hoverPoint;
8345                         
8346                 // set normal state to previous series
8347                 if (hoverPoint && hoverPoint !== point) {
8348                         hoverPoint.onMouseOut();
8349                 }
8350                 
8351                 // trigger the event
8352                 point.firePointEvent('mouseOver');
8353                 
8354                 // update the tooltip
8355                 if (tooltip && !tooltip.shared) {
8356                         tooltip.refresh(point);
8357                 }
8358                 
8359                 // hover this
8360                 point.setState(HOVER_STATE);
8361                 chart.hoverPoint = point;
8362         },
8363         
8364         onMouseOut: function() {
8365                 var point = this;
8366                 point.firePointEvent('mouseOut');
8367                 
8368                 point.setState();
8369                 point.series.chart.hoverPoint = null;
8370         },
8371         
8372         /**
8373          * Extendable method for formatting each point's tooltip line 
8374          * 
8375          * @param {Boolean} useHeader Whether a common header is used for multiple series in the tooltip
8376          * 
8377          * @return {String} A string to be concatenated in to the common tooltip text
8378          */
8379         tooltipFormatter: function(useHeader) {
8380                 var point = this,
8381                         series = point.series;
8382                                 
8383                 return ['<span style="color:'+ series.color +'">', (point.name || series.name), '</span>: ',
8384                         (!useHeader ? ('<b>x = '+ (point.name || point.x) + ',</b> ') : ''), 
8385                         '<b>', (!useHeader ? 'y = ' : '' ), point.y, '</b>'].join('');
8386                 
8387         },
8388         
8389         /**
8390          * Update the point with new options (typically x/y data) and optionally redraw the series.
8391          * 
8392          * @param {Object} options Point options as defined in the series.data array
8393          * @param {Boolean} redraw Whether to redraw the chart or wait for an explicit call
8394          * @param {Boolean|Object} animation Whether to apply animation, and optionally animation
8395          *    configuration
8396          * 
8397          */
8398         update: function(options, redraw, animation) {
8399                 var point = this,
8400                         series = point.series,
8401                         dataLabel = point.dataLabel,
8402                         graphic = point.graphic,
8403                         chart = series.chart;
8404                 
8405                 redraw = pick(redraw, true);
8406                 
8407                 // fire the event with a default handler of doing the update
8408                 point.firePointEvent('update', { options: options }, function() {
8410                         point.applyOptions(options);
8411                         
8412                         // update visuals
8413                         if (isObject(options)) {
8414                                 series.getAttribs();
8415                                 if (graphic) {
8416                                         graphic.attr(point.pointAttr[series.state]);
8417                                 }
8418                         }
8419                         
8420                         // redraw
8421                         series.isDirty = true;
8422                         if (redraw) {
8423                                 chart.redraw(animation);
8424                         }
8425                 });
8426         },
8427         
8428         /**
8429          * Remove a point and optionally redraw the series and if necessary the axes
8430          * @param {Boolean} redraw Whether to redraw the chart or wait for an explicit call
8431          * @param {Boolean|Object} animation Whether to apply animation, and optionally animation
8432          *    configuration
8433          */
8434         remove: function(redraw, animation) {
8435                 var point = this,
8436                         series = point.series,
8437                         chart = series.chart,
8438                         data = series.data;
8439                 
8440                 setAnimation(animation, chart);
8441                 redraw = pick(redraw, true);
8442                 
8443                 // fire the event with a default handler of removing the point                  
8444                 point.firePointEvent('remove', null, function() {
8446                         erase(data, point);
8447                         
8448                         point.destroy();
8449                         
8450                         
8451                         // redraw
8452                         series.isDirty = true;
8453                         if (redraw) {
8454                                 chart.redraw();
8455                         }
8456                 });
8457                         
8458                 
8459         },
8460         
8461         /**
8462          * Fire an event on the Point object. Must not be renamed to fireEvent, as this
8463          * causes a name clash in MooTools
8464          * @param {String} eventType
8465          * @param {Object} eventArgs Additional event arguments
8466          * @param {Function} defaultFunction Default event handler
8467          */
8468         firePointEvent: function(eventType, eventArgs, defaultFunction) {
8469                 var point = this,
8470                         series = this.series,
8471                         seriesOptions = series.options;
8472                 
8473                 // load event handlers on demand to save time on mouseover/out
8474                 if (seriesOptions.point.events[eventType] || (
8475                                 point.options && point.options.events && point.options.events[eventType])) {
8476                         this.importEvents();
8477                 }
8478                         
8479                 // add default handler if in selection mode
8480                 if (eventType === 'click' && seriesOptions.allowPointSelect) {
8481                         defaultFunction = function (event) {
8482                                 // Control key is for Windows, meta (= Cmd key) for Mac, Shift for Opera
8483                                 point.select(null, event.ctrlKey || event.metaKey || event.shiftKey);
8484                         };
8485                 }
8486                         
8487                 fireEvent(this, eventType, eventArgs, defaultFunction);
8488         },
8489         /**
8490          * Import events from the series' and point's options. Only do it on 
8491          * demand, to save processing time on hovering.
8492          */
8493         importEvents: function() {
8494                 if (!this.hasImportedEvents) {
8495                         var point = this,
8496                                 options = merge(point.series.options.point, point.options),
8497                                 events = options.events,
8498                                 eventType;
8499                                 
8500                         point.events = events;
8501                         
8502                         for (eventType in events) {
8503                                 addEvent(point, eventType, events[eventType]);
8504                         }
8505                         this.hasImportedEvents = true;
8506                         
8507                 }
8508         },
8509         
8510         /**
8511          * Set the point's state
8512          * @param {String} state
8513          */
8514         setState: function(state) {
8515                 var point = this,
8516                         series = point.series,
8517                         stateOptions = series.options.states,
8518                         markerOptions = defaultPlotOptions[series.type].marker && series.options.marker,
8519                         normalDisabled = markerOptions && !markerOptions.enabled,
8520                         markerStateOptions = markerOptions && markerOptions.states[state],
8521                         stateDisabled = markerStateOptions && markerStateOptions.enabled === false,
8522                         stateMarkerGraphic = series.stateMarkerGraphic,
8523                         chart = series.chart,
8524                         pointAttr = point.pointAttr;
8525                         
8526                 state = state || NORMAL_STATE; // empty string
8527                 
8528                 if (
8529                                 // already has this state
8530                                 state === point.state ||
8531                                 // selected points don't respond to hover
8532                                 (point.selected && state !== SELECT_STATE) ||
8533                                 // series' state options is disabled
8534                                 (stateOptions[state] && stateOptions[state].enabled === false) ||
8535                                 // point marker's state options is disabled
8536                                 (state && (stateDisabled || (normalDisabled && !markerStateOptions.enabled)))
8538                         ) {
8539                         return;
8540                 }
8541                 
8542                 // apply hover styles to the existing point
8543                 if (point.graphic) {
8544                         point.graphic.attr(pointAttr[state]);
8545                 }
8546                 // if a graphic is not applied to each point in the normal state, create a shared
8547                 // graphic for the hover state
8548                 else {
8549                         if (state) {
8550                                 if (!stateMarkerGraphic) {
8551                                         series.stateMarkerGraphic = stateMarkerGraphic = chart.renderer.circle(
8552                                                 0, 0, pointAttr[state].r
8553                                         )
8554                                         .attr(pointAttr[state])
8555                                         .add(series.group);
8556                                 }
8557                                 
8558                                 stateMarkerGraphic.translate(
8559                                         point.plotX, 
8560                                         point.plotY
8561                                 );
8562                         }
8563                         
8564                         if (stateMarkerGraphic) {
8565                                 stateMarkerGraphic[state ? 'show' : 'hide']();
8566                         }
8567                 }
8568                 
8569                 point.state = state;
8570         }
8574  * The base function which all other series types inherit from
8575  * @param {Object} chart
8576  * @param {Object} options
8577  */
8578 var Series = function() {};
8580 Series.prototype = {
8581         
8582         isCartesian: true,
8583         type: 'line',
8584         pointClass: Point,
8585         pointAttrToOptions: { // mapping between SVG attributes and the corresponding options
8586                 stroke: 'lineColor',
8587                 'stroke-width': 'lineWidth',
8588                 fill: 'fillColor',
8589                 r: 'radius'
8590         },
8591         init: function(chart, options) {
8592                 var series = this,
8593                         eventType,
8594                         events,
8595                         //pointEvent,
8596                         index = chart.series.length;
8597                         
8598                 series.chart = chart;
8599                 options = series.setOptions(options); // merge with plotOptions
8600                 
8601                 // set some variables
8602                 extend(series, {
8603                         index: index,
8604                         options: options,
8605                         name: options.name || 'Series '+ (index + 1),
8606                         state: NORMAL_STATE,
8607                         pointAttr: {},
8608                         visible: options.visible !== false, // true by default
8609                         selected: options.selected === true // false by default
8610                 });
8611                 
8612                 // register event listeners
8613                 events = options.events;
8614                 for (eventType in events) {
8615                         addEvent(series, eventType, events[eventType]);
8616                 }
8617                 if (
8618                         (events && events.click) || 
8619                         (options.point && options.point.events && options.point.events.click) ||
8620                         options.allowPointSelect 
8621                 ) {
8622                         chart.runTrackerClick = true;
8623                 }
8624                 
8625                 series.getColor();
8626                 series.getSymbol();
8627                 
8628                 
8629                 // set the data
8630                 series.setData(options.data, false);
8631                         
8632         },
8633         
8634         
8635         /**
8636          * Return an auto incremented x value based on the pointStart and pointInterval options. 
8637          * This is only used if an x value is not given for the point that calls autoIncrement.
8638          */
8639         autoIncrement: function() {
8640                 var series = this,
8641                         options = series.options,
8642                         xIncrement = series.xIncrement;
8643                         
8644                 xIncrement = pick(xIncrement, options.pointStart, 0);
8645                 
8646                 series.pointInterval = pick(series.pointInterval, options.pointInterval, 1);
8647                 
8648                 series.xIncrement = xIncrement + series.pointInterval;
8649                 return xIncrement;
8650         },
8651         
8652         /**
8653          * Sort the data and remove duplicates 
8654          */
8655         cleanData: function() {
8656                 var series = this,
8657                         chart = series.chart,
8658                         data = series.data,
8659                         closestPoints,
8660                         smallestInterval,
8661                         chartSmallestInterval = chart.smallestInterval,
8662                         interval,
8663                         i;
8664                         
8665                 // sort the data points
8666                 data.sort(function(a, b){
8667                         return (a.x - b.x);
8668                 });
8669                 
8670                 // remove points with equal x values
8671                 // record the closest distance for calculation of column widths
8672                 /*for (i = data.length - 1; i >= 0; i--) {
8673                         if (data[i - 1]) {
8674                                 if (data[i - 1].x == data[i].x) {
8675                                         data[i - 1].destroy();
8676                                         data.splice(i - 1, 1); // remove the duplicate
8677                                 }
8678                         }
8679                 }*/
8680                 
8681                 // connect nulls
8682                 if (series.options.connectNulls) {
8683                         for (i = data.length - 1; i >= 0; i--) {
8684                                 if (data[i].y === null && data[i - 1] && data[i + 1]) {
8685                                         data.splice(i, 1);
8686                                 }
8687                         }
8688                 }
8689                 
8690                 // find the closes pair of points
8691                 for (i = data.length - 1; i >= 0; i--) {
8692                         if (data[i - 1]) {
8693                                 interval = data[i].x - data[i - 1].x;
8694                                 if (interval > 0 && (smallestInterval === UNDEFINED || interval < smallestInterval)) {
8695                                         smallestInterval = interval;
8696                                         closestPoints = i;      
8697                                 }
8698                         }
8699                 }
8700                 
8701                 if (chartSmallestInterval === UNDEFINED || smallestInterval < chartSmallestInterval) {
8702                         chart.smallestInterval = smallestInterval;
8703                 }
8704                 series.closestPoints = closestPoints;
8705         },              
8706                 
8707         /**
8708          * Divide the series data into segments divided by null values. Also sort
8709          * the data points and delete duplicate values.
8710          */
8711         getSegments: function() {
8712                 var lastNull = -1,
8713                         segments = [],
8714                         data = this.data;
8715                 
8716                 // create the segments
8717                 each(data, function(point, i) {
8718                         if (point.y === null) {
8719                                 if (i > lastNull + 1) {
8720                                         segments.push(data.slice(lastNull + 1, i));
8721                                 }
8722                                 lastNull = i;   
8723                         } else if (i === data.length - 1) { // last value
8724                                 segments.push(data.slice(lastNull + 1, i + 1));
8725                         }
8726                 });
8727                 this.segments = segments;
8728                 
8729                 
8730         },
8731         /**
8732          * Set the series options by merging from the options tree
8733          * @param {Object} itemOptions
8734          */
8735         setOptions: function(itemOptions) {
8736                 var plotOptions = this.chart.options.plotOptions,
8737                         options = merge(
8738                                 plotOptions[this.type],
8739                                 plotOptions.series,
8740                                 itemOptions
8741                         );
8742                 
8743                 return options;
8744                 
8745         },
8746         /**
8747          * Get the series' color
8748          */
8749         getColor: function(){
8750                 var defaultColors = this.chart.options.colors;
8751                 this.color = this.options.color || defaultColors[colorCounter++] || '#0000ff';
8752                 if (colorCounter >= defaultColors.length) {
8753                         colorCounter = 0;
8754                 }
8755         },
8756         /**
8757          * Get the series' symbol
8758          */
8759         getSymbol: function(){
8760                 var defaultSymbols = this.chart.options.symbols,
8761                         symbol = this.options.marker.symbol || defaultSymbols[symbolCounter++];
8762                 this.symbol = symbol;
8763                 if (symbolCounter >= defaultSymbols.length) { 
8764                         symbolCounter = 0;
8765                 }
8766         },
8767         
8768         /**
8769          * Add a point dynamically after chart load time
8770          * @param {Object} options Point options as given in series.data
8771          * @param {Boolean} redraw Whether to redraw the chart or wait for an explicit call
8772          * @param {Boolean} shift If shift is true, a point is shifted off the start 
8773          *    of the series as one is appended to the end.
8774          * @param {Boolean|Object} animation Whether to apply animation, and optionally animation
8775          *    configuration
8776          */
8777         addPoint: function(options, redraw, shift, animation) {
8778                 var series = this,
8779                         data = series.data,
8780                         graph = series.graph,
8781                         area = series.area,
8782                         chart = series.chart,
8783                         point = (new series.pointClass()).init(series, options);
8784                         
8785                 setAnimation(animation, chart);
8786                 
8787                 if (graph && shift) { // make graph animate sideways
8788                         graph.shift = shift;
8789                 }
8790                 if (area) {
8791                         area.shift = shift;
8792                         area.isArea = true;
8793                 }
8794                         
8795                 redraw = pick(redraw, true);
8796                         
8797                 data.push(point);
8798                 if (shift) {
8799                         data[0].remove(false);
8800                 }
8801                 series.getAttribs();
8802                 
8803                 
8804                 // redraw
8805                 series.isDirty = true;
8806                 if (redraw) {
8807                         chart.redraw();
8808                 }
8809         },
8810         
8811         /**
8812          * Replace the series data with a new set of data
8813          * @param {Object} data
8814          * @param {Object} redraw
8815          */
8816         setData: function(data, redraw) {
8817                 var series = this,
8818                         oldData = series.data,
8819                         initialColor = series.initialColor,
8820                         chart = series.chart,
8821                         i = (oldData && oldData.length) || 0;
8822                 
8823                 series.xIncrement = null; // reset for new data
8824                 if (defined(initialColor)) { // reset colors for pie
8825                         colorCounter = initialColor;
8826                 }
8827                 
8828                 data = map(splat(data || []), function(pointOptions) {
8829                         return (new series.pointClass()).init(series, pointOptions);
8830                 });
8831                 
8832                 // destroy old points
8833                 while (i--) {
8834                         oldData[i].destroy();
8835                 }
8836                 
8837                 // set the data
8838                 series.data = data;
8839         
8840                 series.cleanData();     
8841                 series.getSegments();
8842                 
8843                 
8844                 // cache attributes for shapes
8845                 series.getAttribs();
8846                 
8847                 // redraw
8848                 series.isDirty = true;
8849                 chart.isDirtyBox = true;
8850                 if (pick(redraw, true)) {
8851                         chart.redraw(false);
8852                 }
8853         },
8854         
8855         /**
8856          * Remove a series and optionally redraw the chart
8857          * 
8858          * @param {Boolean} redraw Whether to redraw the chart or wait for an explicit call
8859          * @param {Boolean|Object} animation Whether to apply animation, and optionally animation
8860          *    configuration
8861          */
8862         
8863         remove: function(redraw, animation) {
8864                 var series = this,
8865                         chart = series.chart;
8866                 redraw = pick(redraw, true);
8867                 
8868                 if (!series.isRemoving) {  /* prevent triggering native event in jQuery
8869                                 (calling the remove function from the remove event) */ 
8870                         series.isRemoving = true;
8872                         // fire the event with a default handler of removing the point                  
8873                         fireEvent(series, 'remove', null, function() {
8874                                 
8875                                                 
8876                                 // destroy elements
8877                                 series.destroy();
8878                         
8879                                 
8880                                 // redraw
8881                                 chart.isDirtyLegend = chart.isDirtyBox = true;
8882                                 if (redraw) {
8883                                         chart.redraw(animation);
8884                                 }
8885                         });
8886                         
8887                 } 
8888                 series.isRemoving = false;
8889         },
8890         
8891         /**
8892          * Translate data points from raw data values to chart specific positioning data
8893          * needed later in drawPoints, drawGraph and drawTracker. 
8894          */
8895         translate: function() {
8896                 var series = this, 
8897                         chart = series.chart, 
8898                         stacking = series.options.stacking,
8899                         categories = series.xAxis.categories,
8900                         yAxis = series.yAxis,
8901                         data = series.data,                     
8902                         i = data.length;
8903                         
8904                 // do the translation
8905                 while (i--) {
8906                         var point = data[i],
8907                                 xValue = point.x, 
8908                                 yValue = point.y, 
8909                                 yBottom = point.low,
8910                                 stack = yAxis.stacks[(yValue < 0 ? '-' : '') + series.stackKey],
8911                                 pointStack,
8912                                 pointStackTotal;
8913                         point.plotX = series.xAxis.translate(xValue);
8914                         
8915                         // calculate the bottom y value for stacked series
8916                         if (stacking && series.visible && stack && stack[xValue]) {
8917                                 pointStack = stack[xValue];
8918                                 pointStackTotal = pointStack.total;
8919                                 pointStack.cum = yBottom = pointStack.cum - yValue; // start from top
8920                                 yValue = yBottom + yValue;
8921                                 
8922                                 if (stacking === 'percent') {
8923                                         yBottom = pointStackTotal ? yBottom * 100 / pointStackTotal : 0;
8924                                         yValue = pointStackTotal ? yValue * 100 / pointStackTotal : 0;
8925                                 }
8927                                 point.percentage = pointStackTotal ? point.y * 100 / pointStackTotal : 0;
8928                                 point.stackTotal = pointStackTotal;
8929                         }
8930                         
8931                         if (defined(yBottom)) {
8932                                 point.yBottom = yAxis.translate(yBottom, 0, 1, 0, 1);
8933                         }
8934                         
8935                         // set the y value
8936                         if (yValue !== null) {
8937                                 point.plotY = yAxis.translate(yValue, 0, 1, 0, 1);
8938                         }
8939                         
8940                         // set client related positions for mouse tracking
8941                         point.clientX = chart.inverted ? 
8942                                 chart.plotHeight - point.plotX : 
8943                                 point.plotX; // for mouse tracking
8944                                 
8945                         // some API data
8946                         point.category = categories && categories[point.x] !== UNDEFINED ? 
8947                                 categories[point.x] : point.x;
8948                                 
8949                 }
8950         },
8951         /**
8952          * Memoize tooltip texts and positions
8953          */
8954         setTooltipPoints: function (renew) {
8955                 var series = this,
8956                         chart = series.chart,
8957                         inverted = chart.inverted,
8958                         data = [],
8959                         plotSize = mathRound((inverted ? chart.plotTop : chart.plotLeft) + chart.plotSizeX),
8960                         low,
8961                         high,
8962                         tooltipPoints = []; // a lookup array for each pixel in the x dimension
8963                         
8964                 // renew
8965                 if (renew) {
8966                         series.tooltipPoints = null;
8967                 }
8968                         
8969                 // concat segments to overcome null values
8970                 each(series.segments, function(segment){
8971                         data = data.concat(segment);
8972                 });
8973                 
8974                 // loop the concatenated data and apply each point to all the closest
8975                 // pixel positions
8976                 if (series.xAxis && series.xAxis.reversed) {
8977                         data = data.reverse();//reverseArray(data);
8978                 }
8979                 
8980                 each(data, function(point, i) {
8981                         
8982                         low = data[i - 1] ? data[i - 1]._high + 1 : 0;
8983                         high = point._high = data[i + 1] ? (
8984                                 mathFloor((point.plotX + (data[i + 1] ? 
8985                                         data[i + 1].plotX : plotSize)) / 2)) :
8986                                         plotSize;
8987                         
8988                         while (low <= high) {
8989                                 tooltipPoints[inverted ? plotSize - low++ : low++] = point;
8990                         }
8991                 });
8992                 series.tooltipPoints = tooltipPoints;
8993         },
8994         
8995         
8997         
8998         /**
8999          * Series mouse over handler
9000          */
9001         onMouseOver: function() {
9002                 var series = this,
9003                         chart = series.chart,
9004                         hoverSeries = chart.hoverSeries;
9005                         
9006                 if (!hasTouch && chart.mouseIsDown) {
9007                         return;
9008                 }
9009                 
9010                 // set normal state to previous series
9011                 if (hoverSeries && hoverSeries !== series) {
9012                         hoverSeries.onMouseOut();
9013                 }
9014                 
9015                 // trigger the event, but to save processing time, 
9016                 // only if defined
9017                 if (series.options.events.mouseOver) { 
9018                         fireEvent(series, 'mouseOver');
9019                 }
9020                 
9021                 
9022                 // bring to front
9023                 // Todo: optimize. This is one of two operations slowing down the tooltip in Firefox.
9024                 // Can the tracking be done otherwise?
9025                 if (series.tracker) {
9026                         series.tracker.toFront();
9027                 }
9028                 
9029                 // hover this
9030                 series.setState(HOVER_STATE);
9031                 chart.hoverSeries = series;
9032         },
9033         
9034         /**
9035          * Series mouse out handler
9036          */
9037         onMouseOut: function() {
9038                 // trigger the event only if listeners exist
9039                 var series = this,
9040                         options = series.options,
9041                         chart = series.chart,
9042                         tooltip = chart.tooltip,
9043                         hoverPoint = chart.hoverPoint;
9044                 
9045                 // trigger mouse out on the point, which must be in this series
9046                 if (hoverPoint) {
9047                         hoverPoint.onMouseOut();
9048                 }               
9049                 
9050                 // fire the mouse out event
9051                 if (series && options.events.mouseOut) { 
9052                         fireEvent(series, 'mouseOut');
9053                 }
9054                 
9055                 
9056                 // hide the tooltip
9057                 if (tooltip && !options.stickyTracking) {
9058                         tooltip.hide();
9059                 }
9060                 
9061                 // set normal state
9062                 series.setState();
9063                 chart.hoverSeries = null;               
9064         },
9065         
9066         /**
9067          * Animate in the series
9068          */
9069         animate: function(init) {
9070                 var series = this,
9071                         chart = series.chart,
9072                         clipRect = series.clipRect,
9073                         animation = series.options.animation;
9074                         
9075                 if (animation && !isObject(animation)) {
9076                         animation = {};
9077                 }
9078                         
9079                 if (init) { // initialize the animation
9080                         if (!clipRect.isAnimating) { // apply it only for one of the series
9081                                 clipRect.attr( 'width', 0 );
9082                                 clipRect.isAnimating = true;
9083                         }
9084                         
9085                 } else { // run the animation
9086                         clipRect.animate({ 
9087                                 width: chart.plotSizeX 
9088                         }, animation);
9089                         
9090                         // delete this function to allow it only once
9091                         this.animate = null;
9092                 }
9093         },
9094         
9095         
9096         /**
9097          * Draw the markers
9098          */
9099         drawPoints: function(){
9100                 var series = this,
9101                         pointAttr,
9102                         data = series.data, 
9103                         chart = series.chart,
9104                         plotX,
9105                         plotY,
9106                         i,
9107                         point,
9108                         radius,
9109                         graphic;
9110                 
9111                 if (series.options.marker.enabled) {
9112                         i = data.length;
9113                         while (i--) {
9114                                 point = data[i];
9115                                 plotX = point.plotX;
9116                                 plotY = point.plotY;
9117                                 graphic = point.graphic;
9118                                 
9119                                 // only draw the point if y is defined
9120                                 if (plotY !== UNDEFINED && !isNaN(plotY)) {
9121                                 
9122                                         /* && removed this code because points stayed after zoom
9123                                                 point.plotX >= 0 && point.plotX <= chart.plotSizeX &&
9124                                                 point.plotY >= 0 && point.plotY <= chart.plotSizeY*/
9125                                         
9126                                         // shortcuts
9127                                         pointAttr = point.pointAttr[point.selected ? SELECT_STATE : NORMAL_STATE];
9128                                         radius = pointAttr.r;
9129                                         
9130                                         if (graphic) { // update
9131                                                 graphic.animate({
9132                                                         x: plotX,
9133                                                         y: plotY,
9134                                                         r: radius
9135                                                 });
9136                                         } else {
9137                                                 point.graphic = chart.renderer.symbol(
9138                                                         pick(point.marker && point.marker.symbol, series.symbol),
9139                                                         plotX,
9140                                                         plotY, 
9141                                                         radius
9142                                                 )
9143                                                 .attr(pointAttr)
9144                                                 .add(series.group);
9145                                         }
9146                                 }
9147                         }
9148                 }
9149                 
9150         },
9151         
9152         /**
9153          * Convert state properties from API naming conventions to SVG attributes
9154          * 
9155          * @param {Object} options API options object
9156          * @param {Object} base1 SVG attribute object to inherit from
9157          * @param {Object} base2 Second level SVG attribute object to inherit from
9158          */
9159         convertAttribs: function(options, base1, base2, base3) {
9160                 var conversion = this.pointAttrToOptions,
9161                         attr,
9162                         option,
9163                         obj = {};
9164                 
9165                 options = options || {};
9166                 base1 = base1 || {};
9167                 base2 = base2 || {};
9168                 base3 = base3 || {};
9169                 
9170                 for (attr in conversion) {
9171                         option = conversion[attr];
9172                         obj[attr] = pick(options[option], base1[attr], base2[attr], base3[attr]);                
9173                 }
9174                 return obj;
9175         },
9176         
9177         /**
9178          * Get the state attributes. Each series type has its own set of attributes
9179          * that are allowed to change on a point's state change. Series wide attributes are stored for
9180          * all series, and additionally point specific attributes are stored for all 
9181          * points with individual marker options. If such options are not defined for the point,
9182          * a reference to the series wide attributes is stored in point.pointAttr.
9183          */
9184         getAttribs: function() {
9185                 var series = this, 
9186                         normalOptions = defaultPlotOptions[series.type].marker ? series.options.marker : series.options,
9187                         stateOptions = normalOptions.states,
9188                         stateOptionsHover = stateOptions[HOVER_STATE],
9189                         pointStateOptionsHover,
9190                         seriesColor = series.color,
9191                         normalDefaults = {
9192                                 stroke: seriesColor,
9193                                 fill: seriesColor
9194                         },
9195                         data = series.data,
9196                         i,
9197                         point,
9198                         seriesPointAttr = [],
9199                         pointAttr,
9200                         pointAttrToOptions = series.pointAttrToOptions,
9201                         hasPointSpecificOptions,
9202                         key;
9203                         
9204                 // series type specific modifications
9205                 if (series.options.marker) { // line, spline, area, areaspline, scatter
9206                         
9207                         // if no hover radius is given, default to normal radius + 2  
9208                         stateOptionsHover.radius = stateOptionsHover.radius || normalOptions.radius + 2;
9209                         stateOptionsHover.lineWidth = stateOptionsHover.lineWidth || normalOptions.lineWidth + 1;
9210                         
9211                 } else { // column, bar, pie
9212                         
9213                         // if no hover color is given, brighten the normal color
9214                         stateOptionsHover.color = stateOptionsHover.color || 
9215                                 Color(stateOptionsHover.color || seriesColor)
9216                                         .brighten(stateOptionsHover.brightness).get();
9217                 }
9218                 
9219                 // general point attributes for the series normal state
9220                 seriesPointAttr[NORMAL_STATE] = series.convertAttribs(normalOptions, normalDefaults);
9221                 
9222                 // HOVER_STATE and SELECT_STATE states inherit from normal state except the default radius
9223                 each([HOVER_STATE, SELECT_STATE], function(state) {
9224                         seriesPointAttr[state] = 
9225                                         series.convertAttribs(stateOptions[state], seriesPointAttr[NORMAL_STATE]);
9226                 });
9227                                 
9228                 // set it
9229                 series.pointAttr = seriesPointAttr;
9230                 
9231                 
9232                 // Generate the point-specific attribute collections if specific point
9233                 // options are given. If not, create a referance to the series wide point 
9234                 // attributes
9235                 i = data.length;
9236                 while (i--) {
9237                         point = data[i];
9238                         normalOptions = (point.options && point.options.marker) || point.options;
9239                         if (normalOptions && normalOptions.enabled === false) {
9240                                 normalOptions.radius = 0;
9241                         }
9242                         hasPointSpecificOptions = false;
9243                         
9244                         // check if the point has specific visual options
9245                         if (point.options) {
9246                                 for (key in pointAttrToOptions) {
9247                                         if (defined(normalOptions[pointAttrToOptions[key]])) {
9248                                                 hasPointSpecificOptions = true;
9249                                         }
9250                                 }
9251                         }
9252                         
9253                         
9254                         
9255                         // a specific marker config object is defined for the individual point:
9256                         // create it's own attribute collection
9257                         if (hasPointSpecificOptions) {
9259                                 pointAttr = [];
9260                                 stateOptions = normalOptions.states || {}; // reassign for individual point
9261                                 pointStateOptionsHover = stateOptions[HOVER_STATE] = stateOptions[HOVER_STATE] || {};
9262                                 
9263                                 // if no hover color is given, brighten the normal color
9264                                 if (!series.options.marker) { // column, bar, point
9265                                         pointStateOptionsHover.color = 
9266                                                 Color(pointStateOptionsHover.color || point.options.color)
9267                                                         .brighten(pointStateOptionsHover.brightness || 
9268                                                                 stateOptionsHover.brightness).get();
9269                                 
9270                                 }
9271                                 
9272                                 // normal point state inherits series wide normal state
9273                                 pointAttr[NORMAL_STATE] = series.convertAttribs(normalOptions, seriesPointAttr[NORMAL_STATE]);
9274                                                                         
9275                                 // inherit from point normal and series hover
9276                                 pointAttr[HOVER_STATE] = series.convertAttribs(
9277                                         stateOptions[HOVER_STATE],
9278                                         seriesPointAttr[HOVER_STATE],
9279                                         pointAttr[NORMAL_STATE]
9280                                 );
9281                                 // inherit from point normal and series hover
9282                                 pointAttr[SELECT_STATE] = series.convertAttribs(
9283                                         stateOptions[SELECT_STATE],
9284                                         seriesPointAttr[SELECT_STATE],
9285                                         pointAttr[NORMAL_STATE]
9286                                 );
9287                                 
9288                                 
9289                                 
9290                         // no marker config object is created: copy a reference to the series-wide
9291                         // attribute collection
9292                         } else {
9293                                 pointAttr = seriesPointAttr;
9294                         }
9295                 
9296                         point.pointAttr = pointAttr;
9298                 }
9300         },
9302         
9303         /**
9304          * Clear DOM objects and free up memory
9305          */
9306         destroy: function() {
9307                 var series = this,
9308                         chart = series.chart,
9309                         //chartSeries = series.chart.series,
9310                         clipRect = series.clipRect,
9311                         issue134 = /\/5[0-9\.]+ (Safari|Mobile)\//.test(userAgent), // todo: update when Safari bug is fixed
9312                         destroy,
9313                         prop;
9314                 
9315                 // remove all events
9316                 removeEvent(series);
9317                         
9318                 // remove legend items
9319                 if (series.legendItem) {
9320                         series.chart.legend.destroyItem(series);
9321                 }
9322                 
9323                 // destroy all points with their elements
9324                 each(series.data, function(point) {
9325                         point.destroy();
9326                 });
9327                 // destroy all SVGElements associated to the series
9328                 each(['area', 'graph', 'dataLabelsGroup', 'group', 'tracker'], function(prop) {
9329                         if (series[prop]) {
9330                                 
9331                                 // issue 134 workaround
9332                                 destroy = issue134 && prop === 'group' ?
9333                                         'hide' :
9334                                         'destroy';
9335                                         
9336                                 series[prop][destroy]();
9337                         }
9338                 });
9339                 
9340                 // remove from hoverSeries
9341                 if (chart.hoverSeries === series) {
9342                         chart.hoverSeries = null;
9343                 }
9344                 erase(chart.series, series);
9345                                 
9346                 // clear all members
9347                 for (prop in series) {
9348                         delete series[prop];
9349                 } 
9350         },
9351         
9352         /**
9353          * Draw the data labels
9354          */
9355         drawDataLabels: function() {
9356                 if (this.options.dataLabels.enabled) {
9357                         var series = this,
9358                                 x, 
9359                                 y, 
9360                                 data = series.data, 
9361                                 options = series.options.dataLabels,
9362                                 str, 
9363                                 dataLabelsGroup = series.dataLabelsGroup, 
9364                                 chart = series.chart, 
9365                                 inverted = chart.inverted,
9366                                 seriesType = series.type,
9367                                 color,
9368                                 stacking = series.options.stacking,
9369                                 isBarLike = seriesType === 'column' || seriesType === 'bar',
9370                                 vAlignIsNull = options.verticalAlign === null,
9371                                 yIsNull = options.y === null;
9373                         if (isBarLike) {
9374                                 if (stacking) {
9375                                         // In stacked series the default label placement is inside the bars
9376                                         if (vAlignIsNull) {
9377                                                 options = merge(options, {verticalAlign: 'middle'});
9378                                         }
9380                                         // If no y delta is specified, try to create a good default
9381                                         if (yIsNull) {
9382                                                 options = merge(options, {y: {top: 14, middle: 4, bottom: -6}[options.verticalAlign]}); 
9383                                         }
9384                                 } else {
9385                                         // In non stacked series the default label placement is on top of the bars
9386                                         if (vAlignIsNull) {
9387                                                 options = merge(options, {verticalAlign: 'top'});
9388                                         }
9390                                         // If no y delta is specified, set the default
9391                                         if (yIsNull) {
9392                                                 options = merge(options, {y: -6}); 
9393                                         }
9394                                 }
9395                         }
9397                         // create a separate group for the data labels to avoid rotation
9398                         if (!dataLabelsGroup) {
9399                                 dataLabelsGroup = series.dataLabelsGroup = 
9400                                         chart.renderer.g('data-labels')
9401                                                 .attr({ 
9402                                                         visibility: series.visible ? VISIBLE : HIDDEN,
9403                                                         zIndex: 6                                                       
9404                                                 })
9405                                                 .translate(chart.plotLeft, chart.plotTop)
9406                                                 .add();
9407                         }
9408                 
9409                         // determine the color
9410                         color = options.color;
9411                         if (color === 'auto') { // 1.0 backwards compatibility
9412                                 color = null;   
9413                         }
9414                         options.style.color = pick(color, series.color);
9415                 
9416                         // make the labels for each point
9417                         each(data, function(point, i){
9418                                 var barX = point.barX,
9419                                         plotX = (barX && barX + point.barW / 2) || point.plotX || -999,
9420                                         plotY = pick(point.plotY, -999),
9421                                         dataLabel = point.dataLabel,
9422                                         align = options.align;
9423                                         
9424                                 // get the string
9425                                 str = options.formatter.call(point.getLabelConfig());
9426                                 x = (inverted ? chart.plotWidth - plotY : plotX) + options.x;
9427                                 y = (inverted ? chart.plotHeight - plotX : plotY) + options.y;
9428                                 
9429                                 // in columns, align the string to the column
9430                                 if (seriesType === 'column') {
9431                                         x += { left: -1, right: 1 }[align] * point.barW / 2 || 0;
9432                                 }
9433                                 
9434                                 // update existing label
9435                                 if (dataLabel) {
9436                                         // vertically centered
9437                                         if (inverted && !options.y) {
9438                                                 y = y + pInt(dataLabel.styles.lineHeight) * 0.9 - dataLabel.getBBox().height / 2;
9439                                         }
9440                                         dataLabel
9441                                                 .attr({
9442                                                         text: str
9443                                                 }).animate({
9444                                                         x: x,
9445                                                         y: y
9446                                                 });
9447                                 // create new label
9448                                 } else if (defined(str)) {
9449                                         dataLabel = point.dataLabel = chart.renderer.text(
9450                                                 str, 
9451                                                 x, 
9452                                                 y
9453                                         )
9454                                         .attr({
9455                                                 align: align,
9456                                                 rotation: options.rotation,
9457                                                 zIndex: 1
9458                                         })
9459                                         .css(options.style)
9460                                         .add(dataLabelsGroup);
9461                                         // vertically centered
9462                                         if (inverted && !options.y) {
9463                                                 dataLabel.attr({
9464                                                         y: y + pInt(dataLabel.styles.lineHeight) * 0.9 - dataLabel.getBBox().height / 2
9465                                                 });
9466                                         }
9467                                 }
9468                                 
9469                                 
9470                                 /*if (series.isCartesian) {
9471                                         dataLabel[chart.isInsidePlot(plotX, plotY) ? 'show' : 'hide']();
9472                                 }*/
9474                                 if (isBarLike && series.options.stacking) {
9475                                         var barY = point.barY,
9476                                                 barW = point.barW,
9477                                                 barH = point.barH;
9479                                         dataLabel.align(options, null, 
9480                                                 {
9481                                                         x: inverted ? chart.plotWidth - barY - barH : barX,
9482                                                         y: inverted ? chart.plotHeight - barX - barW : barY,
9483                                                         width: inverted ? barH : barW,
9484                                                         height: inverted ? barW : barH
9485                                                 });
9486                                 }
9487                         });
9488                 }
9489         },
9490         
9491         /**
9492          * Draw the actual graph
9493          */
9494         drawGraph: function(state) {
9495                 var series = this, 
9496                         options = series.options, 
9497                         chart = series.chart,
9498                         graph = series.graph,
9499                         graphPath = [],
9500                         fillColor,
9501                         area = series.area,
9502                         group = series.group,
9503                         color = options.lineColor || series.color, 
9504                         lineWidth = options.lineWidth,
9505                         dashStyle =  options.dashStyle,
9506                         segmentPath,
9507                         renderer = chart.renderer,
9508                         translatedThreshold = series.yAxis.getThreshold(options.threshold || 0),
9509                         useArea = /^area/.test(series.type),
9510                         singlePoints = [], // used in drawTracker
9511                         areaPath = [],
9512                         attribs;
9513                         
9514                 
9515                 // divide into segments and build graph and area paths
9516                 each(series.segments, function(segment) {
9517                         segmentPath = [];
9518                         
9519                         // build the segment line
9520                         each(segment, function(point, i) {
9522                                 if (series.getPointSpline) { // generate the spline as defined in the SplineSeries object
9523                                         segmentPath.push.apply(segmentPath, series.getPointSpline(segment, point, i));
9524                                 
9525                                 } else {
9526                                 
9527                                         // moveTo or lineTo
9528                                         segmentPath.push(i ? L : M);
9529                                         
9530                                         // step line?
9531                                         if (i && options.step) {
9532                                                 var lastPoint = segment[i - 1];
9533                                                 segmentPath.push(
9534                                                         point.plotX, 
9535                                                         lastPoint.plotY                                         
9536                                                 );
9537                                         }
9538                                         
9539                                         // normal line to next point
9540                                         segmentPath.push(
9541                                                 point.plotX, 
9542                                                 point.plotY
9543                                         );
9544                                 }
9545                         });
9546                         
9547                         // add the segment to the graph, or a single point for tracking
9548                         if (segment.length > 1) {
9549                                 graphPath = graphPath.concat(segmentPath);
9550                         } else {
9551                                 singlePoints.push(segment[0]);
9552                         }
9553                         
9554                         // build the area
9555                         if (useArea) {
9556                                 var areaSegmentPath = [],
9557                                         i,
9558                                         segLength = segmentPath.length;
9559                                 for (i = 0; i < segLength; i++) {
9560                                         areaSegmentPath.push(segmentPath[i]);
9561                                 }
9562                                 if (segLength === 3) { // for animation from 1 to two points
9563                                         areaSegmentPath.push(L, segmentPath[1], segmentPath[2]);
9564                                 }
9565                                 if (options.stacking && series.type !== 'areaspline') {
9566                                         // follow stack back. Todo: implement areaspline
9567                                         for (i = segment.length - 1; i >= 0; i--) {
9568                                                 areaSegmentPath.push(segment[i].plotX, segment[i].yBottom);
9569                                         }
9570                                 
9571                                 } else { // follow zero line back
9572                                         areaSegmentPath.push(
9573                                                 L,
9574                                                 segment[segment.length - 1].plotX, 
9575                                                 translatedThreshold,
9576                                                 L,
9577                                                 segment[0].plotX, 
9578                                                 translatedThreshold
9579                                         );
9580                                 }
9581                                 areaPath = areaPath.concat(areaSegmentPath);
9582                         }
9583                 });
9585                 // used in drawTracker:
9586                 series.graphPath = graphPath;
9587                 series.singlePoints = singlePoints;
9589                 // draw the area if area series or areaspline
9590                 if (useArea) {
9591                         fillColor = pick(
9592                                 options.fillColor,
9593                                 Color(series.color).setOpacity(options.fillOpacity || 0.75).get()
9594                         );
9595                         if (area) {
9596                                 area.animate({ d: areaPath });
9597                         
9598                         } else {
9599                                 // draw the area
9600                                 series.area = series.chart.renderer.path(areaPath)
9601                                         .attr({
9602                                                 fill: fillColor
9603                                         }).add(group);
9604                         }
9605                 }
9606                 
9607                 // draw the graph
9608                 if (graph) {
9609                         //graph.animate({ d: graphPath.join(' ') });
9610                         graph.animate({ d: graphPath });
9611                         
9612                 } else {
9613                         if (lineWidth) {
9614                                 attribs = {
9615                                         'stroke': color,
9616                                         'stroke-width': lineWidth
9617                                 };
9618                                 if (dashStyle) {
9619                                         attribs.dashstyle = dashStyle;
9620                                 }
9621                                 
9622                                 series.graph = renderer.path(graphPath)
9623                                         .attr(attribs).add(group).shadow(options.shadow);
9624                         }
9625                 }
9626         },
9627         
9628         
9629         /**
9630          * Render the graph and markers
9631          */
9632         render: function() {
9633                 var series = this,
9634                         chart = series.chart,
9635                         group,
9636                         setInvert,
9637                         options = series.options,
9638                         animation = options.animation,
9639                         doAnimation = animation && series.animate,
9640                         duration = doAnimation ? (animation && animation.duration) || 500 : 0,
9641                         clipRect = series.clipRect,
9642                         renderer = chart.renderer;
9643                         
9644                 
9645                 // Add plot area clipping rectangle. If this is before chart.hasRendered,
9646                 // create one shared clipRect. 
9647                 if (!clipRect) {
9648                         clipRect = series.clipRect = !chart.hasRendered && chart.clipRect ?
9649                                 chart.clipRect : 
9650                                 renderer.clipRect(0, 0, chart.plotSizeX, chart.plotSizeY);
9651                         if (!chart.clipRect) {
9652                                 chart.clipRect = clipRect;
9653                         }
9654                 }
9655                 
9656                         
9657                 // the group
9658                 if (!series.group) {
9659                         group = series.group = renderer.g('series');
9660                                 
9661                         if (chart.inverted) {
9662                                 setInvert = function() {
9663                                         group.attr({
9664                                                 width: chart.plotWidth,
9665                                                 height: chart.plotHeight
9666                                         }).invert();
9667                                 };
9668                                 
9669                                 setInvert(); // do it now
9670                                 addEvent(chart, 'resize', setInvert); // do it on resize
9671                         } 
9672                         group.clip(series.clipRect)
9673                                 .attr({ 
9674                                         visibility: series.visible ? VISIBLE : HIDDEN,
9675                                         zIndex: options.zIndex
9676                                 })
9677                                 .translate(chart.plotLeft, chart.plotTop)
9678                                 .add(chart.seriesGroup);
9679                 }
9680                         
9681                 series.drawDataLabels();
9683                 // initiate the animation
9684                 if (doAnimation) {
9685                         series.animate(true);
9686                 }
9687                 
9688                 // cache attributes for shapes
9689                 //series.getAttribs();
9690                 
9691                 // draw the graph if any
9692                 if (series.drawGraph) {
9693                         series.drawGraph();
9694                 }
9695                 
9696                 // draw the points
9697                 series.drawPoints();
9698                 
9699                 // draw the mouse tracking area
9700                 if (series.options.enableMouseTracking !== false) {
9701                         series.drawTracker();
9702                 }
9703                 
9704                 // run the animation
9705                 if (doAnimation) {
9706                         series.animate();
9707                 }
9708                 
9709                 // finish the individual clipRect
9710                 setTimeout(function() {
9711                         clipRect.isAnimating = false;
9712                         group = series.group; // can be destroyed during the timeout
9713                         if (group && clipRect !== chart.clipRect && clipRect.renderer) {
9714                                 group.clip((series.clipRect = chart.clipRect));
9715                                 clipRect.destroy();
9716                         }
9717                 }, duration);
9718                 
9719                 
9720                 series.isDirty = false; // means data is in accordance with what you see
9721                 
9722         },
9723         
9724         /**
9725          * Redraw the series after an update in the axes.
9726          */
9727         redraw: function() {
9728                 var series = this,
9729                         chart = series.chart,
9730                         clipRect = series.clipRect,
9731                         group = series.group;
9732                 
9733                 /*if (clipRect) {
9734                         stop(clipRect);
9735                         clipRect.animate({ // for chart resize
9736                                 width: chart.plotSizeX,
9737                                 height: chart.plotSizeY
9738                         });
9739                 }*/
9740                 
9741                 // reposition on resize
9742                 if (group) {
9743                         if (chart.inverted) {
9744                                 group.attr({
9745                                         width: chart.plotWidth,
9746                                         height: chart.plotHeight
9747                                 });
9748                         }
9749                         
9750                         group.animate({
9751                                 translateX: chart.plotLeft, 
9752                                 translateY: chart.plotTop
9753                         });
9754                 }
9755                 
9756                 series.translate();
9757                 series.setTooltipPoints(true);
9758                 series.render();
9759         },
9760         
9761         /**
9762          * Set the state of the graph
9763          */
9764         setState: function(state) {
9765                 var series = this,
9766                         options = series.options,
9767                         graph = series.graph,
9768                         stateOptions = options.states,
9769                         lineWidth = options.lineWidth;
9771                 state = state || NORMAL_STATE;
9772                                 
9773                 if (series.state !== state) {
9774                         series.state = state;
9775                         
9776                         if (stateOptions[state] && stateOptions[state].enabled === false) {
9777                                 return;
9778                         }
9779                 
9780                         if (state) {
9781                                 lineWidth = stateOptions[state].lineWidth || lineWidth + 1;
9782                         }
9783                         
9784                         if (graph && !graph.dashstyle) { // hover is turned off for dashed lines in VML
9785                                 graph.attr({ // use attr because animate will cause any other animation on the graph to stop
9786                                         'stroke-width': lineWidth
9787                                 }, state ? 0 : 500);
9788                         }
9789                 }
9790         },
9791         
9792         /**
9793          * Set the visibility of the graph
9794          * 
9795          * @param vis {Boolean} True to show the series, false to hide. If UNDEFINED,
9796          *        the visibility is toggled.
9797          */
9798         setVisible: function(vis, redraw) {
9799                 var series = this,
9800                         chart = series.chart,
9801                         legendItem = series.legendItem,
9802                         seriesGroup = series.group,
9803                         seriesTracker = series.tracker,
9804                         dataLabelsGroup = series.dataLabelsGroup,
9805                         showOrHide,
9806                         i,
9807                         data = series.data,
9808                         point,
9809                         ignoreHiddenSeries = chart.options.chart.ignoreHiddenSeries,
9810                         oldVisibility = series.visible;
9811                 
9812                 // if called without an argument, toggle visibility
9813                 series.visible = vis = vis === UNDEFINED ? !oldVisibility : vis;
9814                 showOrHide = vis ? 'show' : 'hide';
9815                 
9816                 // show or hide series
9817                 if (seriesGroup) { // pies don't have one
9818                         seriesGroup[showOrHide]();
9819                 }
9820                 
9821                 // show or hide trackers
9822                 if (seriesTracker) {
9823                         seriesTracker[showOrHide]();
9824                 } else {
9825                         i = data.length;
9826                         while (i--) {
9827                                 point = data[i];
9828                                 if (point.tracker) {
9829                                         point.tracker[showOrHide]();
9830                                 }
9831                         }
9832                 }
9833                 
9834                 
9835                 if (dataLabelsGroup) {
9836                         dataLabelsGroup[showOrHide]();
9837                 }
9838                 
9839                 if (legendItem) {
9840                         chart.legend.colorizeItem(series, vis);
9841                 }
9842                         
9843                 
9844                 // rescale or adapt to resized chart
9845                 series.isDirty = true;
9846                 // in a stack, all other series are affected
9847                 if (series.options.stacking) {
9848                         each(chart.series, function(otherSeries) {
9849                                 if (otherSeries.options.stacking && otherSeries.visible) { 
9850                                         otherSeries.isDirty = true;
9851                                 }
9852                         });
9853                 }
9854                 
9855                 if (ignoreHiddenSeries) {
9856                         chart.isDirtyBox = true;
9857                 }
9858                 if (redraw !== false) {
9859                         chart.redraw();
9860                 }
9861                 
9862                 fireEvent(series, showOrHide);
9863         },
9864         
9865         /**
9866          * Show the graph
9867          */
9868         show: function() {
9869                 this.setVisible(true);
9870         },
9871         
9872         /**
9873          * Hide the graph
9874          */
9875         hide: function() {
9876                 this.setVisible(false);
9877         },
9878         
9879         
9880         /**
9881          * Set the selected state of the graph
9882          * 
9883          * @param selected {Boolean} True to select the series, false to unselect. If
9884          *        UNDEFINED, the selection state is toggled.
9885          */
9886         select: function(selected) {
9887                 var series = this;
9888                 // if called without an argument, toggle
9889                 series.selected = selected = (selected === UNDEFINED) ? !series.selected : selected;
9891                 if (series.checkbox) {
9892                         series.checkbox.checked = selected;
9893                 }
9894                 
9895                 fireEvent(series, selected ? 'select' : 'unselect');
9896         },
9897         
9898         
9899         /**
9900          * Draw the tracker object that sits above all data labels and markers to
9901          * track mouse events on the graph or points. For the line type charts
9902          * the tracker uses the same graphPath, but with a greater stroke width
9903          * for better control.
9904          */
9905         drawTracker: function() {
9906                 var series = this,
9907                         options = series.options,
9908                         trackerPath = [].concat(series.graphPath),
9909                         trackerPathLength = trackerPath.length,
9910                         chart = series.chart,
9911                         snap = chart.options.tooltip.snap,
9912                         tracker = series.tracker,
9913                         cursor = options.cursor,
9914                         css = cursor && { cursor: cursor },
9915                         singlePoints = series.singlePoints,
9916                         singlePoint,
9917                         i;
9918         
9919                 // Extend end points. A better way would be to use round linecaps,
9920                 // but those are not clickable in VML.
9921                 if (trackerPathLength) {
9922                         i = trackerPathLength + 1;
9923                         while (i--) {
9924                                 if (trackerPath[i] === M) { // extend left side
9925                                         trackerPath.splice(i + 1, 0, trackerPath[i + 1] - snap, trackerPath[i + 2], L);
9926                                 }
9927                                 if ((i && trackerPath[i] === M) || i === trackerPathLength) { // extend right side
9928                                         trackerPath.splice(i, 0, L, trackerPath[i - 2] + snap, trackerPath[i - 1]);
9929                                 }
9930                         }
9931                 }
9932                 
9933                 // handle single points
9934                 for (i = 0; i < singlePoints.length; i++) {
9935                         singlePoint = singlePoints[i];
9936                         trackerPath.push(M, singlePoint.plotX - snap, singlePoint.plotY,
9937                                 L, singlePoint.plotX + snap, singlePoint.plotY);
9938                 }
9939                 
9940                 // draw the tracker
9941                 if (tracker) {
9942                         tracker.attr({ d: trackerPath });
9943                         
9944                 } else { // create
9945                         series.tracker = chart.renderer.path(trackerPath)
9946                                 .attr({
9947                                         isTracker: true,
9948                                         stroke: TRACKER_FILL,
9949                                         fill: NONE,
9950                                         'stroke-width' : options.lineWidth + 2 * snap,
9951                                         visibility: series.visible ? VISIBLE : HIDDEN,
9952                                         zIndex: 1
9953                                 })
9954                                 .on(hasTouch ? 'touchstart' : 'mouseover', function() {
9955                                         if (chart.hoverSeries !== series) {
9956                                                 series.onMouseOver();
9957                                         }
9958                                 })
9959                                 .on('mouseout', function() {
9960                                         if (!options.stickyTracking) {
9961                                                 series.onMouseOut();
9962                                         }
9963                                 })
9964                                 .css(css)
9965                                 .add(chart.trackerGroup);
9966                 }
9967                 
9968         }
9969         
9970 }; // end Series prototype
9974  * LineSeries object
9975  */
9976 var LineSeries = extendClass(Series);
9977 seriesTypes.line = LineSeries;
9980  * AreaSeries object
9981  */
9982 var AreaSeries = extendClass(Series, {
9983         type: 'area'
9985 seriesTypes.area = AreaSeries;
9991  * SplineSeries object
9992  */
9993 var SplineSeries = extendClass( Series, {
9994         type: 'spline',
9995         
9996         /**
9997          * Draw the actual graph
9998          */
9999         getPointSpline: function(segment, point, i) {
10000                 var smoothing = 1.5, // 1 means control points midway between points, 2 means 1/3 from the point, 3 is 1/4 etc
10001                         denom = smoothing + 1,
10002                         plotX = point.plotX,
10003                         plotY = point.plotY,
10004                         lastPoint = segment[i - 1],
10005                         nextPoint = segment[i + 1],
10006                         leftContX,
10007                         leftContY,
10008                         rightContX,
10009                         rightContY,
10010                         ret;
10011                         
10012                 // find control points
10013                 if (i && i < segment.length - 1) {
10014                         var lastX = lastPoint.plotX,
10015                                 lastY = lastPoint.plotY,
10016                                 nextX = nextPoint.plotX,
10017                                 nextY = nextPoint.plotY,
10018                                 correction;
10019                         
10020                         leftContX = (smoothing * plotX + lastX) / denom;
10021                         leftContY = (smoothing * plotY + lastY) / denom;
10022                         rightContX = (smoothing * plotX + nextX) / denom;
10023                         rightContY = (smoothing * plotY + nextY) / denom;
10024                 
10025                         // have the two control points make a straight line through main point
10026                         correction = ((rightContY - leftContY) * (rightContX - plotX)) / 
10027                                 (rightContX - leftContX) + plotY - rightContY;
10028                                 
10029                         leftContY += correction;
10030                         rightContY += correction;
10031                         
10032                         // to prevent false extremes, check that control points are between
10033                         // neighbouring points' y values
10034                         if (leftContY > lastY && leftContY > plotY) {
10035                                 leftContY = mathMax(lastY, plotY);
10036                                 rightContY = 2 * plotY - leftContY; // mirror of left control point
10037                         } else if (leftContY < lastY && leftContY < plotY) {
10038                                 leftContY = mathMin(lastY, plotY);
10039                                 rightContY = 2 * plotY - leftContY;
10040                         } 
10041                         if (rightContY > nextY && rightContY > plotY) {
10042                                 rightContY = mathMax(nextY, plotY);
10043                                 leftContY = 2 * plotY - rightContY;
10044                         } else if (rightContY < nextY && rightContY < plotY) {
10045                                 rightContY = mathMin(nextY, plotY);
10046                                 leftContY = 2 * plotY - rightContY;
10047                         }
10048                         
10049                         // record for drawing in next point
10050                         point.rightContX = rightContX;
10051                         point.rightContY = rightContY;
10052                         
10053                 }
10054                 
10055                 // moveTo or lineTo
10056                 if (!i) {
10057                         ret = [M, plotX, plotY];
10058                 }
10059                 
10060                 // curve from last point to this
10061                 else {
10062                         ret = [
10063                                 'C',
10064                                 lastPoint.rightContX || lastPoint.plotX, 
10065                                 lastPoint.rightContY || lastPoint.plotY,
10066                                 leftContX || plotX, 
10067                                 leftContY || plotY,
10068                                 plotX, 
10069                                 plotY
10070                         ];
10071                         lastPoint.rightContX = lastPoint.rightContY = null; // reset for updating series later
10072                 }
10073                 return ret;
10074         }
10076 seriesTypes.spline = SplineSeries;
10081  * AreaSplineSeries object
10082  */
10083 var AreaSplineSeries = extendClass(SplineSeries, {
10084         type: 'areaspline'
10086 seriesTypes.areaspline = AreaSplineSeries;
10089  * ColumnSeries object
10090  */
10091 var ColumnSeries = extendClass(Series, {
10092         type: 'column',
10093         pointAttrToOptions: { // mapping between SVG attributes and the corresponding options
10094                 stroke: 'borderColor',
10095                 'stroke-width': 'borderWidth',
10096                 fill: 'color',
10097                 r: 'borderRadius'
10098         },
10099         init: function() {
10100                 Series.prototype.init.apply(this, arguments);
10101                 
10102                 var series = this,
10103                         chart = series.chart;
10104                 
10105                 // flag the chart in order to pad the x axis
10106                 chart.hasColumn = true;
10107                 
10108                 // if the series is added dynamically, force redraw of other
10109                 // series affected by a new column
10110                 if (chart.hasRendered) {
10111                         each(chart.series, function(otherSeries) {
10112                                 if (otherSeries.type === series.type) {
10113                                         otherSeries.isDirty = true;
10114                                 }
10115                         });
10116                 }
10117         },
10118         
10119         /**
10120          * Translate each point to the plot area coordinate system and find shape positions
10121          */
10122         translate: function() {
10123                 var series = this,
10124                         chart = series.chart,
10125                         options = series.options,
10126                         stacking = options.stacking,
10127                         borderWidth = options.borderWidth,
10128                         columnCount = 0,
10129                         reversedXAxis = series.xAxis.reversed,
10130                         categories = series.xAxis.categories,
10131                         stackGroups = {},
10132                         stackKey,
10133                         columnIndex;
10134                 
10135                 Series.prototype.translate.apply(series);
10136                 
10137                 // Get the total number of column type series.
10138                 // This is called on every series. Consider moving this logic to a 
10139                 // chart.orderStacks() function and call it on init, addSeries and removeSeries
10140                 each(chart.series, function(otherSeries) {
10141                         if (otherSeries.type === series.type && otherSeries.visible) {
10142                                 if (otherSeries.options.stacking) {
10143                                         stackKey = otherSeries.stackKey;
10144                                         if (stackGroups[stackKey] === UNDEFINED) {
10145                                                 stackGroups[stackKey] = columnCount++;  
10146                                         }                                       
10147                                         columnIndex = stackGroups[stackKey];
10148                                 } else {
10149                                         columnIndex = columnCount++;
10150                                 }
10151                                 otherSeries.columnIndex = columnIndex;
10152                         }
10153                 });
10154                 
10155                 // calculate the width and position of each column based on 
10156                 // the number of column series in the plot, the groupPadding
10157                 // and the pointPadding options
10158                 var data = series.data,
10159                         closestPoints = series.closestPoints,
10160                         categoryWidth = mathAbs(
10161                                 data[1] ? data[closestPoints].plotX - data[closestPoints - 1].plotX : 
10162                                 chart.plotSizeX / ((categories && categories.length) || 1)
10163                         ),
10164                         groupPadding = categoryWidth * options.groupPadding,
10165                         groupWidth = categoryWidth - 2 * groupPadding,
10166                         pointOffsetWidth = groupWidth / columnCount,
10167                         optionPointWidth = options.pointWidth,
10168                         pointPadding = defined(optionPointWidth) ? (pointOffsetWidth - optionPointWidth) / 2 : 
10169                                 pointOffsetWidth * options.pointPadding,
10170                         pointWidth = mathMax(pick(optionPointWidth, pointOffsetWidth - 2 * pointPadding), 1),
10171                         colIndex = (reversedXAxis ? columnCount - 
10172                                 series.columnIndex : series.columnIndex) || 0,
10173                         pointXOffset = pointPadding + (groupPadding + colIndex *
10174                                 pointOffsetWidth -(categoryWidth / 2)) *
10175                                 (reversedXAxis ? -1 : 1),
10176                         threshold = options.threshold || 0,
10177                         translatedThreshold = series.yAxis.getThreshold(threshold),
10178                         minPointLength = pick(options.minPointLength, 5);
10179                 
10180                 // record the new values
10181                 each(data, function(point) {
10182                         var plotY = point.plotY,
10183                                 yBottom = point.yBottom || translatedThreshold,
10184                                 barX = point.plotX + pointXOffset,
10185                                 barY = mathCeil(mathMin(plotY, yBottom)), 
10186                                 barH = mathCeil(mathMax(plotY, yBottom) - barY),
10187                                 stack = series.yAxis.stacks[(point.y < 0 ? '-' : '') + series.stackKey],
10188                                 trackerY,
10189                                 shapeArgs;
10190                         
10191                         // Record the offset'ed position and width of the bar to be able to align the stacking total correctly
10192                         if (stacking && series.visible && stack && stack[point.x]) {
10193                                 stack[point.x].setOffset(pointXOffset, pointWidth);
10194                         }
10195                         
10196                         // handle options.minPointLength and tracker for small points
10197                         if (mathAbs(barH) < minPointLength) { 
10198                                 if (minPointLength) {
10199                                         barH = minPointLength;
10200                                         barY = 
10201                                                 mathAbs(barY - translatedThreshold) > minPointLength ? // stacked
10202                                                         yBottom - minPointLength : // keep position
10203                                                         translatedThreshold - (plotY <= translatedThreshold ? minPointLength : 0);
10204                                 }
10205                                 trackerY = barY - 3;
10206                         }
10207                         
10208                         extend(point, {
10209                                 barX: barX,
10210                                 barY: barY, 
10211                                 barW: pointWidth,
10212                                 barH: barH
10213                         });
10214                         
10215                         // create shape type and shape args that are reused in drawPoints and drawTracker
10216                         point.shapeType = 'rect';
10217                         shapeArgs = extend(chart.renderer.Element.prototype.crisp.apply({}, [
10218                                 borderWidth,
10219                                 barX,
10220                                 barY,
10221                                 pointWidth,
10222                                 barH
10223                         ]), {
10224                                 r: options.borderRadius
10225                         });
10226                         if (borderWidth % 2) { // correct for shorting in crisp method, visible in stacked columns with 1px border
10227                                 shapeArgs.y -= 1;
10228                                 shapeArgs.height += 1;
10229                         }
10230                         point.shapeArgs = shapeArgs;
10231                         
10232                         // make small columns responsive to mouse
10233                         point.trackerArgs = defined(trackerY) && merge(point.shapeArgs, {
10234                                 height: mathMax(6, barH + 3),
10235                                 y: trackerY
10236                         });
10237                 });
10238                 
10239         },
10240         
10241         getSymbol: function(){
10242         },
10243         
10244         /** 
10245          * Columns have no graph
10246          */
10247         drawGraph: function() {},
10248         
10249         /**
10250          * Draw the columns. For bars, the series.group is rotated, so the same coordinates
10251          * apply for columns and bars. This method is inherited by scatter series.
10252          * 
10253          */
10254         drawPoints: function() {
10255                 var series = this,
10256                         options = series.options,
10257                         renderer = series.chart.renderer,
10258                         graphic,
10259                         shapeArgs;              
10260                 
10261                 
10262                 // draw the columns
10263                 each(series.data, function(point) {                     
10264                         var plotY = point.plotY;
10265                         if (plotY !== UNDEFINED && !isNaN(plotY) && point.y !== null) {
10266                                 graphic = point.graphic;
10267                                 shapeArgs = point.shapeArgs;
10268                                 if (graphic) { // update
10269                                         stop(graphic);
10270                                         graphic.animate(shapeArgs);
10271                                 
10272                                 } else {
10273                                         point.graphic = renderer[point.shapeType](shapeArgs)
10274                                                 .attr(point.pointAttr[point.selected ? SELECT_STATE : NORMAL_STATE])
10275                                                 .add(series.group)
10276                                                 .shadow(options.shadow);
10277                                 }
10278                         
10279                         }
10280                 });
10281         },
10282         /**
10283          * Draw the individual tracker elements.
10284          * This method is inherited by scatter and pie charts too.
10285          */
10286         drawTracker: function() {
10287                 var series = this,
10288                         chart = series.chart,
10289                         renderer = chart.renderer,
10290                         shapeArgs,
10291                         tracker,
10292                         trackerLabel = +new Date(),
10293                         cursor = series.options.cursor,
10294                         css = cursor && { cursor: cursor },
10295                         rel;
10296                         
10297                 each(series.data, function(point) {
10298                         tracker = point.tracker;
10299                         shapeArgs = point.trackerArgs || point.shapeArgs;
10300                         delete shapeArgs.strokeWidth;
10301                         if (point.y !== null) {
10302                                 if (tracker) {// update
10303                                         tracker.attr(shapeArgs);
10304                                         
10305                                 } else {
10306                                         point.tracker = 
10307                                                 renderer[point.shapeType](shapeArgs)
10308                                                 .attr({
10309                                                         isTracker: trackerLabel,
10310                                                         fill: TRACKER_FILL,
10311                                                         visibility: series.visible ? VISIBLE : HIDDEN,
10312                                                         zIndex: 1
10313                                                 })
10314                                                 .on(hasTouch ? 'touchstart' : 'mouseover', function(event) {
10315                                                         rel = event.relatedTarget || event.fromElement;
10316                                                         if (chart.hoverSeries !== series && attr(rel, 'isTracker') !== trackerLabel) {
10317                                                                 series.onMouseOver();
10318                                                         }
10319                                                         point.onMouseOver();
10320                                                         
10321                                                 })
10322                                                 .on('mouseout', function(event) {
10323                                                         if (!series.options.stickyTracking) {
10324                                                                 rel = event.relatedTarget || event.toElement;
10325                                                                 if (attr(rel, 'isTracker') !== trackerLabel) {
10326                                                                         series.onMouseOut();
10327                                                                 }
10328                                                         }
10329                                                 })
10330                                                 .css(css)
10331                                                 .add(point.group || chart.trackerGroup); // pies have point group - see issue #118
10332                                 }
10333                         }
10334                 });
10335         },
10336         
10337         
10338         /**
10339          * Animate the column heights one by one from zero
10340          * @param {Boolean} init Whether to initialize the animation or run it 
10341          */
10342         animate: function(init) {
10343                 var series = this,
10344                         data = series.data;
10345                         
10346                 if (!init) { // run the animation
10347                         /*
10348                          * Note: Ideally the animation should be initialized by calling
10349                          * series.group.hide(), and then calling series.group.show()
10350                          * after the animation was started. But this rendered the shadows
10351                          * invisible in IE8 standards mode. If the columns flicker on large
10352                          * datasets, this is the cause.
10353                          */
10354                         
10355                         each(data, function(point) {
10356                                 var graphic = point.graphic,
10357                                         shapeArgs = point.shapeArgs;
10358                                 
10359                                 if (graphic) {
10360                                         // start values
10361                                         graphic.attr({ 
10362                                                 height: 0,
10363                                                 y: series.yAxis.translate(0, 0, 1)
10364                                         });
10365                                         
10366                                         // animate
10367                                         graphic.animate({ 
10368                                                 height: shapeArgs.height,
10369                                                 y: shapeArgs.y
10370                                         }, series.options.animation);
10371                                 }
10372                         });
10373                         
10374                         
10375                         // delete this function to allow it only once
10376                         series.animate = null;
10377                 }
10378                 
10379         },
10380         /**
10381          * Remove this series from the chart
10382          */
10383         remove: function() {
10384                 var series = this,
10385                         chart = series.chart;
10386                         
10387                 // column and bar series affects other series of the same type
10388                 // as they are either stacked or grouped
10389                 if (chart.hasRendered) {
10390                         each(chart.series, function(otherSeries) {
10391                                 if (otherSeries.type === series.type) {
10392                                         otherSeries.isDirty = true;
10393                                 }
10394                         });
10395                 }
10396                 
10397                 Series.prototype.remove.apply(series, arguments);
10398         }
10400 seriesTypes.column = ColumnSeries;
10402 var BarSeries = extendClass(ColumnSeries, {
10403         type: 'bar',
10404         init: function(chart) {
10405                 chart.inverted = this.inverted = true;
10406                 ColumnSeries.prototype.init.apply(this, arguments);
10407         }
10409 seriesTypes.bar = BarSeries;
10412  * The scatter series class
10413  */
10414 var ScatterSeries = extendClass(Series, {
10415         type: 'scatter',
10416         
10417         /**
10418          * Extend the base Series' translate method by adding shape type and
10419          * arguments for the point trackers
10420          */
10421         translate: function() {
10422                 var series = this;
10424                 Series.prototype.translate.apply(series);
10426                 each(series.data, function(point) {
10427                         point.shapeType = 'circle';
10428                         point.shapeArgs = {
10429                                 x: point.plotX,
10430                                 y: point.plotY,
10431                                 r: series.chart.options.tooltip.snap
10432                         };
10433                 });
10434         },
10435         
10436         
10437         /**
10438          * Create individual tracker elements for each point
10439          */
10440         //drawTracker: ColumnSeries.prototype.drawTracker,
10441         drawTracker: function() {
10442                 var series = this,
10443                         cursor = series.options.cursor,
10444                         css = cursor && { cursor: cursor },
10445                         graphic;
10446                         
10447                 each(series.data, function(point) {
10448                         graphic = point.graphic;
10449                         if (graphic) { // doesn't exist for null points
10450                                 graphic
10451                                         .attr({ isTracker: true })
10452                                         .on('mouseover', function(event) {
10453                                                 series.onMouseOver();
10454                                                 point.onMouseOver();                                    
10455                                         })
10456                                         .on('mouseout', function(event) {
10457                                                 if (!series.options.stickyTracking) {
10458                                                         series.onMouseOut();
10459                                                 }
10460                                         })
10461                                         .css(css);
10462                         }
10463                 });
10465         },
10466         
10467         /**
10468          * Cleaning the data is not necessary in a scatter plot
10469          */
10470         cleanData: function() {}
10472 seriesTypes.scatter = ScatterSeries;
10475  * Extended point object for pies
10476  */
10477 var PiePoint = extendClass(Point, {
10478         /**
10479          * Initiate the pie slice
10480          */
10481         init: function () {
10482                 
10483                 Point.prototype.init.apply(this, arguments);
10484                 
10485                 var point = this,
10486                         toggleSlice;
10487                 
10488                 //visible: options.visible !== false,
10489                 extend(point, {
10490                         visible: point.visible !== false,
10491                         name: pick(point.name, 'Slice')
10492                 });
10493                 
10494                 // add event listener for select
10495                 toggleSlice = function() {
10496                         point.slice();
10497                 };
10498                 addEvent(point, 'select', toggleSlice);
10499                 addEvent(point, 'unselect', toggleSlice);
10500                 
10501                 return point;
10502         },
10503         
10504         /**
10505          * Toggle the visibility of the pie slice
10506          * @param {Boolean} vis Whether to show the slice or not. If undefined, the
10507          *    visibility is toggled
10508          */
10509         setVisible: function(vis) {     
10510                 var point = this, 
10511                         chart = point.series.chart,
10512                         tracker = point.tracker,
10513                         dataLabel = point.dataLabel,
10514                         connector = point.connector,
10515                         method;
10516                 
10517                 // if called without an argument, toggle visibility
10518                 point.visible = vis = vis === UNDEFINED ? !point.visible : vis;
10519                 
10520                 method = vis ? 'show' : 'hide';
10521                 
10522                 point.group[method]();
10523                 if (tracker) {
10524                         tracker[method]();
10525                 }
10526                 if (dataLabel) {
10527                         dataLabel[method]();
10528                 }
10529                 if (connector) {
10530                         connector[method]();
10531                 }
10532                 if (point.legendItem) {
10533                         chart.legend.colorizeItem(point, vis);
10534                 }
10535         },
10536         
10537         /**
10538          * Set or toggle whether the slice is cut out from the pie
10539          * @param {Boolean} sliced When undefined, the slice state is toggled 
10540          * @param {Boolean} redraw Whether to redraw the chart. True by default.
10541          */
10542         slice: function(sliced, redraw, animation) {
10543                 var point = this,
10544                         series = point.series,
10545                         chart = series.chart,
10546                         slicedTranslation = point.slicedTranslation,
10547                         translation;
10548                         
10549                 setAnimation(animation, chart);
10550                 
10551                 // redraw is true by default
10552                 redraw = pick(redraw, true);
10553                         
10554                 // if called without an argument, toggle
10555                 sliced = point.sliced = defined(sliced) ? sliced : !point.sliced;
10556                 
10557                 translation = {
10558                         translateX: (sliced ? slicedTranslation[0] : chart.plotLeft),
10559                         translateY: (sliced ? slicedTranslation[1] : chart.plotTop)
10560                 };
10561                 point.group.animate(translation);
10562                 if (point.shadowGroup) {
10563                         point.shadowGroup.animate(translation);
10564                 }
10565                 
10566         }
10570  * The Pie series class
10571  */
10572 var PieSeries = extendClass(Series, {
10573         type: 'pie',
10574         isCartesian: false,
10575         pointClass: PiePoint,
10576         pointAttrToOptions: { // mapping between SVG attributes and the corresponding options
10577                 stroke: 'borderColor',
10578                 'stroke-width': 'borderWidth',
10579                 fill: 'color'
10580         },
10581         
10582         /**
10583          * Pies have one color each point
10584          */
10585         getColor: function() {
10586                 // record first color for use in setData
10587                 this.initialColor = colorCounter;
10588         },
10589         
10590         /**
10591          * Animate the column heights one by one from zero
10592          * @param {Boolean} init Whether to initialize the animation or run it 
10593          */
10594         animate: function(init) {
10595                 var series = this,
10596                         data = series.data;
10597                         
10598                 each(data, function(point) {
10599                         var graphic = point.graphic,
10600                                 args = point.shapeArgs,
10601                                 up = -mathPI / 2;
10602                         
10603                         if (graphic) {
10604                                 // start values
10605                                 graphic.attr({ 
10606                                         r: 0,
10607                                         start: up,
10608                                         end: up
10609                                 });
10610                                 
10611                                 // animate
10612                                 graphic.animate({ 
10613                                         r: args.r,
10614                                         start: args.start,
10615                                         end: args.end
10616                                 }, series.options.animation);
10617                         }
10618                 });
10619                 
10620                 // delete this function to allow it only once
10621                 series.animate = null;
10622                 
10623         },
10624         /**
10625          * Do translation for pie slices
10626          */
10627         translate: function() {
10628                 var total = 0,
10629                         series = this,
10630                         cumulative = -0.25, // start at top
10631                         precision = 1000, // issue #172
10632                         options = series.options,
10633                         slicedOffset = options.slicedOffset,
10634                         connectorOffset = slicedOffset + options.borderWidth,
10635                         positions = options.center.concat([options.size, options.innerSize || 0]),
10636                         chart = series.chart,
10637                         plotWidth = chart.plotWidth,
10638                         plotHeight = chart.plotHeight,
10639                         start,
10640                         end,
10641                         angle,
10642                         data = series.data,
10643                         circ = 2 * mathPI,
10644                         fraction,
10645                         smallestSize = mathMin(plotWidth, plotHeight),
10646                         isPercent,
10647                         radiusX, // the x component of the radius vector for a given point
10648                         radiusY,
10649                         labelDistance = options.dataLabels.distance;
10650                         
10651                 // get positions - either an integer or a percentage string must be given
10652                 positions = map(positions, function(length, i) {
10653                         
10654                         isPercent = /%$/.test(length);                  
10655                         return isPercent ? 
10656                                 // i == 0: centerX, relative to width
10657                                 // i == 1: centerY, relative to height
10658                                 // i == 2: size, relative to smallestSize
10659                                 // i == 4: innerSize, relative to smallestSize
10660                                 [plotWidth, plotHeight, smallestSize, smallestSize][i] *
10661                                         pInt(length) / 100:
10662                                 length;
10663                 });
10664                 
10665                 // utility for getting the x value from a given y, used for anticollision logic in data labels
10666                 series.getX = function(y, left) {
10667                         
10668                         angle = math.asin((y - positions[1]) / (positions[2] / 2 + labelDistance));
10669                         
10670                         return positions[0] + 
10671                                 (left ? -1 : 1) *
10672                                 (mathCos(angle) * (positions[2] / 2 + labelDistance));
10673                 };
10674                 
10675                 // set center for later use
10676                 series.center = positions;
10677                                         
10678                 // get the total sum
10679                 each(data, function(point) {
10680                         total += point.y;
10681                 });
10682                 
10683                 each(data, function(point) {
10684                         // set start and end angle
10685                         fraction = total ? point.y / total : 0;
10686                         start = mathRound(cumulative * circ * precision) / precision;
10687                         cumulative += fraction;
10688                         end = mathRound(cumulative * circ * precision) / precision;
10689                         
10690                         // set the shape
10691                         point.shapeType = 'arc';
10692                         point.shapeArgs = {
10693                                 x: positions[0],
10694                                 y: positions[1],
10695                                 r: positions[2] / 2,
10696                                 innerR: positions[3] / 2,
10697                                 start: start,
10698                                 end: end
10699                         };
10700                         
10701                         // center for the sliced out slice
10702                         angle = (end + start) / 2;
10703                         point.slicedTranslation = map([
10704                                 mathCos(angle) * slicedOffset + chart.plotLeft, 
10705                                 mathSin(angle) * slicedOffset + chart.plotTop
10706                         ], mathRound);
10707                         
10708                         // set the anchor point for tooltips
10709                         radiusX = mathCos(angle) * positions[2] / 2;
10710                         radiusY = mathSin(angle) * positions[2] / 2;
10711                         point.tooltipPos = [
10712                                 positions[0] + radiusX * 0.7,
10713                                 positions[1] + radiusY * 0.7
10714                         ];
10715                         
10716                         // set the anchor point for data labels                 
10717                         point.labelPos = [
10718                                 positions[0] + radiusX + mathCos(angle) * labelDistance, // first break of connector
10719                                 positions[1] + radiusY + mathSin(angle) * labelDistance, // a/a
10720                                 positions[0] + radiusX + mathCos(angle) * connectorOffset, // second break, right outside pie
10721                                 positions[1] + radiusY + mathSin(angle) * connectorOffset, // a/a
10722                                 positions[0] + radiusX, // landing point for connector
10723                                 positions[1] + radiusY, // a/a
10724                                 labelDistance < 0 ? // alignment
10725                                         'center' :
10726                                         angle < circ / 4 ? 'left' : 'right', // alignment
10727                                 angle // center angle
10728                         ];
10729                         
10730                         
10731                         // API properties
10732                         point.percentage = fraction * 100;
10733                         point.total = total;
10734                         
10735                 });
10736                 
10737                 this.setTooltipPoints();
10738         },
10739         
10740         /**
10741          * Render the slices
10742          */
10743         render: function() {
10744                 var series = this;
10745                         
10746                 // cache attributes for shapes
10747                 //series.getAttribs();
10749                 this.drawPoints();
10750                 
10751                 // draw the mouse tracking area
10752                 if (series.options.enableMouseTracking !== false) {
10753                         series.drawTracker();
10754                 }
10755                 
10756                 this.drawDataLabels();
10757                 
10758                 if (series.options.animation && series.animate) {
10759                         series.animate();
10760                 }
10761                 
10762                 series.isDirty = false; // means data is in accordance with what you see
10763         },
10764         
10765         /**
10766          * Draw the data points
10767          */
10768         drawPoints: function() {
10769                 var series = this,
10770                         chart = series.chart,
10771                         renderer = chart.renderer,
10772                         groupTranslation,
10773                         //center,
10774                         graphic,
10775                         group,
10776                         shadow = series.options.shadow,
10777                         shadowGroup,
10778                         shapeArgs;
10779                         
10780                 
10781                 // draw the slices
10782                 each(series.data, function(point) {
10783                         graphic = point.graphic;
10784                         shapeArgs = point.shapeArgs;
10785                         group = point.group;
10786                         shadowGroup = point.shadowGroup;
10788                         // put the shadow behind all points
10789                         if (shadow && !shadowGroup) {
10790                                 shadowGroup = point.shadowGroup = renderer.g('shadow')
10791                                         .attr({ zIndex: 4 })
10792                                         .add();
10793                         }
10794                 
10795                         // create the group the first time
10796                         if (!group) {
10797                                 group = point.group = renderer.g('point')
10798                                         .attr({ zIndex: 5 })
10799                                         .add();
10800                         }
10801                         
10802                         // if the point is sliced, use special translation, else use plot area traslation
10803                         groupTranslation = point.sliced ? point.slicedTranslation : [chart.plotLeft, chart.plotTop];
10804                         group.translate(groupTranslation[0], groupTranslation[1]);
10805                         if (shadowGroup) {
10806                                 shadowGroup.translate(groupTranslation[0], groupTranslation[1]);
10807                         }
10808                                 
10809                         
10810                         // draw the slice
10811                         if (graphic) {
10812                                 graphic.animate(shapeArgs);
10813                         } else {
10814                                 point.graphic = 
10815                                         renderer.arc(shapeArgs)
10816                                         .attr(extend(
10817                                                 point.pointAttr[NORMAL_STATE],
10818                                                 { 'stroke-linejoin': 'round' }
10819                                         ))
10820                                         .add(point.group)
10821                                         .shadow(shadow, shadowGroup);
10822                         }
10823                         
10824                         // detect point specific visibility
10825                         if (point.visible === false) {
10826                                 point.setVisible(false);
10827                         }
10828                                         
10829                 });
10830                 
10831         },
10832         
10833         /**
10834          * Override the base drawDataLabels method by pie specific functionality
10835          */
10836         drawDataLabels: function() {
10837                 var series = this,
10838                         data = series.data,
10839                         point,
10840                         chart = series.chart,
10841                         options = series.options.dataLabels,
10842                         connectorPadding = pick(options.connectorPadding, 10),
10843                         connectorWidth = pick(options.connectorWidth, 1),
10844                         connector,
10845                         connectorPath,
10846                         outside = options.distance > 0,
10847                         dataLabel,
10848                         labelPos,
10849                         labelHeight,
10850                         lastY,
10851                         centerY = series.center[1],
10852                         quarters = [// divide the points into quarters for anti collision
10853                                 [], // top right
10854                                 [], // bottom right
10855                                 [], // bottom left
10856                                 [] // top left
10857                         ], 
10858                         x,
10859                         y,
10860                         visibility,
10861                         overlapping,
10862                         rankArr,
10863                         secondPass,
10864                         sign,
10865                         lowerHalf,
10866                         sort,
10867                         i = 4,
10868                         j;
10869                         
10870                 // run parent method
10871                 Series.prototype.drawDataLabels.apply(series);
10872                 
10873                 // arrange points for detection collision
10874                 each(data, function(point) {
10875                         var angle = point.labelPos[7],
10876                                 quarter;
10877                         if (angle < 0) {
10878                                 quarter = 0;
10879                         } else if (angle < mathPI / 2) {
10880                                 quarter = 1;
10881                         } else if (angle < mathPI) {
10882                                 quarter = 2;
10883                         } else {
10884                                 quarter = 3;
10885                         }
10886                         quarters[quarter].push(point);
10887                 });
10888                 quarters[1].reverse();
10889                 quarters[3].reverse();
10890                 
10891                 // define the sorting algorithm
10892                 sort = function(a,b) {
10893                         return a.y > b.y;
10894                 };
10895                 /* Loop over the points in each quartile, starting from the top and bottom
10896                  * of the pie to detect overlapping labels.
10897                  */
10898                 while (i--) {
10899                         overlapping = 0;
10900                         
10901                         // create an array for sorting and ranking the points within each quarter
10902                         rankArr = [].concat(quarters[i]);
10903                         rankArr.sort(sort);
10904                         j = rankArr.length;
10905                         while (j--) {
10906                                 rankArr[j].rank = j;
10907                         }
10908                                 
10909                         /* In the first pass, count the number of overlapping labels. In the second
10910                          * pass, remove the labels with lowest rank/values.
10911                          */
10912                         for (secondPass = 0; secondPass < 2; secondPass++) {
10913                                 lowerHalf = i % 3;
10914                                 lastY = lowerHalf ? 9999 : -9999;
10915                                 sign = lowerHalf ? -1 : 1;
10916                                 
10917                                 for (j = 0; j < quarters[i].length; j++) {
10918                                         point = quarters[i][j];
10919                                         
10920                                         dataLabel = point.dataLabel;
10921                                         if (dataLabel) {
10922                                                 labelPos = point.labelPos;
10923                                                 visibility = VISIBLE;
10924                                                 x = labelPos[0];
10925                                                 y = labelPos[1];
10926                                                 
10927                                                 
10928                                                 // assume all labels have equal height
10929                                                 if (!labelHeight) {
10930                                                         labelHeight = dataLabel && dataLabel.getBBox().height;
10931                                                 }
10932                                                 
10933                                                 // anticollision
10934                                                 if (outside) {
10935                                                         if (secondPass && point.rank < overlapping) {
10936                                                                 visibility = HIDDEN;
10937                                                         } else if ((!lowerHalf && y < lastY + labelHeight) ||
10938                                                                         (lowerHalf && y > lastY - labelHeight)) {  
10939                                                                 y = lastY + sign * labelHeight;
10940                                                                 x = series.getX(y, i > 1);
10941                                                                 if ((!lowerHalf && y + labelHeight > centerY) ||
10942                                                                                 (lowerHalf && y -labelHeight < centerY)) {
10943                                                                         if (secondPass) {
10944                                                                                 visibility = HIDDEN;
10945                                                                         } else {                                                                        
10946                                                                                 overlapping++;
10947                                                                         }
10948                                                                 }
10949                                                         }
10950                                                 }
10951                                                 
10952                                                 if (point.visible === false) {
10953                                                         visibility = HIDDEN;
10954                                                 }
10955                                                 
10956                                                 if (visibility === VISIBLE) {
10957                                                         lastY = y;
10958                                                 }
10959                                                         
10960                                                 if (secondPass) {
10961                                                 
10962                                                         // move or place the data label
10963                                                         dataLabel
10964                                                                 .attr({
10965                                                                         visibility: visibility,
10966                                                                         align: labelPos[6]
10967                                                                 })[dataLabel.moved ? 'animate' : 'attr']({
10968                                                                         x: x + options.x + 
10969                                                                                 ({ left: connectorPadding, right: -connectorPadding }[labelPos[6]] || 0),
10970                                                                         y: y + options.y
10971                                                                 });
10972                                                         dataLabel.moved = true;
10973                                                         
10974                                                         // draw the connector
10975                                                         if (outside && connectorWidth) {
10976                                                                 connector = point.connector;
10977                                                                         
10978                                                                 connectorPath = [
10979                                                                         M,
10980                                                                         x + (labelPos[6] === 'left' ? 5 : -5), y, // end of the string at the label
10981                                                                         L,
10982                                                                         x, y, // first break, next to the label
10983                                                                         L,
10984                                                                         labelPos[2], labelPos[3], // second break
10985                                                                         L,
10986                                                                         labelPos[4], labelPos[5] // base
10987                                                                 ];
10988                                                                         
10989                                                                 if (connector) {
10990                                                                         connector.animate({ d: connectorPath });
10991                                                                         connector.attr('visibility', visibility);
10992                                                                 
10993                                                                 } else {                
10994                                                                         point.connector = connector = series.chart.renderer.path(connectorPath).attr({
10995                                                                                 'stroke-width': connectorWidth,
10996                                                                                 stroke: options.connectorColor || '#606060',
10997                                                                                 visibility: visibility,
10998                                                                                 zIndex: 3
10999                                                                         })
11000                                                                         .translate(chart.plotLeft, chart.plotTop)
11001                                                                         .add();
11002                                                                 }
11003                                                         }
11004                                                 }
11005                                         }
11006                                 }
11007                         }
11008                 }
11009         },
11010         
11011         /**
11012          * Draw point specific tracker objects. Inherit directly from column series.
11013          */
11014         drawTracker: ColumnSeries.prototype.drawTracker,
11015         
11016         /**
11017          * Pies don't have point marker symbols
11018          */
11019         getSymbol: function() {}
11020         
11022 seriesTypes.pie = PieSeries;
11025 // global variables
11026 win.Highcharts = {
11027         Chart: Chart,
11028         dateFormat: dateFormat,
11029         pathAnim: pathAnim,
11030         getOptions: getOptions,
11031         numberFormat: numberFormat,
11032         Point: Point,
11033         Color: Color,
11034         Renderer: Renderer,
11035         seriesTypes: seriesTypes,
11036         setOptions: setOptions,
11037         Series: Series,
11038                 
11039         // Expose utility funcitons for modules
11040         addEvent: addEvent,
11041         createElement: createElement,
11042         discardElement: discardElement,
11043         css: css,
11044         each: each,
11045         extend: extend,
11046         map: map,
11047         merge: merge,
11048         pick: pick,
11049         extendClass: extendClass,
11050         version: '2.1.5'
11052 }());