]> rtime.felk.cvut.cz Git - nul-nightly.git/blob - js/highstock.src.js
New nightly build log
[nul-nightly.git] / js / highstock.src.js
1 // ==ClosureCompiler==
2 // @compilation_level SIMPLE_OPTIMIZATIONS
3
4 /**
5  * @license Highstock JS v1.1.4 (2012-02-15)
6  *
7  * (c) 2009-2011 Torstein Hønsi
8  *
9  * License: www.highcharts.com/license
10  */
11
12 // JSLint options:
13 /*global Highcharts, document, window, navigator, setInterval, clearInterval, clearTimeout, setTimeout, location, jQuery, $ */
14
15 (function () {
16 // encapsulated variables
17 var UNDEFINED,
18         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         SVG_NS = 'http://www.w3.org/2000/svg',
40         hasSVG = !!doc.createElementNS && !!doc.createElementNS(SVG_NS, 'svg').createSVGRect,
41         hasRtlBug = isFirefox && parseInt(userAgent.split('Firefox/')[1], 10) < 4, // issue #38
42         Renderer,
43         hasTouch = doc.documentElement.ontouchstart !== UNDEFINED,
44         symbolSizes = {},
45         idCounter = 0,
46         garbageBin,
47         defaultOptions,
48         dateFormat, // function
49         globalAnimation,
50         pathAnim,
51         timeUnits,
52
53         // some constants for frequently used strings
54         DIV = 'div',
55         ABSOLUTE = 'absolute',
56         RELATIVE = 'relative',
57         HIDDEN = 'hidden',
58         PREFIX = 'highcharts-',
59         VISIBLE = 'visible',
60         PX = 'px',
61         NONE = 'none',
62         M = 'M',
63         L = 'L',
64         /*
65          * Empirical lowest possible opacities for TRACKER_FILL
66          * IE6: 0.002
67          * IE7: 0.002
68          * IE8: 0.002
69          * IE9: 0.00000000001 (unlimited)
70          * FF: 0.00000000001 (unlimited)
71          * Chrome: 0.000001
72          * Safari: 0.000001
73          * Opera: 0.00000000001 (unlimited)
74          */
75         TRACKER_FILL = 'rgba(192,192,192,' + (hasSVG ? 0.000001 : 0.002) + ')', // invisible but clickable
76         //TRACKER_FILL = 'rgba(192,192,192,0.5)',
77         NORMAL_STATE = '',
78         HOVER_STATE = 'hover',
79         SELECT_STATE = 'select',
80         MILLISECOND = 'millisecond',
81         SECOND = 'second',
82         MINUTE = 'minute',
83         HOUR = 'hour',
84         DAY = 'day',
85         WEEK = 'week',
86         MONTH = 'month',
87         YEAR = 'year',
88
89         // constants for attributes
90         FILL = 'fill',
91         LINEAR_GRADIENT = 'linearGradient',
92         STOPS = 'stops',
93         STROKE = 'stroke',
94         STROKE_WIDTH = 'stroke-width',
95
96         // time methods, changed based on whether or not UTC is used
97         makeTime,
98         getMinutes,
99         getHours,
100         getDay,
101         getDate,
102         getMonth,
103         getFullYear,
104         setMinutes,
105         setHours,
106         setDate,
107         setMonth,
108         setFullYear,
109
110         // check for a custom HighchartsAdapter defined prior to this file
111         globalAdapter = win.HighchartsAdapter,
112         adapter = globalAdapter || {},
113
114         // Utility functions. If the HighchartsAdapter is not defined, adapter is an empty object
115         // and all the utility functions will be null. In that case they are populated by the
116         // default adapters below.
117         each = adapter.each,
118         grep = adapter.grep,
119         offset = adapter.offset,
120         map = adapter.map,
121         merge = adapter.merge,
122         addEvent = adapter.addEvent,
123         removeEvent = adapter.removeEvent,
124         fireEvent = adapter.fireEvent,
125         animate = adapter.animate,
126         stop = adapter.stop,
127
128         // lookup over the types and the associated classes
129         seriesTypes = {};
130
131 // The Highcharts namespace
132 win.Highcharts = {};
133
134 /**
135  * Extend an object with the members of another
136  * @param {Object} a The object to be extended
137  * @param {Object} b The object to add to the first one
138  */
139 function extend(a, b) {
140         var n;
141         if (!a) {
142                 a = {};
143         }
144         for (n in b) {
145                 a[n] = b[n];
146         }
147         return a;
148 }
149
150 /**
151  * Take an array and turn into a hash with even number arguments as keys and odd numbers as
152  * values. Allows creating constants for commonly used style properties, attributes etc.
153  * Avoid it in performance critical situations like looping
154  */
155 function hash() {
156         var i = 0,
157                 args = arguments,
158                 length = args.length,
159                 obj = {};
160         for (; i < length; i++) {
161                 obj[args[i++]] = args[i];
162         }
163         return obj;
164 }
165
166 /**
167  * Shortcut for parseInt
168  * @param {Object} s
169  * @param {Number} mag Magnitude
170  */
171 function pInt(s, mag) {
172         return parseInt(s, mag || 10);
173 }
174
175 /**
176  * Check for string
177  * @param {Object} s
178  */
179 function isString(s) {
180         return typeof s === 'string';
181 }
182
183 /**
184  * Check for object
185  * @param {Object} obj
186  */
187 function isObject(obj) {
188         return typeof obj === 'object';
189 }
190
191 /**
192  * Check for array
193  * @param {Object} obj
194  */
195 function isArray(obj) {
196         return Object.prototype.toString.call(obj) === '[object Array]';
197 }
198
199 /**
200  * Check for number
201  * @param {Object} n
202  */
203 function isNumber(n) {
204         return typeof n === 'number';
205 }
206
207 function log2lin(num) {
208         return math.log(num) / math.LN10;
209 }
210 function lin2log(num) {
211         return math.pow(10, num);
212 }
213
214 /**
215  * Remove last occurence of an item from an array
216  * @param {Array} arr
217  * @param {Mixed} item
218  */
219 function erase(arr, item) {
220         var i = arr.length;
221         while (i--) {
222                 if (arr[i] === item) {
223                         arr.splice(i, 1);
224                         break;
225                 }
226         }
227         //return arr;
228 }
229
230 /**
231  * Returns true if the object is not null or undefined. Like MooTools' $.defined.
232  * @param {Object} obj
233  */
234 function defined(obj) {
235         return obj !== UNDEFINED && obj !== null;
236 }
237
238 /**
239  * Set or get an attribute or an object of attributes. Can't use jQuery attr because
240  * it attempts to set expando properties on the SVG element, which is not allowed.
241  *
242  * @param {Object} elem The DOM element to receive the attribute(s)
243  * @param {String|Object} prop The property or an abject of key-value pairs
244  * @param {String} value The value if a single property is set
245  */
246 function attr(elem, prop, value) {
247         var key,
248                 setAttribute = 'setAttribute',
249                 ret;
250
251         // if the prop is a string
252         if (isString(prop)) {
253                 // set the value
254                 if (defined(value)) {
255
256                         elem[setAttribute](prop, value);
257
258                 // get the value
259                 } else if (elem && elem.getAttribute) { // elem not defined when printing pie demo...
260                         ret = elem.getAttribute(prop);
261                 }
262
263         // else if prop is defined, it is a hash of key/value pairs
264         } else if (defined(prop) && isObject(prop)) {
265                 for (key in prop) {
266                         elem[setAttribute](key, prop[key]);
267                 }
268         }
269         return ret;
270 }
271 /**
272  * Check if an element is an array, and if not, make it into an array. Like
273  * MooTools' $.splat.
274  */
275 function splat(obj) {
276         return isArray(obj) ? obj : [obj];
277 }
278
279
280 /**
281  * Return the first value that is defined. Like MooTools' $.pick.
282  */
283 function pick() {
284         var args = arguments,
285                 i,
286                 arg,
287                 length = args.length;
288         for (i = 0; i < length; i++) {
289                 arg = args[i];
290                 if (typeof arg !== 'undefined' && arg !== null) {
291                         return arg;
292                 }
293         }
294 }
295
296 /**
297  * Set CSS on a given element
298  * @param {Object} el
299  * @param {Object} styles Style object with camel case property names
300  */
301 function css(el, styles) {
302         if (isIE) {
303                 if (styles && styles.opacity !== UNDEFINED) {
304                         styles.filter = 'alpha(opacity=' + (styles.opacity * 100) + ')';
305                 }
306         }
307         extend(el.style, styles);
308 }
309
310 /**
311  * Utility function to create element with attributes and styles
312  * @param {Object} tag
313  * @param {Object} attribs
314  * @param {Object} styles
315  * @param {Object} parent
316  * @param {Object} nopad
317  */
318 function createElement(tag, attribs, styles, parent, nopad) {
319         var el = doc.createElement(tag);
320         if (attribs) {
321                 extend(el, attribs);
322         }
323         if (nopad) {
324                 css(el, {padding: 0, border: NONE, margin: 0});
325         }
326         if (styles) {
327                 css(el, styles);
328         }
329         if (parent) {
330                 parent.appendChild(el);
331         }
332         return el;
333 }
334
335 /**
336  * Extend a prototyped class by new members
337  * @param {Object} parent
338  * @param {Object} members
339  */
340 function extendClass(parent, members) {
341         var object = function () {};
342         object.prototype = new parent();
343         extend(object.prototype, members);
344         return object;
345 }
346
347 /**
348  * Format a number and return a string based on input settings
349  * @param {Number} number The input number to format
350  * @param {Number} decimals The amount of decimals
351  * @param {String} decPoint The decimal point, defaults to the one given in the lang options
352  * @param {String} thousandsSep The thousands separator, defaults to the one given in the lang options
353  */
354 function numberFormat(number, decimals, decPoint, thousandsSep) {
355         var lang = defaultOptions.lang,
356                 // http://kevin.vanzonneveld.net/techblog/article/javascript_equivalent_for_phps_number_format/
357                 n = number,
358                 c = isNaN(decimals = mathAbs(decimals)) ? 2 : decimals,
359                 d = decPoint === undefined ? lang.decimalPoint : decPoint,
360                 t = thousandsSep === undefined ? lang.thousandsSep : thousandsSep,
361                 s = n < 0 ? "-" : "",
362                 i = String(pInt(n = mathAbs(+n || 0).toFixed(c))),
363                 j = i.length > 3 ? i.length % 3 : 0;
364
365         return s + (j ? i.substr(0, j) + t : "") + i.substr(j).replace(/(\d{3})(?=\d)/g, "$1" + t) +
366                 (c ? d + mathAbs(n - i).toFixed(c).slice(2) : "");
367 }
368
369 /**
370  * Based on http://www.php.net/manual/en/function.strftime.php
371  * @param {String} format
372  * @param {Number} timestamp
373  * @param {Boolean} capitalize
374  */
375 dateFormat = function (format, timestamp, capitalize) {
376         function pad(number, length) {
377                 // two digits
378                 number = number.toString().replace(/^([0-9])$/, '0$1');
379                 // three digits
380                 if (length === 3) {
381                         number = number.toString().replace(/^([0-9]{2})$/, '0$1');
382                 }
383                 return number;
384         }
385
386         if (!defined(timestamp) || isNaN(timestamp)) {
387                 return 'Invalid date';
388         }
389         format = pick(format, '%Y-%m-%d %H:%M:%S');
390
391         var date = new Date(timestamp),
392                 key, // used in for constuct below
393                 // get the basic time values
394                 hours = date[getHours](),
395                 day = date[getDay](),
396                 dayOfMonth = date[getDate](),
397                 month = date[getMonth](),
398                 fullYear = date[getFullYear](),
399                 lang = defaultOptions.lang,
400                 langWeekdays = lang.weekdays,
401                 /* // uncomment this and the 'W' format key below to enable week numbers
402                 weekNumber = function () {
403                         var clone = new Date(date.valueOf()),
404                                 day = clone[getDay]() == 0 ? 7 : clone[getDay](),
405                                 dayNumber;
406                         clone.setDate(clone[getDate]() + 4 - day);
407                         dayNumber = mathFloor((clone.getTime() - new Date(clone[getFullYear](), 0, 1, -6)) / 86400000);
408                         return 1 + mathFloor(dayNumber / 7);
409                 },
410                 */
411
412                 // list all format keys
413                 replacements = {
414
415                         // Day
416                         'a': langWeekdays[day].substr(0, 3), // Short weekday, like 'Mon'
417                         'A': langWeekdays[day], // Long weekday, like 'Monday'
418                         'd': pad(dayOfMonth), // Two digit day of the month, 01 to 31
419                         'e': dayOfMonth, // Day of the month, 1 through 31
420
421                         // Week (none implemented)
422                         //'W': weekNumber(),
423
424                         // Month
425                         'b': lang.shortMonths[month], // Short month, like 'Jan'
426                         'B': lang.months[month], // Long month, like 'January'
427                         'm': pad(month + 1), // Two digit month number, 01 through 12
428
429                         // Year
430                         'y': fullYear.toString().substr(2, 2), // Two digits year, like 09 for 2009
431                         'Y': fullYear, // Four digits year, like 2009
432
433                         // Time
434                         'H': pad(hours), // Two digits hours in 24h format, 00 through 23
435                         'I': pad((hours % 12) || 12), // Two digits hours in 12h format, 00 through 11
436                         'l': (hours % 12) || 12, // Hours in 12h format, 1 through 12
437                         'M': pad(date[getMinutes]()), // Two digits minutes, 00 through 59
438                         'p': hours < 12 ? 'AM' : 'PM', // Upper case AM or PM
439                         'P': hours < 12 ? 'am' : 'pm', // Lower case AM or PM
440                         'S': pad(date.getSeconds()), // Two digits seconds, 00 through  59
441                         'L': pad(timestamp % 1000, 3) // Milliseconds (naming from Ruby)
442                 };
443
444
445         // do the replaces
446         for (key in replacements) {
447                 format = format.replace('%' + key, replacements[key]);
448         }
449
450         // Optionally capitalize the string and return
451         return capitalize ? format.substr(0, 1).toUpperCase() + format.substr(1) : format;
452 };
453
454 /**
455  * Take an interval and normalize it to multiples of 1, 2, 2.5 and 5
456  * @param {Number} interval
457  * @param {Array} multiples
458  * @param {Number} magnitude
459  * @param {Object} options
460  */
461 function normalizeTickInterval(interval, multiples, magnitude, options) {
462         var normalized, i;
463
464         // round to a tenfold of 1, 2, 2.5 or 5
465         //magnitude = multiples ? 1 : math.pow(10, mathFloor(math.log(interval) / math.LN10));
466         magnitude = pick(magnitude, 1);
467         normalized = interval / magnitude;
468
469         // multiples for a linear scale
470         if (!multiples) {
471                 multiples = [1, 2, 2.5, 5, 10];
472                 //multiples = [1, 2, 2.5, 4, 5, 7.5, 10];
473
474                 // the allowDecimals option
475                 if (options && (options.allowDecimals === false || options.type === 'logarithmic')) {
476                         if (magnitude === 1) {
477                                 multiples = [1, 2, 5, 10];
478                         } else if (magnitude <= 0.1) {
479                                 multiples = [1 / magnitude];
480                         }
481                 }
482         }
483
484         // normalize the interval to the nearest multiple
485         for (i = 0; i < multiples.length; i++) {
486                 interval = multiples[i];
487                 if (normalized <= (multiples[i] + (multiples[i + 1] || multiples[i])) / 2) {
488                         break;
489                 }
490         }
491
492         // multiply back to the correct magnitude
493         interval *= magnitude;
494
495         return interval;
496 }
497
498 /**
499  * Get a normalized tick interval for dates. Returns a configuration object with
500  * unit range (interval), count and name. Used to prepare data for getTimeTicks. 
501  * Previously this logic was part of getTimeTicks, but as getTimeTicks now runs
502  * of segments in stock charts, the normalizing logic was extracted in order to 
503  * prevent it for running over again for each segment having the same interval. 
504  * #662, #697.
505  */
506 function normalizeTimeTickInterval(tickInterval, unitsOption) {
507         var units = unitsOption || [[
508                                 MILLISECOND, // unit name
509                                 [1, 2, 5, 10, 20, 25, 50, 100, 200, 500] // allowed multiples
510                         ], [
511                                 SECOND,
512                                 [1, 2, 5, 10, 15, 30]
513                         ], [
514                                 MINUTE,
515                                 [1, 2, 5, 10, 15, 30]
516                         ], [
517                                 HOUR,
518                                 [1, 2, 3, 4, 6, 8, 12]
519                         ], [
520                                 DAY,
521                                 [1, 2]
522                         ], [
523                                 WEEK,
524                                 [1, 2]
525                         ], [
526                                 MONTH,
527                                 [1, 2, 3, 4, 6]
528                         ], [
529                                 YEAR,
530                                 null
531                         ]],
532                 unit = units[units.length - 1], // default unit is years
533                 interval = timeUnits[unit[0]],
534                 multiples = unit[1],
535                 count,
536                 i;
537                 
538         // loop through the units to find the one that best fits the tickInterval
539         for (i = 0; i < units.length; i++) {
540                 unit = units[i];
541                 interval = timeUnits[unit[0]];
542                 multiples = unit[1];
543
544
545                 if (units[i + 1]) {
546                         // lessThan is in the middle between the highest multiple and the next unit.
547                         var lessThan = (interval * multiples[multiples.length - 1] +
548                                                 timeUnits[units[i + 1][0]]) / 2;
549
550                         // break and keep the current unit
551                         if (tickInterval <= lessThan) {
552                                 break;
553                         }
554                 }
555         }
556
557         // prevent 2.5 years intervals, though 25, 250 etc. are allowed
558         if (interval === timeUnits[YEAR] && tickInterval < 5 * interval) {
559                 multiples = [1, 2, 5];
560         }
561         
562         // prevent 2.5 years intervals, though 25, 250 etc. are allowed
563         if (interval === timeUnits[YEAR] && tickInterval < 5 * interval) {
564                 multiples = [1, 2, 5];
565         }
566
567         // get the count
568         count = normalizeTickInterval(tickInterval / interval, multiples);
569         
570         return {
571                 unitRange: interval,
572                 count: count,
573                 unitName: unit[0]
574         };
575 }
576
577 /**
578  * Set the tick positions to a time unit that makes sense, for example
579  * on the first of each month or on every Monday. Return an array
580  * with the time positions. Used in datetime axes as well as for grouping
581  * data on a datetime axis.
582  *
583  * @param {Object} normalizedInterval The interval in axis values (ms) and the count
584  * @param {Number} min The minimum in axis values
585  * @param {Number} max The maximum in axis values
586  * @param {Number} startOfWeek
587  */
588 function getTimeTicks(normalizedInterval, min, max, startOfWeek) {
589         var tickPositions = [],
590                 i,
591                 higherRanks = {},
592                 useUTC = defaultOptions.global.useUTC,
593                 minYear, // used in months and years as a basis for Date.UTC()
594                 minDate = new Date(min),
595                 interval = normalizedInterval.unitRange,
596                 count = normalizedInterval.count;
597
598         minDate.setMilliseconds(0);
599
600         if (interval >= timeUnits[SECOND]) { // second
601                 minDate.setSeconds(interval >= timeUnits[MINUTE] ? 0 :
602                         count * mathFloor(minDate.getSeconds() / count));
603         }
604
605         if (interval >= timeUnits[MINUTE]) { // minute
606                 minDate[setMinutes](interval >= timeUnits[HOUR] ? 0 :
607                         count * mathFloor(minDate[getMinutes]() / count));
608         }
609
610         if (interval >= timeUnits[HOUR]) { // hour
611                 minDate[setHours](interval >= timeUnits[DAY] ? 0 :
612                         count * mathFloor(minDate[getHours]() / count));
613         }
614
615         if (interval >= timeUnits[DAY]) { // day
616                 minDate[setDate](interval >= timeUnits[MONTH] ? 1 :
617                         count * mathFloor(minDate[getDate]() / count));
618         }
619
620         if (interval >= timeUnits[MONTH]) { // month
621                 minDate[setMonth](interval >= timeUnits[YEAR] ? 0 :
622                         count * mathFloor(minDate[getMonth]() / count));
623                 minYear = minDate[getFullYear]();
624         }
625
626         if (interval >= timeUnits[YEAR]) { // year
627                 minYear -= minYear % count;
628                 minDate[setFullYear](minYear);
629         }
630
631         // week is a special case that runs outside the hierarchy
632         if (interval === timeUnits[WEEK]) {
633                 // get start of current week, independent of count
634                 minDate[setDate](minDate[getDate]() - minDate[getDay]() +
635                         pick(startOfWeek, 1));
636         }
637
638
639         // get tick positions
640         i = 1;
641         minYear = minDate[getFullYear]();
642         var time = minDate.getTime(),
643                 minMonth = minDate[getMonth](),
644                 minDateDate = minDate[getDate]();
645
646         // iterate and add tick positions at appropriate values
647         while (time < max) {
648                 tickPositions.push(time);
649
650                 // if the interval is years, use Date.UTC to increase years
651                 if (interval === timeUnits[YEAR]) {
652                         time = makeTime(minYear + i * count, 0);
653
654                 // if the interval is months, use Date.UTC to increase months
655                 } else if (interval === timeUnits[MONTH]) {
656                         time = makeTime(minYear, minMonth + i * count);
657
658                 // if we're using global time, the interval is not fixed as it jumps
659                 // one hour at the DST crossover
660                 } else if (!useUTC && (interval === timeUnits[DAY] || interval === timeUnits[WEEK])) {
661                         time = makeTime(minYear, minMonth, minDateDate +
662                                 i * count * (interval === timeUnits[DAY] ? 1 : 7));
663
664                 // else, the interval is fixed and we use simple addition
665                 } else {
666                         time += interval * count;
667                         
668                         // mark new days if the time is dividable by day
669                         if (interval <= timeUnits[HOUR] && time % timeUnits[DAY] === 0) {
670                                 higherRanks[time] = DAY;
671                         }
672                 }
673
674                 i++;
675         }
676         
677         // push the last time
678         tickPositions.push(time);
679
680         // record information on the chosen unit - for dynamic label formatter
681         tickPositions.info = extend(normalizedInterval, {
682                 higherRanks: higherRanks,
683                 totalRange: interval * count
684         });
685
686         return tickPositions;
687 }
688
689 /**
690  * Helper class that contains variuos counters that are local to the chart.
691  */
692 function ChartCounters() {
693         this.color = 0;
694         this.symbol = 0;
695 }
696
697 ChartCounters.prototype =  {
698         /**
699          * Wraps the color counter if it reaches the specified length.
700          */
701         wrapColor: function (length) {
702                 if (this.color >= length) {
703                         this.color = 0;
704                 }
705         },
706
707         /**
708          * Wraps the symbol counter if it reaches the specified length.
709          */
710         wrapSymbol: function (length) {
711                 if (this.symbol >= length) {
712                         this.symbol = 0;
713                 }
714         }
715 };
716
717 /**
718  * Utility method extracted from Tooltip code that places a tooltip in a chart without spilling over
719  * and not covering the point it self.
720  */
721 function placeBox(boxWidth, boxHeight, outerLeft, outerTop, outerWidth, outerHeight, point, distance, preferRight) {
722         
723         // keep the box within the chart area
724         var pointX = point.x,
725                 pointY = point.y,
726                 x = pointX + outerLeft + (preferRight ? distance : -boxWidth - distance),
727                 y = pointY - boxHeight + outerTop + 15, // 15 means the point is 15 pixels up from the bottom of the tooltip
728                 alignedRight;
729
730         // it is too far to the left, adjust it
731         if (x < 7) {
732                 x = outerLeft + pointX + distance;
733         }
734
735         // Test to see if the tooltip is too far to the right,
736         // if it is, move it back to be inside and then up to not cover the point.
737         if ((x + boxWidth) > (outerLeft + outerWidth)) {
738                 x -= (x + boxWidth) - (outerLeft + outerWidth);
739                 y = pointY - boxHeight + outerTop - distance;
740                 alignedRight = true;
741         }
742
743         // if it is now above the plot area, align it to the top of the plot area
744         if (y < outerTop + 5) {
745                 y = outerTop + 5;
746
747                 // If the tooltip is still covering the point, move it below instead
748                 if (alignedRight && pointY >= y && pointY <= (y + boxHeight)) {
749                         y = pointY + outerTop + distance; // below
750                 }
751         } else if (y + boxHeight > outerTop + outerHeight) {
752                 y = outerTop + outerHeight - boxHeight - distance; // below
753         }
754
755         return {x: x, y: y};
756 }
757
758 /**
759  * Utility method that sorts an object array and keeping the order of equal items.
760  * ECMA script standard does not specify the behaviour when items are equal.
761  */
762 function stableSort(arr, sortFunction) {
763         var length = arr.length,
764                 sortValue,
765                 i;
766
767         // Add index to each item
768         for (i = 0; i < length; i++) {
769                 arr[i].ss_i = i; // stable sort index
770         }
771
772         arr.sort(function (a, b) {
773                 sortValue = sortFunction(a, b);
774                 return sortValue === 0 ? a.ss_i - b.ss_i : sortValue;
775         });
776
777         // Remove index from items
778         for (i = 0; i < length; i++) {
779                 delete arr[i].ss_i; // stable sort index
780         }
781 }
782
783 /**
784  * Non-recursive method to find the lowest member of an array. Math.min raises a maximum
785  * call stack size exceeded error in Chrome when trying to apply more than 150.000 points. This
786  * method is slightly slower, but safe.
787  */
788 function arrayMin(data) {
789         var i = data.length,
790                 min = data[0];
791
792         while (i--) {
793                 if (data[i] < min) {
794                         min = data[i];
795                 }
796         }
797         return min;
798 }
799
800 /**
801  * Non-recursive method to find the lowest member of an array. Math.min raises a maximum
802  * call stack size exceeded error in Chrome when trying to apply more than 150.000 points. This
803  * method is slightly slower, but safe.
804  */
805 function arrayMax(data) {
806         var i = data.length,
807                 max = data[0];
808
809         while (i--) {
810                 if (data[i] > max) {
811                         max = data[i];
812                 }
813         }
814         return max;
815 }
816
817 /**
818  * Utility method that destroys any SVGElement or VMLElement that are properties on the given object.
819  * It loops all properties and invokes destroy if there is a destroy method. The property is
820  * then delete'ed.
821  */
822 function destroyObjectProperties(obj) {
823         var n;
824         for (n in obj) {
825                 // If the object is non-null and destroy is defined
826                 if (obj[n] && obj[n].destroy) {
827                         // Invoke the destroy
828                         obj[n].destroy();
829                 }
830
831                 // Delete the property from the object.
832                 delete obj[n];
833         }
834 }
835
836
837 /**
838  * Discard an element by moving it to the bin and delete
839  * @param {Object} The HTML node to discard
840  */
841 function discardElement(element) {
842         // create a garbage bin element, not part of the DOM
843         if (!garbageBin) {
844                 garbageBin = createElement(DIV);
845         }
846
847         // move the node and empty bin
848         if (element) {
849                 garbageBin.appendChild(element);
850         }
851         garbageBin.innerHTML = '';
852 }
853
854 /**
855  * The time unit lookup
856  */
857 /*jslint white: true*/
858 timeUnits = hash(
859         MILLISECOND, 1,
860         SECOND, 1000,
861         MINUTE, 60000,
862         HOUR, 3600000,
863         DAY, 24 * 3600000,
864         WEEK, 7 * 24 * 3600000,
865         MONTH, 30 * 24 * 3600000,
866         YEAR, 31556952000
867 );
868 /*jslint white: false*/
869 /**
870  * Path interpolation algorithm used across adapters
871  */
872 pathAnim = {
873         /**
874          * Prepare start and end values so that the path can be animated one to one
875          */
876         init: function (elem, fromD, toD) {
877                 fromD = fromD || '';
878                 var shift = elem.shift,
879                         bezier = fromD.indexOf('C') > -1,
880                         numParams = bezier ? 7 : 3,
881                         endLength,
882                         slice,
883                         i,
884                         start = fromD.split(' '),
885                         end = [].concat(toD), // copy
886                         startBaseLine,
887                         endBaseLine,
888                         sixify = function (arr) { // in splines make move points have six parameters like bezier curves
889                                 i = arr.length;
890                                 while (i--) {
891                                         if (arr[i] === M) {
892                                                 arr.splice(i + 1, 0, arr[i + 1], arr[i + 2], arr[i + 1], arr[i + 2]);
893                                         }
894                                 }
895                         };
896
897                 if (bezier) {
898                         sixify(start);
899                         sixify(end);
900                 }
901
902                 // pull out the base lines before padding
903                 if (elem.isArea) {
904                         startBaseLine = start.splice(start.length - 6, 6);
905                         endBaseLine = end.splice(end.length - 6, 6);
906                 }
907
908                 // if shifting points, prepend a dummy point to the end path
909                 if (shift === 1) {
910
911                         end = [].concat(end).splice(0, numParams).concat(end);
912                 }
913                 elem.shift = 0; // reset for following animations
914
915                 // copy and append last point until the length matches the end length
916                 if (start.length) {
917                         endLength = end.length;
918                         while (start.length < endLength) {
919
920                                 //bezier && sixify(start);
921                                 slice = [].concat(start).splice(start.length - numParams, numParams);
922                                 if (bezier) { // disable first control point
923                                         slice[numParams - 6] = slice[numParams - 2];
924                                         slice[numParams - 5] = slice[numParams - 1];
925                                 }
926                                 start = start.concat(slice);
927                         }
928                 }
929
930                 if (startBaseLine) { // append the base lines for areas
931                         start = start.concat(startBaseLine);
932                         end = end.concat(endBaseLine);
933                 }
934                 return [start, end];
935         },
936
937         /**
938          * Interpolate each value of the path and return the array
939          */
940         step: function (start, end, pos, complete) {
941                 var ret = [],
942                         i = start.length,
943                         startVal;
944
945                 if (pos === 1) { // land on the final path without adjustment points appended in the ends
946                         ret = complete;
947
948                 } else if (i === end.length && pos < 1) {
949                         while (i--) {
950                                 startVal = parseFloat(start[i]);
951                                 ret[i] =
952                                         isNaN(startVal) ? // a letter instruction like M or L
953                                                 start[i] :
954                                                 pos * (parseFloat(end[i] - startVal)) + startVal;
955
956                         }
957                 } else { // if animation is finished or length not matching, land on right value
958                         ret = end;
959                 }
960                 return ret;
961         }
962 };
963
964
965 /**
966  * Set the global animation to either a given value, or fall back to the
967  * given chart's animation option
968  * @param {Object} animation
969  * @param {Object} chart
970  */
971 function setAnimation(animation, chart) {
972         globalAnimation = pick(animation, chart.animation);
973 }
974
975 /*
976  * Define the adapter for frameworks. If an external adapter is not defined,
977  * Highcharts reverts to the built-in jQuery adapter.
978  */
979 if (globalAdapter && globalAdapter.init) {
980         // Initialize the adapter with the pathAnim object that takes care
981         // of path animations.
982         globalAdapter.init(pathAnim);
983 }
984 if (!globalAdapter && win.jQuery) {
985         var jQ = jQuery;
986
987         /**
988          * Utility for iterating over an array. Parameters are reversed compared to jQuery.
989          * @param {Array} arr
990          * @param {Function} fn
991          */
992         each = function (arr, fn) {
993                 var i = 0,
994                         len = arr.length;
995                 for (; i < len; i++) {
996                         if (fn.call(arr[i], arr[i], i, arr) === false) {
997                                 return i;
998                         }
999                 }
1000         };
1001
1002         /**
1003          * Filter an array
1004          */
1005         grep = jQ.grep;
1006
1007         /**
1008          * Map an array
1009          * @param {Array} arr
1010          * @param {Function} fn
1011          */
1012         map = function (arr, fn) {
1013                 //return jQuery.map(arr, fn);
1014                 var results = [],
1015                         i = 0,
1016                         len = arr.length;
1017                 for (; i < len; i++) {
1018                         results[i] = fn.call(arr[i], arr[i], i, arr);
1019                 }
1020                 return results;
1021
1022         };
1023
1024         /**
1025          * Deep merge two objects and return a third object
1026          */
1027         merge = function () {
1028                 var args = arguments;
1029                 return jQ.extend(true, null, args[0], args[1], args[2], args[3]);
1030         };
1031
1032         /**
1033          * Get the position of an element relative to the top left of the page
1034          */
1035         offset = function (el) {
1036                 return jQ(el).offset();
1037         };
1038
1039         /**
1040          * Add an event listener
1041          * @param {Object} el A HTML element or custom object
1042          * @param {String} event The event type
1043          * @param {Function} fn The event handler
1044          */
1045         addEvent = function (el, event, fn) {
1046                 jQ(el).bind(event, fn);
1047         };
1048
1049         /**
1050          * Remove event added with addEvent
1051          * @param {Object} el The object
1052          * @param {String} eventType The event type. Leave blank to remove all events.
1053          * @param {Function} handler The function to remove
1054          */
1055         removeEvent = function (el, eventType, handler) {
1056                 // workaround for jQuery issue with unbinding custom events:
1057                 // http://forum.jquery.com/topic/javascript-error-when-unbinding-a-custom-event-using-jquery-1-4-2
1058                 var func = doc.removeEventListener ? 'removeEventListener' : 'detachEvent';
1059                 if (doc[func] && !el[func]) {
1060                         el[func] = function () {};
1061                 }
1062
1063                 jQ(el).unbind(eventType, handler);
1064         };
1065
1066         /**
1067          * Fire an event on a custom object
1068          * @param {Object} el
1069          * @param {String} type
1070          * @param {Object} eventArguments
1071          * @param {Function} defaultFunction
1072          */
1073         fireEvent = function (el, type, eventArguments, defaultFunction) {
1074                 var event = jQ.Event(type),
1075                         detachedType = 'detached' + type,
1076                         defaultPrevented;
1077                         
1078                 extend(event, eventArguments);
1079
1080                 // Prevent jQuery from triggering the object method that is named the
1081                 // same as the event. For example, if the event is 'select', jQuery
1082                 // attempts calling el.select and it goes into a loop.
1083                 if (el[type]) {
1084                         el[detachedType] = el[type];
1085                         el[type] = null;
1086                 }
1087                 
1088                 // Wrap preventDefault and stopPropagation in try/catch blocks in
1089                 // order to prevent JS errors when cancelling events on non-DOM
1090                 // objects. #615.
1091                 each(['preventDefault', 'stopPropagation'], function (fn) {
1092                         var base = event[fn];
1093                         event[fn] = function () {
1094                                 try {
1095                                         base.call(event);
1096                                 } catch (e) {
1097                                         if (fn === 'preventDefault') {
1098                                                 defaultPrevented = true;
1099                                         }
1100                                 }
1101                         };
1102                 });
1103
1104                 // trigger it
1105                 jQ(el).trigger(event);
1106
1107                 // attach the method
1108                 if (el[detachedType]) {
1109                         el[type] = el[detachedType];
1110                         el[detachedType] = null;
1111                 }
1112
1113                 if (defaultFunction && !event.isDefaultPrevented() && !defaultPrevented) {
1114                         defaultFunction(event);
1115                 }
1116         };
1117
1118         /**
1119          * Animate a HTML element or SVG element wrapper
1120          * @param {Object} el
1121          * @param {Object} params
1122          * @param {Object} options jQuery-like animation options: duration, easing, callback
1123          */
1124         animate = function (el, params, options) {
1125                 var $el = jQ(el);
1126                 if (params.d) {
1127                         el.toD = params.d; // keep the array form for paths, used in jQ.fx.step.d
1128                         params.d = 1; // because in jQuery, animating to an array has a different meaning
1129                 }
1130
1131                 $el.stop();
1132                 $el.animate(params, options);
1133
1134         };
1135         /**
1136          * Stop running animation
1137          */
1138         stop = function (el) {
1139                 jQ(el).stop();
1140         };
1141
1142
1143         //=== Extend jQuery on init
1144         
1145         /*jslint unparam: true*//* allow unused param x in this function */
1146         jQ.extend(jQ.easing, {
1147                 easeOutQuad: function (x, t, b, c, d) {
1148                         return -c * (t /= d) * (t - 2) + b;
1149                 }
1150         });
1151         /*jslint unparam: false*/
1152
1153         // extend the animate function to allow SVG animations
1154         var jFx = jQuery.fx,
1155                 jStep = jFx.step;
1156                 
1157         // extend some methods to check for elem.attr, which means it is a Highcharts SVG object
1158         each(['cur', '_default', 'width', 'height'], function (fn, i) {
1159                 var obj = i ? jStep : jFx.prototype, // 'cur', the getter' relates to jFx.prototype
1160                         base = obj[fn],
1161                         elem;
1162                 
1163                 if (base) { // step.width and step.height don't exist in jQuery < 1.7
1164                 
1165                         // create the extended function replacement
1166                         obj[fn] = function (fx) {
1167                                 
1168                                 // jFx.prototype.cur does not use fx argument
1169                                 fx = i ? fx : this;
1170                                 
1171                                 // shortcut
1172                                 elem = fx.elem;
1173                                 
1174                                 // jFX.prototype.cur returns the current value. The other ones are setters 
1175                                 // and returning a value has no effect.
1176                                 return elem.attr ? // is SVG element wrapper
1177                                         elem.attr(fx.prop, fx.now) : // apply the SVG wrapper's method
1178                                         base.apply(this, arguments); // use jQuery's built-in method
1179                         };
1180                 }
1181         });
1182         
1183         // animate paths
1184         jStep.d = function (fx) {
1185                 var elem = fx.elem;
1186
1187
1188                 // Normally start and end should be set in state == 0, but sometimes,
1189                 // for reasons unknown, this doesn't happen. Perhaps state == 0 is skipped
1190                 // in these cases
1191                 if (!fx.started) {
1192                         var ends = pathAnim.init(elem, elem.d, elem.toD);
1193                         fx.start = ends[0];
1194                         fx.end = ends[1];
1195                         fx.started = true;
1196                 }
1197
1198
1199                 // interpolate each value of the path
1200                 elem.attr('d', pathAnim.step(fx.start, fx.end, fx.pos, elem.toD));
1201
1202         };
1203 }
1204
1205 /* ****************************************************************************
1206  * Handle the options                                                         *
1207  *****************************************************************************/
1208 var
1209
1210 defaultLabelOptions = {
1211         enabled: true,
1212         // rotation: 0,
1213         align: 'center',
1214         x: 0,
1215         y: 15,
1216         /*formatter: function () {
1217                 return this.value;
1218         },*/
1219         style: {
1220                 color: '#666',
1221                 fontSize: '11px',
1222                 lineHeight: '14px'
1223         }
1224 };
1225
1226 defaultOptions = {
1227         colors: ['#4572A7', '#AA4643', '#89A54E', '#80699B', '#3D96AE',
1228                 '#DB843D', '#92A8CD', '#A47D7C', '#B5CA92'],
1229         symbols: ['circle', 'diamond', 'square', 'triangle', 'triangle-down'],
1230         lang: {
1231                 loading: 'Loading...',
1232                 months: ['January', 'February', 'March', 'April', 'May', 'June', 'July',
1233                                 'August', 'September', 'October', 'November', 'December'],
1234                 shortMonths: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
1235                 weekdays: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
1236                 decimalPoint: '.',
1237                 resetZoom: 'Reset zoom',
1238                 resetZoomTitle: 'Reset zoom level 1:1',
1239                 thousandsSep: ','
1240         },
1241         global: {
1242                 useUTC: true
1243         },
1244         chart: {
1245                 //animation: true,
1246                 //alignTicks: false,
1247                 //reflow: true,
1248                 //className: null,
1249                 //events: { load, selection },
1250                 //margin: [null],
1251                 //marginTop: null,
1252                 //marginRight: null,
1253                 //marginBottom: null,
1254                 //marginLeft: null,
1255                 borderColor: '#4572A7',
1256                 //borderWidth: 0,
1257                 borderRadius: 5,
1258                 defaultSeriesType: 'line',
1259                 ignoreHiddenSeries: true,
1260                 //inverted: false,
1261                 //shadow: false,
1262                 spacingTop: 10,
1263                 spacingRight: 10,
1264                 spacingBottom: 15,
1265                 spacingLeft: 10,
1266                 style: {
1267                         fontFamily: '"Lucida Grande", "Lucida Sans Unicode", Verdana, Arial, Helvetica, sans-serif', // default font
1268                         fontSize: '12px'
1269                 },
1270                 backgroundColor: '#FFFFFF',
1271                 //plotBackgroundColor: null,
1272                 plotBorderColor: '#C0C0C0',
1273                 //plotBorderWidth: 0,
1274                 //plotShadow: false,
1275                 //zoomType: ''
1276                 resetZoomButton: { // docs
1277                         theme: {
1278                                 zIndex: 20
1279                         },
1280                         position: {
1281                                 align: 'right',
1282                                 x: -10,
1283                                 //verticalAlign: 'top',
1284                                 y: 10
1285                         },
1286                         relativeTo: 'plot'
1287                 }
1288         },
1289         title: {
1290                 text: 'Chart title',
1291                 align: 'center',
1292                 // floating: false,
1293                 // margin: 15,
1294                 // x: 0,
1295                 // verticalAlign: 'top',
1296                 y: 15,
1297                 style: {
1298                         color: '#3E576F',
1299                         fontSize: '16px'
1300                 }
1301
1302         },
1303         subtitle: {
1304                 text: '',
1305                 align: 'center',
1306                 // floating: false
1307                 // x: 0,
1308                 // verticalAlign: 'top',
1309                 y: 30,
1310                 style: {
1311                         color: '#6D869F'
1312                 }
1313         },
1314
1315         plotOptions: {
1316                 line: { // base series options
1317                         allowPointSelect: false,
1318                         showCheckbox: false,
1319                         animation: {
1320                                 duration: 1000
1321                         },
1322                         //connectNulls: false,
1323                         //cursor: 'default',
1324                         //clip: true,
1325                         //dashStyle: null,
1326                         //enableMouseTracking: true,
1327                         events: {},
1328                         //legendIndex: 0,
1329                         lineWidth: 2,
1330                         shadow: true,
1331                         // stacking: null,
1332                         marker: {
1333                                 enabled: true,
1334                                 //symbol: null,
1335                                 lineWidth: 0,
1336                                 radius: 4,
1337                                 lineColor: '#FFFFFF',
1338                                 //fillColor: null,
1339                                 states: { // states for a single point
1340                                         hover: {
1341                                                 //radius: base + 2
1342                                         },
1343                                         select: {
1344                                                 fillColor: '#FFFFFF',
1345                                                 lineColor: '#000000',
1346                                                 lineWidth: 2
1347                                         }
1348                                 }
1349                         },
1350                         point: {
1351                                 events: {}
1352                         },
1353                         dataLabels: merge(defaultLabelOptions, {
1354                                 enabled: false,
1355                                 y: -6,
1356                                 formatter: function () {
1357                                         return this.y;
1358                                 }
1359                         }),
1360                         cropThreshold: 300, // draw points outside the plot area when the number of points is less than this
1361                         pointRange: 0,
1362                         //pointStart: 0,
1363                         //pointInterval: 1,
1364                         showInLegend: true,
1365                         states: { // states for the entire series
1366                                 hover: {
1367                                         //enabled: false,
1368                                         //lineWidth: base + 1,
1369                                         marker: {
1370                                                 // lineWidth: base + 1,
1371                                                 // radius: base + 1
1372                                         }
1373                                 },
1374                                 select: {
1375                                         marker: {}
1376                                 }
1377                         },
1378                         stickyTracking: true
1379                         //tooltip: {
1380                                 //pointFormat: '<span style="color:{series.color}">{series.name}</span>: <b>{point.y}</b>'
1381                                 //valueDecimals: null,
1382                                 //xDateFormat: '%A, %b %e, %Y',
1383                                 //valuePrefix: '',
1384                                 //ySuffix: ''                           
1385                         //}
1386                         // turboThreshold: 1000
1387                         // zIndex: null
1388                 }
1389         },
1390         labels: {
1391                 //items: [],
1392                 style: {
1393                         //font: defaultFont,
1394                         position: ABSOLUTE,
1395                         color: '#3E576F'
1396                 }
1397         },
1398         legend: {
1399                 enabled: true,
1400                 align: 'center',
1401                 //floating: false,
1402                 layout: 'horizontal',
1403                 labelFormatter: function () {
1404                         return this.name;
1405                 },
1406                 borderWidth: 1,
1407                 borderColor: '#909090',
1408                 borderRadius: 5,
1409                 // margin: 10,
1410                 // reversed: false,
1411                 shadow: false,
1412                 // backgroundColor: null,
1413                 style: {
1414                         padding: '5px'
1415                 },
1416                 itemStyle: {
1417                         cursor: 'pointer',
1418                         color: '#3E576F'
1419                 },
1420                 itemHoverStyle: {
1421                         //cursor: 'pointer', removed as of #601
1422                         color: '#000000'
1423                 },
1424                 itemHiddenStyle: {
1425                         color: '#C0C0C0'
1426                 },
1427                 itemCheckboxStyle: {
1428                         position: ABSOLUTE,
1429                         width: '13px', // for IE precision
1430                         height: '13px'
1431                 },
1432                 // itemWidth: undefined,
1433                 symbolWidth: 16,
1434                 symbolPadding: 5,
1435                 verticalAlign: 'bottom',
1436                 // width: undefined,
1437                 x: 0,
1438                 y: 0
1439         },
1440
1441         loading: {
1442                 // hideDuration: 100,
1443                 labelStyle: {
1444                         fontWeight: 'bold',
1445                         position: RELATIVE,
1446                         top: '1em'
1447                 },
1448                 // showDuration: 0,
1449                 style: {
1450                         position: ABSOLUTE,
1451                         backgroundColor: 'white',
1452                         opacity: 0.5,
1453                         textAlign: 'center'
1454                 }
1455         },
1456
1457         tooltip: {
1458                 enabled: true,
1459                 //crosshairs: null,
1460                 backgroundColor: 'rgba(255, 255, 255, .85)',
1461                 borderWidth: 2,
1462                 borderRadius: 5,
1463                 //formatter: defaultFormatter,
1464                 headerFormat: '<span style="font-size: 10px">{point.key}</span><br/>',
1465                 pointFormat: '<span style="color:{series.color}">{series.name}</span>: <b>{point.y}</b><br/>',
1466                 shadow: true,
1467                 //shared: false,
1468                 snap: hasTouch ? 25 : 10,
1469                 style: {
1470                         color: '#333333',
1471                         fontSize: '12px',
1472                         padding: '5px',
1473                         whiteSpace: 'nowrap'
1474                 }
1475                 //xDateFormat: '%A, %b %e, %Y',
1476                 //valueDecimals: null,
1477                 //valuePrefix: '',
1478                 //ySuffix: ''
1479         },
1480
1481         credits: {
1482                 enabled: true,
1483                 text: 'Highcharts.com',
1484                 href: 'http://www.highcharts.com',
1485                 position: {
1486                         align: 'right',
1487                         x: -10,
1488                         verticalAlign: 'bottom',
1489                         y: -5
1490                 },
1491                 style: {
1492                         cursor: 'pointer',
1493                         color: '#909090',
1494                         fontSize: '10px'
1495                 }
1496         }
1497 };
1498
1499 // Axis defaults
1500 /*jslint white: true*/
1501 var defaultXAxisOptions = {
1502         // allowDecimals: null,
1503         // alternateGridColor: null,
1504         // categories: [],
1505         dateTimeLabelFormats: hash(
1506                 MILLISECOND, '%H:%M:%S.%L',
1507                 SECOND, '%H:%M:%S',
1508                 MINUTE, '%H:%M',
1509                 HOUR, '%H:%M',
1510                 DAY, '%e. %b',
1511                 WEEK, '%e. %b',
1512                 MONTH, '%b \'%y',
1513                 YEAR, '%Y'
1514         ),
1515         endOnTick: false,
1516         gridLineColor: '#C0C0C0',
1517         // gridLineDashStyle: 'solid',
1518         // gridLineWidth: 0,
1519         // reversed: false,
1520
1521         labels: defaultLabelOptions,
1522                 // { step: null },
1523         lineColor: '#C0D0E0',
1524         lineWidth: 1,
1525         //linkedTo: null,
1526         max: null,
1527         min: null,
1528         minPadding: 0.01,
1529         maxPadding: 0.01,
1530         //minRange: null,
1531         minorGridLineColor: '#E0E0E0',
1532         // minorGridLineDashStyle: null,
1533         minorGridLineWidth: 1,
1534         minorTickColor: '#A0A0A0',
1535         //minorTickInterval: null,
1536         minorTickLength: 2,
1537         minorTickPosition: 'outside', // inside or outside
1538         //minorTickWidth: 0,
1539         //opposite: false,
1540         //offset: 0,
1541         //plotBands: [{
1542         //      events: {},
1543         //      zIndex: 1,
1544         //      labels: { align, x, verticalAlign, y, style, rotation, textAlign }
1545         //}],
1546         //plotLines: [{
1547         //      events: {}
1548         //  dashStyle: {}
1549         //      zIndex:
1550         //      labels: { align, x, verticalAlign, y, style, rotation, textAlign }
1551         //}],
1552         //reversed: false,
1553         // showFirstLabel: true,
1554         // showLastLabel: true,
1555         startOfWeek: 1,
1556         startOnTick: false,
1557         tickColor: '#C0D0E0',
1558         //tickInterval: null,
1559         tickLength: 5,
1560         tickmarkPlacement: 'between', // on or between
1561         tickPixelInterval: 100,
1562         tickPosition: 'outside',
1563         tickWidth: 1,
1564         title: {
1565                 //text: null,
1566                 align: 'middle', // low, middle or high
1567                 //margin: 0 for horizontal, 10 for vertical axes,
1568                 //rotation: 0,
1569                 //side: 'outside',
1570                 style: {
1571                         color: '#6D869F',
1572                         //font: defaultFont.replace('normal', 'bold')
1573                         fontWeight: 'bold'
1574                 }
1575                 //x: 0,
1576                 //y: 0
1577         },
1578         type: 'linear' // linear, logarithmic or datetime
1579 },
1580
1581 defaultYAxisOptions = merge(defaultXAxisOptions, {
1582         endOnTick: true,
1583         gridLineWidth: 1,
1584         tickPixelInterval: 72,
1585         showLastLabel: true,
1586         labels: {
1587                 align: 'right',
1588                 x: -8,
1589                 y: 3
1590         },
1591         lineWidth: 0,
1592         maxPadding: 0.05,
1593         minPadding: 0.05,
1594         startOnTick: true,
1595         tickWidth: 0,
1596         title: {
1597                 rotation: 270,
1598                 text: 'Y-values'
1599         },
1600         stackLabels: {
1601                 enabled: false,
1602                 //align: dynamic,
1603                 //y: dynamic,
1604                 //x: dynamic,
1605                 //verticalAlign: dynamic,
1606                 //textAlign: dynamic,
1607                 //rotation: 0,
1608                 formatter: function () {
1609                         return this.total;
1610                 },
1611                 style: defaultLabelOptions.style
1612         }
1613 }),
1614
1615 defaultLeftAxisOptions = {
1616         labels: {
1617                 align: 'right',
1618                 x: -8,
1619                 y: null
1620         },
1621         title: {
1622                 rotation: 270
1623         }
1624 },
1625 defaultRightAxisOptions = {
1626         labels: {
1627                 align: 'left',
1628                 x: 8,
1629                 y: null
1630         },
1631         title: {
1632                 rotation: 90
1633         }
1634 },
1635 defaultBottomAxisOptions = { // horizontal axis
1636         labels: {
1637                 align: 'center',
1638                 x: 0,
1639                 y: 14
1640                 // staggerLines: null
1641         },
1642         title: {
1643                 rotation: 0
1644         }
1645 },
1646 defaultTopAxisOptions = merge(defaultBottomAxisOptions, {
1647         labels: {
1648                 y: -5
1649                 // staggerLines: null
1650         }
1651 });
1652 /*jslint white: false*/
1653
1654
1655
1656 // Series defaults
1657 var defaultPlotOptions = defaultOptions.plotOptions,
1658         defaultSeriesOptions = defaultPlotOptions.line;
1659 //defaultPlotOptions.line = merge(defaultSeriesOptions);
1660 defaultPlotOptions.spline = merge(defaultSeriesOptions);
1661 defaultPlotOptions.scatter = merge(defaultSeriesOptions, {
1662         lineWidth: 0,
1663         states: {
1664                 hover: {
1665                         lineWidth: 0
1666                 }
1667         },
1668         tooltip: {
1669                 headerFormat: '<span style="font-size: 10px; color:{series.color}">{series.name}</span><br/>',
1670                 pointFormat: 'x: <b>{point.x}</b><br/>y: <b>{point.y}</b><br/>'
1671         }
1672 });
1673 defaultPlotOptions.area = merge(defaultSeriesOptions, {
1674         threshold: 0
1675         // lineColor: null, // overrides color, but lets fillColor be unaltered
1676         // fillOpacity: 0.75,
1677         // fillColor: null
1678
1679 });
1680 defaultPlotOptions.areaspline = merge(defaultPlotOptions.area);
1681 defaultPlotOptions.column = merge(defaultSeriesOptions, {
1682         borderColor: '#FFFFFF',
1683         borderWidth: 1,
1684         borderRadius: 0,
1685         //colorByPoint: undefined,
1686         groupPadding: 0.2,
1687         marker: null, // point options are specified in the base options
1688         pointPadding: 0.1,
1689         //pointWidth: null,
1690         minPointLength: 0,
1691         cropThreshold: 50, // when there are more points, they will not animate out of the chart on xAxis.setExtremes
1692         pointRange: null, // null means auto, meaning 1 in a categorized axis and least distance between points if not categories
1693         states: {
1694                 hover: {
1695                         brightness: 0.1,
1696                         shadow: false
1697                 },
1698                 select: {
1699                         color: '#C0C0C0',
1700                         borderColor: '#000000',
1701                         shadow: false
1702                 }
1703         },
1704         dataLabels: {
1705                 y: null,
1706                 verticalAlign: null
1707         },
1708         threshold: 0
1709 });
1710 defaultPlotOptions.bar = merge(defaultPlotOptions.column, {
1711         dataLabels: {
1712                 align: 'left',
1713                 x: 5,
1714                 y: 0
1715         }
1716 });
1717 defaultPlotOptions.pie = merge(defaultSeriesOptions, {
1718         //dragType: '', // n/a
1719         borderColor: '#FFFFFF',
1720         borderWidth: 1,
1721         center: ['50%', '50%'],
1722         colorByPoint: true, // always true for pies
1723         dataLabels: {
1724                 // align: null,
1725                 // connectorWidth: 1,
1726                 // connectorColor: point.color,
1727                 // connectorPadding: 5,
1728                 distance: 30,
1729                 enabled: true,
1730                 formatter: function () {
1731                         return this.point.name;
1732                 },
1733                 // softConnector: true,
1734                 y: 5
1735         },
1736         //innerSize: 0,
1737         legendType: 'point',
1738         marker: null, // point options are specified in the base options
1739         size: '75%',
1740         showInLegend: false,
1741         slicedOffset: 10,
1742         states: {
1743                 hover: {
1744                         brightness: 0.1,
1745                         shadow: false
1746                 }
1747         }
1748
1749 });
1750
1751 // set the default time methods
1752 setTimeMethods();
1753
1754
1755
1756 /**
1757  * Set the time methods globally based on the useUTC option. Time method can be either
1758  * local time or UTC (default).
1759  */
1760 function setTimeMethods() {
1761         var useUTC = defaultOptions.global.useUTC,
1762                 GET = useUTC ? 'getUTC' : 'get',
1763                 SET = useUTC ? 'setUTC' : 'set';
1764
1765         makeTime = useUTC ? Date.UTC : function (year, month, date, hours, minutes, seconds) {
1766                 return new Date(
1767                         year,
1768                         month,
1769                         pick(date, 1),
1770                         pick(hours, 0),
1771                         pick(minutes, 0),
1772                         pick(seconds, 0)
1773                 ).getTime();
1774         };
1775         getMinutes =  GET + 'Minutes';
1776         getHours =    GET + 'Hours';
1777         getDay =      GET + 'Day';
1778         getDate =     GET + 'Date';
1779         getMonth =    GET + 'Month';
1780         getFullYear = GET + 'FullYear';
1781         setMinutes =  SET + 'Minutes';
1782         setHours =    SET + 'Hours';
1783         setDate =     SET + 'Date';
1784         setMonth =    SET + 'Month';
1785         setFullYear = SET + 'FullYear';
1786
1787 }
1788
1789 /**
1790  * Merge the default options with custom options and return the new options structure
1791  * @param {Object} options The new custom options
1792  */
1793 function setOptions(options) {
1794         
1795         // Pull out axis options and apply them to the respective default axis options 
1796         defaultXAxisOptions = merge(defaultXAxisOptions, options.xAxis);
1797         defaultYAxisOptions = merge(defaultYAxisOptions, options.yAxis);
1798         options.xAxis = options.yAxis = UNDEFINED;
1799         
1800         // Merge in the default options
1801         defaultOptions = merge(defaultOptions, options);
1802         
1803         // Apply UTC
1804         setTimeMethods();
1805
1806         return defaultOptions;
1807 }
1808
1809 /**
1810  * Get the updated default options. Merely exposing defaultOptions for outside modules
1811  * isn't enough because the setOptions method creates a new object.
1812  */
1813 function getOptions() {
1814         return defaultOptions;
1815 }
1816
1817
1818
1819 /**
1820  * Handle color operations. The object methods are chainable.
1821  * @param {String} input The input color in either rbga or hex format
1822  */
1823 var Color = function (input) {
1824         // declare variables
1825         var rgba = [], result;
1826
1827         /**
1828          * Parse the input color to rgba array
1829          * @param {String} input
1830          */
1831         function init(input) {
1832
1833                 // rgba
1834                 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);
1835                 if (result) {
1836                         rgba = [pInt(result[1]), pInt(result[2]), pInt(result[3]), parseFloat(result[4], 10)];
1837                 } else { // hex
1838                         result = /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(input);
1839                         if (result) {
1840                                 rgba = [pInt(result[1], 16), pInt(result[2], 16), pInt(result[3], 16), 1];
1841                         }
1842                 }
1843
1844         }
1845         /**
1846          * Return the color a specified format
1847          * @param {String} format
1848          */
1849         function get(format) {
1850                 var ret;
1851
1852                 // it's NaN if gradient colors on a column chart
1853                 if (rgba && !isNaN(rgba[0])) {
1854                         if (format === 'rgb') {
1855                                 ret = 'rgb(' + rgba[0] + ',' + rgba[1] + ',' + rgba[2] + ')';
1856                         } else if (format === 'a') {
1857                                 ret = rgba[3];
1858                         } else {
1859                                 ret = 'rgba(' + rgba.join(',') + ')';
1860                         }
1861                 } else {
1862                         ret = input;
1863                 }
1864                 return ret;
1865         }
1866
1867         /**
1868          * Brighten the color
1869          * @param {Number} alpha
1870          */
1871         function brighten(alpha) {
1872                 if (isNumber(alpha) && alpha !== 0) {
1873                         var i;
1874                         for (i = 0; i < 3; i++) {
1875                                 rgba[i] += pInt(alpha * 255);
1876
1877                                 if (rgba[i] < 0) {
1878                                         rgba[i] = 0;
1879                                 }
1880                                 if (rgba[i] > 255) {
1881                                         rgba[i] = 255;
1882                                 }
1883                         }
1884                 }
1885                 return this;
1886         }
1887         /**
1888          * Set the color's opacity to a given alpha value
1889          * @param {Number} alpha
1890          */
1891         function setOpacity(alpha) {
1892                 rgba[3] = alpha;
1893                 return this;
1894         }
1895
1896         // initialize: parse the input
1897         init(input);
1898
1899         // public methods
1900         return {
1901                 get: get,
1902                 brighten: brighten,
1903                 setOpacity: setOpacity
1904         };
1905 };
1906
1907
1908 /**
1909  * A wrapper object for SVG elements
1910  */
1911 function SVGElement() {}
1912
1913 SVGElement.prototype = {
1914         /**
1915          * Initialize the SVG renderer
1916          * @param {Object} renderer
1917          * @param {String} nodeName
1918          */
1919         init: function (renderer, nodeName) {
1920                 var wrapper = this;
1921                 wrapper.element = doc.createElementNS(SVG_NS, nodeName);
1922                 wrapper.renderer = renderer;
1923                 /**
1924                  * A collection of attribute setters. These methods, if defined, are called right before a certain
1925                  * attribute is set on an element wrapper. Returning false prevents the default attribute
1926                  * setter to run. Returning a value causes the default setter to set that value. Used in
1927                  * Renderer.label.
1928                  */
1929                 wrapper.attrSetters = {};
1930         },
1931         /**
1932          * Animate a given attribute
1933          * @param {Object} params
1934          * @param {Number} options The same options as in jQuery animation
1935          * @param {Function} complete Function to perform at the end of animation
1936          */
1937         animate: function (params, options, complete) {
1938                 var animOptions = pick(options, globalAnimation, true);
1939                 stop(this); // stop regardless of animation actually running, or reverting to .attr (#607)                      
1940                 if (animOptions) {
1941                         animOptions = merge(animOptions);
1942                         if (complete) { // allows using a callback with the global animation without overwriting it
1943                                 animOptions.complete = complete;
1944                         }
1945                         animate(this, params, animOptions);
1946                 } else {
1947                         this.attr(params);
1948                         if (complete) {
1949                                 complete();
1950                         }
1951                 }
1952         },
1953         /**
1954          * Set or get a given attribute
1955          * @param {Object|String} hash
1956          * @param {Mixed|Undefined} val
1957          */
1958         attr: function (hash, val) {
1959                 var wrapper = this,
1960                         key,
1961                         value,
1962                         result,
1963                         i,
1964                         child,
1965                         element = wrapper.element,
1966                         nodeName = element.nodeName,
1967                         renderer = wrapper.renderer,
1968                         skipAttr,
1969                         attrSetters = wrapper.attrSetters,
1970                         shadows = wrapper.shadows,
1971                         htmlNode = wrapper.htmlNode,
1972                         hasSetSymbolSize,
1973                         ret = wrapper;
1974
1975                 // single key-value pair
1976                 if (isString(hash) && defined(val)) {
1977                         key = hash;
1978                         hash = {};
1979                         hash[key] = val;
1980                 }
1981
1982                 // used as a getter: first argument is a string, second is undefined
1983                 if (isString(hash)) {
1984                         key = hash;
1985                         if (nodeName === 'circle') {
1986                                 key = { x: 'cx', y: 'cy' }[key] || key;
1987                         } else if (key === 'strokeWidth') {
1988                                 key = 'stroke-width';
1989                         }
1990                         ret = attr(element, key) || wrapper[key] || 0;
1991
1992                         if (key !== 'd' && key !== 'visibility') { // 'd' is string in animation step
1993                                 ret = parseFloat(ret);
1994                         }
1995
1996                 // setter
1997                 } else {
1998
1999                         for (key in hash) {
2000                                 skipAttr = false; // reset
2001                                 value = hash[key];
2002
2003                                 // check for a specific attribute setter
2004                                 result = attrSetters[key] && attrSetters[key](value, key);
2005
2006                                 if (result !== false) {
2007
2008                                         if (result !== UNDEFINED) {
2009                                                 value = result; // the attribute setter has returned a new value to set
2010                                         }
2011
2012                                         // paths
2013                                         if (key === 'd') {
2014                                                 if (value && value.join) { // join path
2015                                                         value = value.join(' ');
2016                                                 }
2017                                                 if (/(NaN| {2}|^$)/.test(value)) {
2018                                                         value = 'M 0 0';
2019                                                 }
2020                                                 wrapper.d = value; // shortcut for animations
2021
2022                                         // update child tspans x values
2023                                         } else if (key === 'x' && nodeName === 'text') {
2024                                                 for (i = 0; i < element.childNodes.length; i++) {
2025                                                         child = element.childNodes[i];
2026                                                         // if the x values are equal, the tspan represents a linebreak
2027                                                         if (attr(child, 'x') === attr(element, 'x')) {
2028                                                                 //child.setAttribute('x', value);
2029                                                                 attr(child, 'x', value);
2030                                                         }
2031                                                 }
2032
2033                                                 if (wrapper.rotation) {
2034                                                         attr(element, 'transform', 'rotate(' + wrapper.rotation + ' ' + value + ' ' +
2035                                                                 pInt(hash.y || attr(element, 'y')) + ')');
2036                                                 }
2037
2038                                         // apply gradients
2039                                         } else if (key === 'fill') {
2040                                                 value = renderer.color(value, element, key);
2041
2042                                         // circle x and y
2043                                         } else if (nodeName === 'circle' && (key === 'x' || key === 'y')) {
2044                                                 key = { x: 'cx', y: 'cy' }[key] || key;
2045
2046                                         // rectangle border radius
2047                                         } else if (nodeName === 'rect' && key === 'r') {
2048                                                 attr(element, {
2049                                                         rx: value,
2050                                                         ry: value
2051                                                 });
2052                                                 skipAttr = true;
2053
2054                                         // translation and text rotation
2055                                         } else if (key === 'translateX' || key === 'translateY' || key === 'rotation' || key === 'verticalAlign') {
2056                                                 wrapper[key] = value;
2057                                                 wrapper.updateTransform();
2058                                                 skipAttr = true;
2059
2060                                         // apply opacity as subnode (required by legacy WebKit and Batik)
2061                                         } else if (key === 'stroke') {
2062                                                 value = renderer.color(value, element, key);
2063
2064                                         // emulate VML's dashstyle implementation
2065                                         } else if (key === 'dashstyle') {
2066                                                 key = 'stroke-dasharray';
2067                                                 value = value && value.toLowerCase();
2068                                                 if (value === 'solid') {
2069                                                         value = NONE;
2070                                                 } else if (value) {
2071                                                         value = value
2072                                                                 .replace('shortdashdotdot', '3,1,1,1,1,1,')
2073                                                                 .replace('shortdashdot', '3,1,1,1')
2074                                                                 .replace('shortdot', '1,1,')
2075                                                                 .replace('shortdash', '3,1,')
2076                                                                 .replace('longdash', '8,3,')
2077                                                                 .replace(/dot/g, '1,3,')
2078                                                                 .replace('dash', '4,3,')
2079                                                                 .replace(/,$/, '')
2080                                                                 .split(','); // ending comma
2081
2082                                                         i = value.length;
2083                                                         while (i--) {
2084                                                                 value[i] = pInt(value[i]) * hash['stroke-width'];
2085                                                         }
2086                                                         value = value.join(',');
2087                                                 }
2088
2089                                         // special
2090                                         } else if (key === 'isTracker') {
2091                                                 wrapper[key] = value;
2092
2093                                         // IE9/MooTools combo: MooTools returns objects instead of numbers and IE9 Beta 2
2094                                         // is unable to cast them. Test again with final IE9.
2095                                         } else if (key === 'width') {
2096                                                 value = pInt(value);
2097
2098                                         // Text alignment
2099                                         } else if (key === 'align') {
2100                                                 key = 'text-anchor';
2101                                                 value = { left: 'start', center: 'middle', right: 'end' }[value];
2102
2103                                         // Title requires a subnode, #431
2104                                         } else if (key === 'title') {
2105                                                 var title = doc.createElementNS(SVG_NS, 'title');
2106                                                 title.appendChild(doc.createTextNode(value));
2107                                                 element.appendChild(title);
2108                                         }
2109
2110                                         // jQuery animate changes case
2111                                         if (key === 'strokeWidth') {
2112                                                 key = 'stroke-width';
2113                                         }
2114
2115                                         // Chrome/Win < 6 bug (http://code.google.com/p/chromium/issues/detail?id=15461)
2116                                         if (isWebKit && key === 'stroke-width' && value === 0) {
2117                                                 value = 0.000001;
2118                                         }
2119
2120                                         // symbols
2121                                         if (wrapper.symbolName && /^(x|y|r|start|end|innerR|anchorX|anchorY)/.test(key)) {
2122
2123
2124                                                 if (!hasSetSymbolSize) {
2125                                                         wrapper.symbolAttr(hash);
2126                                                         hasSetSymbolSize = true;
2127                                                 }
2128                                                 skipAttr = true;
2129                                         }
2130
2131                                         // let the shadow follow the main element
2132                                         if (shadows && /^(width|height|visibility|x|y|d|transform)$/.test(key)) {
2133                                                 i = shadows.length;
2134                                                 while (i--) {
2135                                                         attr(shadows[i], key, value);
2136                                                 }
2137                                         }
2138
2139                                         // validate heights
2140                                         if ((key === 'width' || key === 'height') && nodeName === 'rect' && value < 0) {
2141                                                 value = 0;
2142                                         }
2143
2144
2145
2146
2147                                         if (key === 'text') {
2148                                                 // only one node allowed
2149                                                 wrapper.textStr = value;
2150                                                 if (wrapper.added) {
2151                                                         renderer.buildText(wrapper);
2152                                                 }
2153                                         } else if (!skipAttr) {
2154                                                 attr(element, key, value);
2155                                         }
2156
2157                                 }
2158
2159                                 // Issue #38
2160                                 if (htmlNode && (key === 'x' || key === 'y' ||
2161                                                 key === 'translateX' || key === 'translateY' || key === 'visibility')) {
2162                                         var bBox,
2163                                                 arr = htmlNode.length ? htmlNode : [this],
2164                                                 length = arr.length,
2165                                                 itemWrapper,
2166                                                 j;
2167
2168                                         for (j = 0; j < length; j++) {
2169                                                 itemWrapper = arr[j];
2170                                                 bBox = itemWrapper.getBBox();
2171                                                 htmlNode = itemWrapper.htmlNode; // reassign to child item
2172                                                 css(htmlNode, extend(wrapper.styles, {
2173                                                         left: (bBox.x + (wrapper.translateX || 0)) + PX,
2174                                                         top: (bBox.y + (wrapper.translateY || 0)) + PX
2175                                                 }));
2176
2177                                                 if (key === 'visibility') {
2178                                                         css(htmlNode, {
2179                                                                 visibility: value
2180                                                         });
2181                                                 }
2182                                         }
2183                                 }
2184
2185                         }
2186
2187                 }
2188                 
2189                 // Workaround for our #732, WebKit's issue https://bugs.webkit.org/show_bug.cgi?id=78385
2190                 // TODO: If the WebKit team fix this bug before the final release of Chrome 18, remove the workaround.
2191                 if (isWebKit && /Chrome\/(18|19)/.test(userAgent)) {
2192                         if (nodeName === 'text' && (hash.x !== UNDEFINED || hash.y !== UNDEFINED)) {
2193                                 var parent = element.parentNode,
2194                                         next = element.nextSibling;
2195                         
2196                                 if (parent) {
2197                                         parent.removeChild(element);
2198                                         if (next) {
2199                                                 parent.insertBefore(element, next);
2200                                         } else {
2201                                                 parent.appendChild(element);
2202                                         }
2203                                 }
2204                         }
2205                 }
2206                 // End of workaround for #732
2207                 
2208                 return ret;
2209         },
2210
2211         /**
2212          * If one of the symbol size affecting parameters are changed,
2213          * check all the others only once for each call to an element's
2214          * .attr() method
2215          * @param {Object} hash
2216          */
2217         symbolAttr: function (hash) {
2218                 var wrapper = this;
2219
2220                 each(['x', 'y', 'r', 'start', 'end', 'width', 'height', 'innerR', 'anchorX', 'anchorY'], function (key) {
2221                         wrapper[key] = pick(hash[key], wrapper[key]);
2222                 });
2223
2224                 wrapper.attr({
2225                         d: wrapper.renderer.symbols[wrapper.symbolName](wrapper.x, wrapper.y, wrapper.width, wrapper.height, wrapper)
2226                 });
2227         },
2228
2229         /**
2230          * Apply a clipping path to this object
2231          * @param {String} id
2232          */
2233         clip: function (clipRect) {
2234                 return this.attr('clip-path', 'url(' + this.renderer.url + '#' + clipRect.id + ')');
2235         },
2236
2237         /**
2238          * Calculate the coordinates needed for drawing a rectangle crisply and return the
2239          * calculated attributes
2240          * @param {Number} strokeWidth
2241          * @param {Number} x
2242          * @param {Number} y
2243          * @param {Number} width
2244          * @param {Number} height
2245          */
2246         crisp: function (strokeWidth, x, y, width, height) {
2247
2248                 var wrapper = this,
2249                         key,
2250                         attribs = {},
2251                         values = {},
2252                         normalizer;
2253
2254                 strokeWidth = strokeWidth || wrapper.strokeWidth || (wrapper.attr && wrapper.attr('stroke-width')) || 0;
2255                 normalizer = mathRound(strokeWidth) % 2 / 2; // mathRound because strokeWidth can sometimes have roundoff errors
2256
2257                 // normalize for crisp edges
2258                 values.x = mathFloor(x || wrapper.x || 0) + normalizer;
2259                 values.y = mathFloor(y || wrapper.y || 0) + normalizer;
2260                 values.width = mathFloor((width || wrapper.width || 0) - 2 * normalizer);
2261                 values.height = mathFloor((height || wrapper.height || 0) - 2 * normalizer);
2262                 values.strokeWidth = strokeWidth;
2263
2264                 for (key in values) {
2265                         if (wrapper[key] !== values[key]) { // only set attribute if changed
2266                                 wrapper[key] = attribs[key] = values[key];
2267                         }
2268                 }
2269
2270                 return attribs;
2271         },
2272
2273         /**
2274          * Set styles for the element
2275          * @param {Object} styles
2276          */
2277         css: function (styles) {
2278                 /*jslint unparam: true*//* allow unused param a in the regexp function below */
2279                 var elemWrapper = this,
2280                         elem = elemWrapper.element,
2281                         textWidth = styles && styles.width && elem.nodeName === 'text',
2282                         n,
2283                         serializedCss = '',
2284                         hyphenate = function (a, b) { return '-' + b.toLowerCase(); };
2285                 /*jslint unparam: false*/
2286
2287                 // convert legacy
2288                 if (styles && styles.color) {
2289                         styles.fill = styles.color;
2290                 }
2291
2292                 // Merge the new styles with the old ones
2293                 styles = extend(
2294                         elemWrapper.styles,
2295                         styles
2296                 );
2297
2298                 // store object
2299                 elemWrapper.styles = styles;
2300
2301                 // serialize and set style attribute
2302                 if (isIE && !hasSVG) { // legacy IE doesn't support setting style attribute
2303                         if (textWidth) {
2304                                 delete styles.width;
2305                         }
2306                         css(elemWrapper.element, styles);
2307                 } else {
2308                         for (n in styles) {
2309                                 serializedCss += n.replace(/([A-Z])/g, hyphenate) + ':' + styles[n] + ';';
2310                         }
2311                         elemWrapper.attr({
2312                                 style: serializedCss
2313                         });
2314                 }
2315
2316
2317                 // re-build text
2318                 if (textWidth && elemWrapper.added) {
2319                         elemWrapper.renderer.buildText(elemWrapper);
2320                 }
2321
2322                 return elemWrapper;
2323         },
2324
2325         /**
2326          * Add an event listener
2327          * @param {String} eventType
2328          * @param {Function} handler
2329          */
2330         on: function (eventType, handler) {
2331                 var fn = handler;
2332                 // touch
2333                 if (hasTouch && eventType === 'click') {
2334                         eventType = 'touchstart';
2335                         fn = function (e) {
2336                                 e.preventDefault();
2337                                 handler();
2338                         };
2339                 }
2340                 // simplest possible event model for internal use
2341                 this.element['on' + eventType] = fn;
2342                 return this;
2343         },
2344
2345
2346         /**
2347          * Move an object and its children by x and y values
2348          * @param {Number} x
2349          * @param {Number} y
2350          */
2351         translate: function (x, y) {
2352                 return this.attr({
2353                         translateX: x,
2354                         translateY: y
2355                 });
2356         },
2357
2358         /**
2359          * Invert a group, rotate and flip
2360          */
2361         invert: function () {
2362                 var wrapper = this;
2363                 wrapper.inverted = true;
2364                 wrapper.updateTransform();
2365                 return wrapper;
2366         },
2367
2368         /**
2369          * Private method to update the transform attribute based on internal
2370          * properties
2371          */
2372         updateTransform: function () {
2373                 var wrapper = this,
2374                         translateX = wrapper.translateX || 0,
2375                         translateY = wrapper.translateY || 0,
2376                         inverted = wrapper.inverted,
2377                         rotation = wrapper.rotation,
2378                         transform = [];
2379
2380                 // flipping affects translate as adjustment for flipping around the group's axis
2381                 if (inverted) {
2382                         translateX += wrapper.attr('width');
2383                         translateY += wrapper.attr('height');
2384                 }
2385
2386                 // apply translate
2387                 if (translateX || translateY) {
2388                         transform.push('translate(' + translateX + ',' + translateY + ')');
2389                 }
2390
2391                 // apply rotation
2392                 if (inverted) {
2393                         transform.push('rotate(90) scale(-1,1)');
2394                 } else if (rotation) { // text rotation
2395                         transform.push('rotate(' + rotation + ' ' + wrapper.x + ' ' + wrapper.y + ')');
2396                 }
2397
2398                 if (transform.length) {
2399                         attr(wrapper.element, 'transform', transform.join(' '));
2400                 }
2401         },
2402         /**
2403          * Bring the element to the front
2404          */
2405         toFront: function () {
2406                 var element = this.element;
2407                 element.parentNode.appendChild(element);
2408                 return this;
2409         },
2410
2411
2412         /**
2413          * Break down alignment options like align, verticalAlign, x and y
2414          * to x and y relative to the chart.
2415          *
2416          * @param {Object} alignOptions
2417          * @param {Boolean} alignByTranslate
2418          * @param {Object} box The box to align to, needs a width and height
2419          *
2420          */
2421         align: function (alignOptions, alignByTranslate, box) {
2422                 var elemWrapper = this;
2423
2424                 if (!alignOptions) { // called on resize
2425                         alignOptions = elemWrapper.alignOptions;
2426                         alignByTranslate = elemWrapper.alignByTranslate;
2427                 } else { // first call on instanciate
2428                         elemWrapper.alignOptions = alignOptions;
2429                         elemWrapper.alignByTranslate = alignByTranslate;
2430                         if (!box) { // boxes other than renderer handle this internally
2431                                 elemWrapper.renderer.alignedObjects.push(elemWrapper);
2432                         }
2433                 }
2434
2435                 box = pick(box, elemWrapper.renderer);
2436
2437                 var align = alignOptions.align,
2438                         vAlign = alignOptions.verticalAlign,
2439                         x = (box.x || 0) + (alignOptions.x || 0), // default: left align
2440                         y = (box.y || 0) + (alignOptions.y || 0), // default: top align
2441                         attribs = {};
2442
2443
2444                 // align
2445                 if (/^(right|center)$/.test(align)) {
2446                         x += (box.width - (alignOptions.width || 0)) /
2447                                         { right: 1, center: 2 }[align];
2448                 }
2449                 attribs[alignByTranslate ? 'translateX' : 'x'] = mathRound(x);
2450
2451
2452                 // vertical align
2453                 if (/^(bottom|middle)$/.test(vAlign)) {
2454                         y += (box.height - (alignOptions.height || 0)) /
2455                                         ({ bottom: 1, middle: 2 }[vAlign] || 1);
2456
2457                 }
2458                 attribs[alignByTranslate ? 'translateY' : 'y'] = mathRound(y);
2459
2460                 // animate only if already placed
2461                 elemWrapper[elemWrapper.placed ? 'animate' : 'attr'](attribs);
2462                 elemWrapper.placed = true;
2463                 elemWrapper.alignAttr = attribs;
2464
2465                 return elemWrapper;
2466         },
2467
2468         /**
2469          * Get the bounding box (width, height, x and y) for the element
2470          */
2471         getBBox: function () {
2472                 var bBox,
2473                         width,
2474                         height,
2475                         rotation = this.rotation,
2476                         rad = rotation * deg2rad;
2477
2478                 try { // fails in Firefox if the container has display: none
2479                         // use extend because IE9 is not allowed to change width and height in case
2480                         // of rotation (below)
2481                         bBox = extend({}, this.element.getBBox());
2482                 } catch (e) {
2483                         bBox = { width: 0, height: 0 };
2484                 }
2485                 width = bBox.width;
2486                 height = bBox.height;
2487
2488                 // adjust for rotated text
2489                 if (rotation) {
2490                         bBox.width = mathAbs(height * mathSin(rad)) + mathAbs(width * mathCos(rad));
2491                         bBox.height = mathAbs(height * mathCos(rad)) + mathAbs(width * mathSin(rad));
2492                 }
2493
2494                 return bBox;
2495         },
2496
2497         /**
2498          * Show the element
2499          */
2500         show: function () {
2501                 return this.attr({ visibility: VISIBLE });
2502         },
2503
2504         /**
2505          * Hide the element
2506          */
2507         hide: function () {
2508                 return this.attr({ visibility: HIDDEN });
2509         },
2510
2511         /**
2512          * Add the element
2513          * @param {Object|Undefined} parent Can be an element, an element wrapper or undefined
2514          *    to append the element to the renderer.box.
2515          */
2516         add: function (parent) {
2517
2518                 var renderer = this.renderer,
2519                         parentWrapper = parent || renderer,
2520                         parentNode = parentWrapper.element || renderer.box,
2521                         childNodes = parentNode.childNodes,
2522                         element = this.element,
2523                         zIndex = attr(element, 'zIndex'),
2524                         otherElement,
2525                         otherZIndex,
2526                         i,
2527                         inserted;
2528
2529                 // mark as inverted
2530                 this.parentInverted = parent && parent.inverted;
2531
2532                 // build formatted text
2533                 if (this.textStr !== undefined) {
2534                         renderer.buildText(this);
2535                 }
2536
2537                 // register html spans in groups
2538                 if (parent && this.htmlNode) {
2539                         if (!parent.htmlNode) {
2540                                 parent.htmlNode = [];
2541                         }
2542                         parent.htmlNode.push(this);
2543                 }
2544
2545                 // mark the container as having z indexed children
2546                 if (zIndex) {
2547                         parentWrapper.handleZ = true;
2548                         zIndex = pInt(zIndex);
2549                 }
2550
2551                 // insert according to this and other elements' zIndex
2552                 if (parentWrapper.handleZ) { // this element or any of its siblings has a z index
2553                         for (i = 0; i < childNodes.length; i++) {
2554                                 otherElement = childNodes[i];
2555                                 otherZIndex = attr(otherElement, 'zIndex');
2556                                 if (otherElement !== element && (
2557                                                 // insert before the first element with a higher zIndex
2558                                                 pInt(otherZIndex) > zIndex ||
2559                                                 // if no zIndex given, insert before the first element with a zIndex
2560                                                 (!defined(zIndex) && defined(otherZIndex))
2561
2562                                                 )) {
2563                                         parentNode.insertBefore(element, otherElement);
2564                                         inserted = true;
2565                                         break;
2566                                 }
2567                         }
2568                 }
2569
2570                 // default: append at the end
2571                 if (!inserted) {
2572                         parentNode.appendChild(element);
2573                 }
2574
2575                 // mark as added
2576                 this.added = true;
2577
2578                 // fire an event for internal hooks
2579                 fireEvent(this, 'add');
2580
2581                 return this;
2582         },
2583
2584         /**
2585          * Removes a child either by removeChild or move to garbageBin.
2586          * Issue 490; in VML removeChild results in Orphaned nodes according to sIEve, discardElement does not.
2587          */
2588         safeRemoveChild: function (element) {
2589                 var parentNode = element.parentNode;
2590                 if (parentNode) {
2591                         parentNode.removeChild(element);
2592                 }
2593         },
2594
2595         /**
2596          * Destroy the element and element wrapper
2597          */
2598         destroy: function () {
2599                 var wrapper = this,
2600                         element = wrapper.element || {},
2601                         shadows = wrapper.shadows,
2602                         box = wrapper.box,
2603                         key,
2604                         i;
2605
2606                 // remove events
2607                 element.onclick = element.onmouseout = element.onmouseover = element.onmousemove = null;
2608                 stop(wrapper); // stop running animations
2609
2610                 if (wrapper.clipPath) {
2611                         wrapper.clipPath = wrapper.clipPath.destroy();
2612                 }
2613
2614                 // Destroy stops in case this is a gradient object
2615                 if (wrapper.stops) {
2616                         for (i = 0; i < wrapper.stops.length; i++) {
2617                                 wrapper.stops[i] = wrapper.stops[i].destroy();
2618                         }
2619                         wrapper.stops = null;
2620                 }
2621
2622                 // remove element
2623                 wrapper.safeRemoveChild(element);
2624
2625                 // destroy shadows
2626                 if (shadows) {
2627                         each(shadows, function (shadow) {
2628                                 wrapper.safeRemoveChild(shadow);
2629                         });
2630                 }
2631
2632                 // destroy label box
2633                 if (box) {
2634                         box.destroy();
2635                 }
2636
2637                 // remove from alignObjects
2638                 erase(wrapper.renderer.alignedObjects, wrapper);
2639
2640                 for (key in wrapper) {
2641                         delete wrapper[key];
2642                 }
2643
2644                 return null;
2645         },
2646
2647         /**
2648          * Empty a group element
2649          */
2650         empty: function () {
2651                 var element = this.element,
2652                         childNodes = element.childNodes,
2653                         i = childNodes.length;
2654
2655                 while (i--) {
2656                         element.removeChild(childNodes[i]);
2657                 }
2658         },
2659
2660         /**
2661          * Add a shadow to the element. Must be done after the element is added to the DOM
2662          * @param {Boolean} apply
2663          */
2664         shadow: function (apply, group) {
2665                 var shadows = [],
2666                         i,
2667                         shadow,
2668                         element = this.element,
2669
2670                         // compensate for inverted plot area
2671                         transform = this.parentInverted ? '(-1,-1)' : '(1,1)';
2672
2673
2674                 if (apply) {
2675                         for (i = 1; i <= 3; i++) {
2676                                 shadow = element.cloneNode(0);
2677                                 attr(shadow, {
2678                                         'isShadow': 'true',
2679                                         'stroke': 'rgb(0, 0, 0)',
2680                                         'stroke-opacity': 0.05 * i,
2681                                         'stroke-width': 7 - 2 * i,
2682                                         'transform': 'translate' + transform,
2683                                         'fill': NONE
2684                                 });
2685
2686                                 if (group) {
2687                                         group.element.appendChild(shadow);
2688                                 } else {
2689                                         element.parentNode.insertBefore(shadow, element);
2690                                 }
2691
2692                                 shadows.push(shadow);
2693                         }
2694
2695                         this.shadows = shadows;
2696                 }
2697                 return this;
2698
2699         }
2700 };
2701
2702
2703 /**
2704  * The default SVG renderer
2705  */
2706 var SVGRenderer = function () {
2707         this.init.apply(this, arguments);
2708 };
2709 SVGRenderer.prototype = {
2710
2711         Element: SVGElement,
2712
2713         /**
2714          * Initialize the SVGRenderer
2715          * @param {Object} container
2716          * @param {Number} width
2717          * @param {Number} height
2718          * @param {Boolean} forExport
2719          */
2720         init: function (container, width, height, forExport) {
2721                 var renderer = this,
2722                         loc = location,
2723                         boxWrapper;
2724
2725                 boxWrapper = renderer.createElement('svg')
2726                         .attr({
2727                                 xmlns: SVG_NS,
2728                                 version: '1.1'
2729                         });
2730                 container.appendChild(boxWrapper.element);
2731
2732                 // object properties
2733                 renderer.box = boxWrapper.element;
2734                 renderer.boxWrapper = boxWrapper;
2735                 renderer.alignedObjects = [];
2736                 renderer.url = isIE ? '' : loc.href.replace(/#.*?$/, '')
2737                         .replace(/\(/g, '\\(').replace(/\)/g, '\\)'); // Page url used for internal references. #24, #672.
2738                 renderer.defs = this.createElement('defs').add();
2739                 renderer.forExport = forExport;
2740                 renderer.gradients = {}; // Object where gradient SvgElements are stored
2741
2742                 renderer.setSize(width, height, false);
2743         },
2744
2745         /**
2746          * Destroys the renderer and its allocated members.
2747          */
2748         destroy: function () {
2749                 var renderer = this,
2750                         rendererDefs = renderer.defs;
2751                 renderer.box = null;
2752                 renderer.boxWrapper = renderer.boxWrapper.destroy();
2753
2754                 // Call destroy on all gradient elements
2755                 destroyObjectProperties(renderer.gradients || {});
2756                 renderer.gradients = null;
2757
2758                 // Defs are null in VMLRenderer
2759                 // Otherwise, destroy them here.
2760                 if (rendererDefs) {
2761                         renderer.defs = rendererDefs.destroy();
2762                 }
2763
2764                 renderer.alignedObjects = null;
2765
2766                 return null;
2767         },
2768
2769         /**
2770          * Create a wrapper for an SVG element
2771          * @param {Object} nodeName
2772          */
2773         createElement: function (nodeName) {
2774                 var wrapper = new this.Element();
2775                 wrapper.init(this, nodeName);
2776                 return wrapper;
2777         },
2778
2779
2780         /**
2781          * Parse a simple HTML string into SVG tspans
2782          *
2783          * @param {Object} textNode The parent text SVG node
2784          */
2785         buildText: function (wrapper) {
2786                 var textNode = wrapper.element,
2787                         lines = pick(wrapper.textStr, '').toString()
2788                                 .replace(/<(b|strong)>/g, '<span style="font-weight:bold">')
2789                                 .replace(/<(i|em)>/g, '<span style="font-style:italic">')
2790                                 .replace(/<a/g, '<span')
2791                                 .replace(/<\/(b|strong|i|em|a)>/g, '</span>')
2792                                 .split(/<br.*?>/g),
2793                         childNodes = textNode.childNodes,
2794                         styleRegex = /style="([^"]+)"/,
2795                         hrefRegex = /href="([^"]+)"/,
2796                         parentX = attr(textNode, 'x'),
2797                         textStyles = wrapper.styles,
2798                         renderAsHtml = textStyles && wrapper.useHTML && !this.forExport,
2799                         htmlNode = wrapper.htmlNode,
2800                         //arr, issue #38 workaround
2801                         width = textStyles && pInt(textStyles.width),
2802                         textLineHeight = textStyles && textStyles.lineHeight,
2803                         lastLine,
2804                         GET_COMPUTED_STYLE = 'getComputedStyle',
2805                         i = childNodes.length;
2806
2807                 // remove old text
2808                 while (i--) {
2809                         textNode.removeChild(childNodes[i]);
2810                 }
2811
2812                 if (width && !wrapper.added) {
2813                         this.box.appendChild(textNode); // attach it to the DOM to read offset width
2814                 }
2815
2816                 // remove empty line at end
2817                 if (lines[lines.length - 1] === '') {
2818                         lines.pop();
2819                 }
2820
2821                 // build the lines
2822                 each(lines, function (line, lineNo) {
2823                         var spans, spanNo = 0, lineHeight;
2824
2825                         line = line.replace(/<span/g, '|||<span').replace(/<\/span>/g, '</span>|||');
2826                         spans = line.split('|||');
2827
2828                         each(spans, function (span) {
2829                                 if (span !== '' || spans.length === 1) {
2830                                         var attributes = {},
2831                                                 tspan = doc.createElementNS(SVG_NS, 'tspan');
2832                                         if (styleRegex.test(span)) {
2833                                                 attr(
2834                                                         tspan,
2835                                                         'style',
2836                                                         span.match(styleRegex)[1].replace(/(;| |^)color([ :])/, '$1fill$2')
2837                                                 );
2838                                         }
2839                                         if (hrefRegex.test(span)) {
2840                                                 attr(tspan, 'onclick', 'location.href=\"' + span.match(hrefRegex)[1] + '\"');
2841                                                 css(tspan, { cursor: 'pointer' });
2842                                         }
2843
2844                                         span = (span.replace(/<(.|\n)*?>/g, '') || ' ')
2845                                                 .replace(/&lt;/g, '<')
2846                                                 .replace(/&gt;/g, '>');
2847
2848                                         // issue #38 workaround.
2849                                         /*if (reverse) {
2850                                                 arr = [];
2851                                                 i = span.length;
2852                                                 while (i--) {
2853                                                         arr.push(span.charAt(i));
2854                                                 }
2855                                                 span = arr.join('');
2856                                         }*/
2857
2858                                         // add the text node
2859                                         tspan.appendChild(doc.createTextNode(span));
2860
2861                                         if (!spanNo) { // first span in a line, align it to the left
2862                                                 attributes.x = parentX;
2863                                         } else {
2864                                                 // Firefox ignores spaces at the front or end of the tspan
2865                                                 attributes.dx = 3; // space
2866                                         }
2867
2868                                         // first span on subsequent line, add the line height
2869                                         if (!spanNo) {
2870                                                 if (lineNo) {
2871
2872                                                         // allow getting the right offset height in exporting in IE
2873                                                         if (!hasSVG && wrapper.renderer.forExport) {
2874                                                                 css(tspan, { display: 'block' });
2875                                                         }
2876
2877                                                         // Webkit and opera sometimes return 'normal' as the line height. In that
2878                                                         // case, webkit uses offsetHeight, while Opera falls back to 18
2879                                                         lineHeight = win[GET_COMPUTED_STYLE] &&
2880                                                                 pInt(win[GET_COMPUTED_STYLE](lastLine, null).getPropertyValue('line-height'));
2881
2882                                                         if (!lineHeight || isNaN(lineHeight)) {
2883                                                                 lineHeight = textLineHeight || lastLine.offsetHeight || 18;
2884                                                         }
2885                                                         attr(tspan, 'dy', lineHeight);
2886                                                 }
2887                                                 lastLine = tspan; // record for use in next line
2888                                         }
2889
2890                                         // add attributes
2891                                         attr(tspan, attributes);
2892
2893                                         // append it
2894                                         textNode.appendChild(tspan);
2895
2896                                         spanNo++;
2897
2898                                         // check width and apply soft breaks
2899                                         if (width) {
2900                                                 var words = span.replace(/-/g, '- ').split(' '),
2901                                                         tooLong,
2902                                                         actualWidth,
2903                                                         rest = [];
2904
2905                                                 while (words.length || rest.length) {
2906                                                         actualWidth = wrapper.getBBox().width;
2907                                                         tooLong = actualWidth > width;
2908                                                         if (!tooLong || words.length === 1) { // new line needed
2909                                                                 words = rest;
2910                                                                 rest = [];
2911                                                                 if (words.length) {
2912                                                                         tspan = doc.createElementNS(SVG_NS, 'tspan');
2913                                                                         attr(tspan, {
2914                                                                                 dy: textLineHeight || 16,
2915                                                                                 x: parentX
2916                                                                         });
2917                                                                         textNode.appendChild(tspan);
2918
2919                                                                         if (actualWidth > width) { // a single word is pressing it out
2920                                                                                 width = actualWidth;
2921                                                                         }
2922                                                                 }
2923                                                         } else { // append to existing line tspan
2924                                                                 tspan.removeChild(tspan.firstChild);
2925                                                                 rest.unshift(words.pop());
2926                                                         }
2927                                                         if (words.length) {
2928                                                                 tspan.appendChild(doc.createTextNode(words.join(' ').replace(/- /g, '-')));
2929                                                         }
2930                                                 }
2931                                         }
2932                                 }
2933                         });
2934                 });
2935
2936                 // Fix issue #38 and allow HTML in tooltips and other labels
2937                 if (renderAsHtml) {
2938                         if (!htmlNode) {
2939                                 htmlNode = wrapper.htmlNode = createElement('span', null, extend(textStyles, {
2940                                         position: ABSOLUTE,
2941                                         top: 0,
2942                                         left: 0
2943                                 }), this.box.parentNode);
2944                         }
2945                         htmlNode.innerHTML = wrapper.textStr;
2946
2947                         i = childNodes.length;
2948                         while (i--) {
2949                                 childNodes[i].style.visibility = HIDDEN;
2950                         }
2951                 }
2952         },
2953
2954         /**
2955          * Create a button with preset states
2956          * @param {String} text
2957          * @param {Number} x
2958          * @param {Number} y
2959          * @param {Function} callback
2960          * @param {Object} normalState
2961          * @param {Object} hoverState
2962          * @param {Object} pressedState
2963          */
2964         button: function (text, x, y, callback, normalState, hoverState, pressedState) {
2965                 var label = this.label(text, x, y),
2966                         curState = 0,
2967                         stateOptions,
2968                         stateStyle,
2969                         normalStyle,
2970                         hoverStyle,
2971                         pressedStyle,
2972                         STYLE = 'style',
2973                         verticalGradient = { x1: 0, y1: 0, x2: 0, y2: 1 };
2974
2975                 // prepare the attributes
2976                 /*jslint white: true*/
2977                 normalState = merge(hash(
2978                         STROKE_WIDTH, 1,
2979                         STROKE, '#999',
2980                         FILL, hash(
2981                                 LINEAR_GRADIENT, verticalGradient,
2982                                 STOPS, [
2983                                         [0, '#FFF'],
2984                                         [1, '#DDD']
2985                                 ]
2986                         ),
2987                         'r', 3,
2988                         'padding', 3,
2989                         STYLE, hash(
2990                                 'color', 'black'
2991                         )
2992                 ), normalState);
2993                 /*jslint white: false*/
2994                 normalStyle = normalState[STYLE];
2995                 delete normalState[STYLE];
2996
2997                 /*jslint white: true*/
2998                 hoverState = merge(normalState, hash(
2999                         STROKE, '#68A',
3000                         FILL, hash(
3001                                 LINEAR_GRADIENT, verticalGradient,
3002                                 STOPS, [
3003                                         [0, '#FFF'],
3004                                         [1, '#ACF']
3005                                 ]
3006                         )
3007                 ), hoverState);
3008                 /*jslint white: false*/
3009                 hoverStyle = hoverState[STYLE];
3010                 delete hoverState[STYLE];
3011
3012                 /*jslint white: true*/
3013                 pressedState = merge(normalState, hash(
3014                         STROKE, '#68A',
3015                         FILL, hash(
3016                                 LINEAR_GRADIENT, verticalGradient,
3017                                 STOPS, [
3018                                         [0, '#9BD'],
3019                                         [1, '#CDF']
3020                                 ]
3021                         )
3022                 ), pressedState);
3023                 /*jslint white: false*/
3024                 pressedStyle = pressedState[STYLE];
3025                 delete pressedState[STYLE];
3026
3027                 // add the events
3028                 addEvent(label.element, 'mouseenter', function () {
3029                         label.attr(hoverState)
3030                                 .css(hoverStyle);
3031                 });
3032                 addEvent(label.element, 'mouseleave', function () {
3033                         stateOptions = [normalState, hoverState, pressedState][curState];
3034                         stateStyle = [normalStyle, hoverStyle, pressedStyle][curState];
3035                         label.attr(stateOptions)
3036                                 .css(stateStyle);
3037                 });
3038
3039                 label.setState = function (state) {
3040                         curState = state;
3041                         if (!state) {
3042                                 label.attr(normalState)
3043                                         .css(normalStyle);
3044                         } else if (state === 2) {
3045                                 label.attr(pressedState)
3046                                         .css(pressedStyle);
3047                         }
3048                 };
3049
3050                 return label
3051                         .on('click', function () {
3052                                 callback.call(label);
3053                         })
3054                         .attr(normalState)
3055                         .css(extend({ cursor: 'default' }, normalStyle));
3056         },
3057
3058         /**
3059          * Make a straight line crisper by not spilling out to neighbour pixels
3060          * @param {Array} points
3061          * @param {Number} width
3062          */
3063         crispLine: function (points, width) {
3064                 // points format: [M, 0, 0, L, 100, 0]
3065                 // normalize to a crisp line
3066                 if (points[1] === points[4]) {
3067                         points[1] = points[4] = mathRound(points[1]) + (width % 2 / 2);
3068                 }
3069                 if (points[2] === points[5]) {
3070                         points[2] = points[5] = mathRound(points[2]) + (width % 2 / 2);
3071                 }
3072                 return points;
3073         },
3074
3075
3076         /**
3077          * Draw a path
3078          * @param {Array} path An SVG path in array form
3079          */
3080         path: function (path) {
3081                 return this.createElement('path').attr({
3082                         d: path,
3083                         fill: NONE
3084                 });
3085         },
3086
3087         /**
3088          * Draw and return an SVG circle
3089          * @param {Number} x The x position
3090          * @param {Number} y The y position
3091          * @param {Number} r The radius
3092          */
3093         circle: function (x, y, r) {
3094                 var attr = isObject(x) ?
3095                         x :
3096                         {
3097                                 x: x,
3098                                 y: y,
3099                                 r: r
3100                         };
3101
3102                 return this.createElement('circle').attr(attr);
3103         },
3104
3105         /**
3106          * Draw and return an arc
3107          * @param {Number} x X position
3108          * @param {Number} y Y position
3109          * @param {Number} r Radius
3110          * @param {Number} innerR Inner radius like used in donut charts
3111          * @param {Number} start Starting angle
3112          * @param {Number} end Ending angle
3113          */
3114         arc: function (x, y, r, innerR, start, end) {
3115                 // arcs are defined as symbols for the ability to set
3116                 // attributes in attr and animate
3117
3118                 if (isObject(x)) {
3119                         y = x.y;
3120                         r = x.r;
3121                         innerR = x.innerR;
3122                         start = x.start;
3123                         end = x.end;
3124                         x = x.x;
3125                 }
3126                 return this.symbol('arc', x || 0, y || 0, r || 0, r || 0, {
3127                         innerR: innerR || 0,
3128                         start: start || 0,
3129                         end: end || 0
3130                 });
3131         },
3132
3133         /**
3134          * Draw and return a rectangle
3135          * @param {Number} x Left position
3136          * @param {Number} y Top position
3137          * @param {Number} width
3138          * @param {Number} height
3139          * @param {Number} r Border corner radius
3140          * @param {Number} strokeWidth A stroke width can be supplied to allow crisp drawing
3141          */
3142         rect: function (x, y, width, height, r, strokeWidth) {
3143                 if (isObject(x)) {
3144                         y = x.y;
3145                         width = x.width;
3146                         height = x.height;
3147                         r = x.r;
3148                         strokeWidth = x.strokeWidth;
3149                         x = x.x;
3150                 }
3151                 var wrapper = this.createElement('rect').attr({
3152                         rx: r,
3153                         ry: r,
3154                         fill: NONE
3155                 });
3156
3157                 return wrapper.attr(wrapper.crisp(strokeWidth, x, y, mathMax(width, 0), mathMax(height, 0)));
3158         },
3159
3160         /**
3161          * Resize the box and re-align all aligned elements
3162          * @param {Object} width
3163          * @param {Object} height
3164          * @param {Boolean} animate
3165          *
3166          */
3167         setSize: function (width, height, animate) {
3168                 var renderer = this,
3169                         alignedObjects = renderer.alignedObjects,
3170                         i = alignedObjects.length;
3171
3172                 renderer.width = width;
3173                 renderer.height = height;
3174
3175                 renderer.boxWrapper[pick(animate, true) ? 'animate' : 'attr']({
3176                         width: width,
3177                         height: height
3178                 });
3179
3180                 while (i--) {
3181                         alignedObjects[i].align();
3182                 }
3183         },
3184
3185         /**
3186          * Create a group
3187          * @param {String} name The group will be given a class name of 'highcharts-{name}'.
3188          *     This can be used for styling and scripting.
3189          */
3190         g: function (name) {
3191                 var elem = this.createElement('g');
3192                 return defined(name) ? elem.attr({ 'class': PREFIX + name }) : elem;
3193         },
3194
3195         /**
3196          * Display an image
3197          * @param {String} src
3198          * @param {Number} x
3199          * @param {Number} y
3200          * @param {Number} width
3201          * @param {Number} height
3202          */
3203         image: function (src, x, y, width, height) {
3204                 var attribs = {
3205                                 preserveAspectRatio: NONE
3206                         },
3207                         elemWrapper;
3208
3209                 // optional properties
3210                 if (arguments.length > 1) {
3211                         extend(attribs, {
3212                                 x: x,
3213                                 y: y,
3214                                 width: width,
3215                                 height: height
3216                         });
3217                 }
3218
3219                 elemWrapper = this.createElement('image').attr(attribs);
3220
3221                 // set the href in the xlink namespace
3222                 if (elemWrapper.element.setAttributeNS) {
3223                         elemWrapper.element.setAttributeNS('http://www.w3.org/1999/xlink',
3224                                 'href', src);
3225                 } else {
3226                         // could be exporting in IE
3227                         // using href throws "not supported" in ie7 and under, requries regex shim to fix later
3228                         elemWrapper.element.setAttribute('hc-svg-href', src);
3229         }
3230
3231                 return elemWrapper;
3232         },
3233
3234         /**
3235          * Draw a symbol out of pre-defined shape paths from the namespace 'symbol' object.
3236          *
3237          * @param {Object} symbol
3238          * @param {Object} x
3239          * @param {Object} y
3240          * @param {Object} radius
3241          * @param {Object} options
3242          */
3243         symbol: function (symbol, x, y, width, height, options) {
3244
3245                 var obj,
3246
3247                         // get the symbol definition function
3248                         symbolFn = this.symbols[symbol],
3249
3250                         // check if there's a path defined for this symbol
3251                         path = symbolFn && symbolFn(
3252                                 mathRound(x),
3253                                 mathRound(y),
3254                                 width,
3255                                 height,
3256                                 options
3257                         ),
3258
3259                         imageRegex = /^url\((.*?)\)$/,
3260                         imageSrc,
3261                         imageSize;
3262
3263                 if (path) {
3264
3265                         obj = this.path(path);
3266                         // expando properties for use in animate and attr
3267                         extend(obj, {
3268                                 symbolName: symbol,
3269                                 x: x,
3270                                 y: y,
3271                                 width: width,
3272                                 height: height
3273                         });
3274                         if (options) {
3275                                 extend(obj, options);
3276                         }
3277
3278
3279                 // image symbols
3280                 } else if (imageRegex.test(symbol)) {
3281
3282                         var centerImage = function (img, size) {
3283                                 img.attr({
3284                                         width: size[0],
3285                                         height: size[1]
3286                                 }).translate(
3287                                         -mathRound(size[0] / 2),
3288                                         -mathRound(size[1] / 2)
3289                                 );
3290                         };
3291
3292                         imageSrc = symbol.match(imageRegex)[1];
3293                         imageSize = symbolSizes[imageSrc];
3294
3295                         // create the image synchronously, add attribs async
3296                         obj = this.image(imageSrc)
3297                                 .attr({
3298                                         x: x,
3299                                         y: y
3300                                 });
3301
3302                         if (imageSize) {
3303                                 centerImage(obj, imageSize);
3304                         } else {
3305                                 // initialize image to be 0 size so export will still function if there's no cached sizes
3306                                 obj.attr({ width: 0, height: 0 });
3307
3308                                 // create a dummy JavaScript image to get the width and height
3309                                 createElement('img', {
3310                                         onload: function () {
3311                                                 var img = this;
3312
3313                                                 centerImage(obj, symbolSizes[imageSrc] = [img.width, img.height]);
3314                                         },
3315                                         src: imageSrc
3316                                 });
3317                         }
3318                 }
3319
3320                 return obj;
3321         },
3322
3323         /**
3324          * An extendable collection of functions for defining symbol paths.
3325          */
3326         symbols: {
3327                 'circle': function (x, y, w, h) {
3328                         var cpw = 0.166 * w;
3329                         return [
3330                                 M, x + w / 2, y,
3331                                 'C', x + w + cpw, y, x + w + cpw, y + h, x + w / 2, y + h,
3332                                 'C', x - cpw, y + h, x - cpw, y, x + w / 2, y,
3333                                 'Z'
3334                         ];
3335                 },
3336
3337                 'square': function (x, y, w, h) {
3338                         return [
3339                                 M, x, y,
3340                                 L, x + w, y,
3341                                 x + w, y + h,
3342                                 x, y + h,
3343                                 'Z'
3344                         ];
3345                 },
3346
3347                 'triangle': function (x, y, w, h) {
3348                         return [
3349                                 M, x + w / 2, y,
3350                                 L, x + w, y + h,
3351                                 x, y + h,
3352                                 'Z'
3353                         ];
3354                 },
3355
3356                 'triangle-down': function (x, y, w, h) {
3357                         return [
3358                                 M, x, y,
3359                                 L, x + w, y,
3360                                 x + w / 2, y + h,
3361                                 'Z'
3362                         ];
3363                 },
3364                 'diamond': function (x, y, w, h) {
3365                         return [
3366                                 M, x + w / 2, y,
3367                                 L, x + w, y + h / 2,
3368                                 x + w / 2, y + h,
3369                                 x, y + h / 2,
3370                                 'Z'
3371                         ];
3372                 },
3373                 'arc': function (x, y, w, h, options) {
3374                         var start = options.start,
3375                                 radius = options.r || w || h,
3376                                 end = options.end - 0.000001, // to prevent cos and sin of start and end from becoming equal on 360 arcs
3377                                 innerRadius = options.innerR,
3378                                 cosStart = mathCos(start),
3379                                 sinStart = mathSin(start),
3380                                 cosEnd = mathCos(end),
3381                                 sinEnd = mathSin(end),
3382                                 longArc = options.end - start < mathPI ? 0 : 1;
3383
3384                         return [
3385                                 M,
3386                                 x + radius * cosStart,
3387                                 y + radius * sinStart,
3388                                 'A', // arcTo
3389                                 radius, // x radius
3390                                 radius, // y radius
3391                                 0, // slanting
3392                                 longArc, // long or short arc
3393                                 1, // clockwise
3394                                 x + radius * cosEnd,
3395                                 y + radius * sinEnd,
3396                                 L,
3397                                 x + innerRadius * cosEnd,
3398                                 y + innerRadius * sinEnd,
3399                                 'A', // arcTo
3400                                 innerRadius, // x radius
3401                                 innerRadius, // y radius
3402                                 0, // slanting
3403                                 longArc, // long or short arc
3404                                 0, // clockwise
3405                                 x + innerRadius * cosStart,
3406                                 y + innerRadius * sinStart,
3407
3408                                 'Z' // close
3409                         ];
3410                 }
3411         },
3412
3413         /**
3414          * Define a clipping rectangle
3415          * @param {String} id
3416          * @param {Number} x
3417          * @param {Number} y
3418          * @param {Number} width
3419          * @param {Number} height
3420          */
3421         clipRect: function (x, y, width, height) {
3422                 var wrapper,
3423                         id = PREFIX + idCounter++,
3424
3425                         clipPath = this.createElement('clipPath').attr({
3426                                 id: id
3427                         }).add(this.defs);
3428
3429                 wrapper = this.rect(x, y, width, height, 0).add(clipPath);
3430                 wrapper.id = id;
3431                 wrapper.clipPath = clipPath;
3432
3433                 return wrapper;
3434         },
3435
3436
3437         /**
3438          * Take a color and return it if it's a string, make it a gradient if it's a
3439          * gradient configuration object. Prior to Highstock, an array was used to define
3440          * a linear gradient with pixel positions relative to the SVG. In newer versions
3441          * we change the coordinates to apply relative to the shape, using coordinates
3442          * 0-1 within the shape. To preserve backwards compatibility, linearGradient
3443          * in this definition is an object of x1, y1, x2 and y2.
3444          *
3445          * @param {Object} color The color or config object
3446          */
3447         color: function (color, elem, prop) {
3448                 var colorObject,
3449                         regexRgba = /^rgba/;
3450                 if (color && color.linearGradient) {
3451                         var renderer = this,
3452                                 linearGradient = color[LINEAR_GRADIENT],
3453                                 relativeToShape = !isArray(linearGradient), // keep backwards compatibility
3454                                 id,
3455                                 gradients = renderer.gradients,
3456                                 gradientObject,
3457                                 x1 = linearGradient.x1 || linearGradient[0] || 0,
3458                                 y1 = linearGradient.y1 || linearGradient[1] || 0,
3459                                 x2 = linearGradient.x2 || linearGradient[2] || 0,
3460                                 y2 = linearGradient.y2 || linearGradient[3] || 0,
3461                                 stopColor,
3462                                 stopOpacity,
3463                                 // Create a unique key in order to reuse gradient objects. #671.
3464                                 key = [relativeToShape, x1, y1, x2, y2, color.stops.join(',')].join(',');
3465                         
3466                         // If the gradient with the same setup is already created, reuse it
3467                         if (gradients[key]) {
3468                                 id = attr(gradients[key].element, 'id');
3469                         
3470                         // If not, create a new one and keep the reference.     
3471                         } else {
3472                                 id = PREFIX + idCounter++;
3473                                 gradientObject = renderer.createElement(LINEAR_GRADIENT)
3474                                         .attr(extend({
3475                                                 id: id,
3476                                                 x1: x1,
3477                                                 y1: y1,
3478                                                 x2: x2,
3479                                                 y2: y2
3480                                         }, relativeToShape ? null : { gradientUnits: 'userSpaceOnUse' }))
3481                                         .add(renderer.defs);
3482         
3483                                 // The gradient needs to keep a list of stops to be able to destroy them
3484                                 gradientObject.stops = [];
3485                                 each(color.stops, function (stop) {
3486                                         var stopObject;
3487                                         if (regexRgba.test(stop[1])) {
3488                                                 colorObject = Color(stop[1]);
3489                                                 stopColor = colorObject.get('rgb');
3490                                                 stopOpacity = colorObject.get('a');
3491                                         } else {
3492                                                 stopColor = stop[1];
3493                                                 stopOpacity = 1;
3494                                         }
3495                                         stopObject = renderer.createElement('stop').attr({
3496                                                 offset: stop[0],
3497                                                 'stop-color': stopColor,
3498                                                 'stop-opacity': stopOpacity
3499                                         }).add(gradientObject);
3500         
3501                                         // Add the stop element to the gradient
3502                                         gradientObject.stops.push(stopObject);
3503                                 });
3504                                 
3505                                 // Keep a reference to the gradient object so it is possible to reuse it and
3506                                 // destroy it later
3507                                 gradients[key] = gradientObject;
3508                         }
3509
3510                         return 'url(' + this.url + '#' + id + ')';
3511
3512                 // Webkit and Batik can't show rgba.
3513                 } else if (regexRgba.test(color)) {
3514                         colorObject = Color(color);
3515                         attr(elem, prop + '-opacity', colorObject.get('a'));
3516
3517                         return colorObject.get('rgb');
3518
3519
3520                 } else {
3521                         // Remove the opacity attribute added above. Does not throw if the attribute is not there.
3522                         elem.removeAttribute(prop + '-opacity');
3523
3524                         return color;
3525                 }
3526
3527         },
3528
3529
3530         /**
3531          * Add text to the SVG object
3532          * @param {String} str
3533          * @param {Number} x Left position
3534          * @param {Number} y Top position
3535          * @param {Boolean} useHTML Use HTML to render the text
3536          */
3537         text: function (str, x, y, useHTML) {
3538
3539                 // declare variables
3540                 var renderer = this,
3541                         defaultChartStyle = defaultOptions.chart.style,
3542                         wrapper;
3543
3544                 x = mathRound(pick(x, 0));
3545                 y = mathRound(pick(y, 0));
3546
3547                 wrapper = renderer.createElement('text')
3548                         .attr({
3549                                 x: x,
3550                                 y: y,
3551                                 text: str
3552                         })
3553                         .css({
3554                                 fontFamily: defaultChartStyle.fontFamily,
3555                                 fontSize: defaultChartStyle.fontSize
3556                         });
3557
3558                 wrapper.x = x;
3559                 wrapper.y = y;
3560                 wrapper.useHTML = useHTML;
3561                 return wrapper;
3562         },
3563
3564         /**
3565          * Add a label, a text item that can hold a colored or gradient background
3566          * as well as a border and shadow.
3567          * @param {string} str
3568          * @param {Number} x
3569          * @param {Number} y
3570          * @param {String} shape
3571          * @param {Number} anchorX In case the shape has a pointer, like a flag, this is the
3572          *    coordinates it should be pinned to
3573          * @param {Number} anchorY
3574          */
3575         label: function (str, x, y, shape, anchorX, anchorY) {
3576
3577                 var renderer = this,
3578                         wrapper = renderer.g(),
3579                         text = renderer.text()
3580                                 .attr({
3581                                         zIndex: 1
3582                                 })
3583                                 .add(wrapper),
3584                         box,
3585                         bBox,
3586                         align = 'left',
3587                         padding = 3,
3588                         width,
3589                         height,
3590                         wrapperX,
3591                         wrapperY,
3592                         crispAdjust = 0,
3593                         deferredAttr = {},
3594                         attrSetters = wrapper.attrSetters;
3595
3596                 /**
3597                  * This function runs after the label is added to the DOM (when the bounding box is
3598                  * available), and after the text of the label is updated to detect the new bounding
3599                  * box and reflect it in the border box.
3600                  */
3601                 function updateBoxSize() {
3602                         bBox = (width === undefined || height === undefined || wrapper.styles.textAlign) &&
3603                                 text.getBBox(true);
3604                         wrapper.width = (width || bBox.width) + 2 * padding;
3605                         wrapper.height = (height || bBox.height) + 2 * padding;
3606
3607                         // create the border box if it is not already present
3608                         if (!box) {
3609                                 wrapper.box = box = shape ?
3610                                         renderer.symbol(shape, 0, 0, wrapper.width, wrapper.height) :
3611                                         renderer.rect(0, 0, wrapper.width, wrapper.height, 0, deferredAttr[STROKE_WIDTH]);
3612                                 box.add(wrapper);
3613                         }
3614
3615                         // apply the box attributes
3616                         box.attr(merge({
3617                                 width: wrapper.width,
3618                                 height: wrapper.height
3619                         }, deferredAttr));
3620                         deferredAttr = null;
3621                 }
3622
3623                 /**
3624                  * This function runs after setting text or padding, but only if padding is changed
3625                  */
3626                 function updateTextPadding() {
3627                         var styles = wrapper.styles,
3628                                 textAlign = styles && styles.textAlign,
3629                                 x = padding,
3630                                 y = padding + mathRound(pInt(wrapper.element.style.fontSize || 11) * 1.2);
3631
3632                         // compensate for alignment
3633                         if (defined(width) && (textAlign === 'center' || textAlign === 'right')) {
3634                                 x += { center: 0.5, right: 1 }[textAlign] * (width - bBox.width);
3635                         }
3636
3637                         // update if anything changed
3638                         if (x !== text.x || y !== text.y) {
3639                                 text.attr({
3640                                         x: x,
3641                                         y: y
3642                                 });
3643                         }
3644
3645                         // record current values
3646                         text.x = x;
3647                         text.y = y;
3648                 }
3649
3650                 /**
3651                  * Set a box attribute, or defer it if the box is not yet created
3652                  * @param {Object} key
3653                  * @param {Object} value
3654                  */
3655                 function boxAttr(key, value) {
3656                         if (box) {
3657                                 box.attr(key, value);
3658                         } else {
3659                                 deferredAttr[key] = value;
3660                         }
3661                 }
3662
3663                 function getSizeAfterAdd() {
3664                         wrapper.attr({
3665                                 text: str, // alignment is available now
3666                                 x: x,
3667                                 y: y,
3668                                 anchorX: anchorX,
3669                                 anchorY: anchorY
3670                         });
3671                 }
3672
3673                 /**
3674                  * After the text element is added, get the desired size of the border box
3675                  * and add it before the text in the DOM.
3676                  */
3677                 addEvent(wrapper, 'add', getSizeAfterAdd);
3678
3679                 /*
3680                  * Add specific attribute setters.
3681                  */
3682
3683                 // only change local variables
3684                 attrSetters.width = function (value) {
3685                         width = value;
3686                         return false;
3687                 };
3688                 attrSetters.height = function (value) {
3689                         height = value;
3690                         return false;
3691                 };
3692                 attrSetters.padding = function (value) {
3693                         padding = value;
3694                         updateTextPadding();
3695
3696                         return false;
3697                 };
3698
3699                 // change local variable and set attribue as well
3700                 attrSetters.align = function (value) {
3701                         align = value;
3702                         return false; // prevent setting text-anchor on the group
3703                 };
3704
3705                 // apply these to the box and the text alike
3706                 attrSetters.text = function (value, key) {
3707                         text.attr(key, value);
3708                         updateBoxSize();
3709                         updateTextPadding();
3710                         return false;
3711                 };
3712
3713                 // apply these to the box but not to the text
3714                 attrSetters[STROKE_WIDTH] = function (value, key) {
3715                         crispAdjust = value % 2 / 2;
3716                         boxAttr(key, value);
3717                         return false;
3718                 };
3719                 attrSetters.stroke = attrSetters.fill = attrSetters.r = function (value, key) {
3720                         boxAttr(key, value);
3721                         return false;
3722                 };
3723                 attrSetters.anchorX = function (value, key) {
3724                         anchorX = value;
3725                         boxAttr(key, value + crispAdjust - wrapperX);
3726                         return false;
3727                 };
3728                 attrSetters.anchorY = function (value, key) {
3729                         anchorY = value;
3730                         boxAttr(key, value - wrapperY);
3731                         return false;
3732                 };
3733
3734                 // rename attributes
3735                 attrSetters.x = function (value) {
3736                         wrapperX = value;
3737                         wrapperX -= { left: 0, center: 0.5, right: 1 }[align] * ((width || bBox.width) + padding);
3738
3739                         wrapper.attr('translateX', mathRound(wrapperX));
3740                         return false;
3741                 };
3742                 attrSetters.y = function (value) {
3743                         wrapperY = value;
3744                         wrapper.attr('translateY', mathRound(value));
3745                         return false;
3746                 };
3747
3748                 // Redirect certain methods to either the box or the text
3749                 var baseCss = wrapper.css;
3750                 return extend(wrapper, {
3751                         /**
3752                          * Pick up some properties and apply them to the text instead of the wrapper
3753                          */
3754                         css: function (styles) {
3755                                 if (styles) {
3756                                         var textStyles = {};
3757                                         styles = merge({}, styles); // create a copy to avoid altering the original object (#537)
3758                                         each(['fontSize', 'fontWeight', 'fontFamily', 'color', 'lineHeight', 'width'], function (prop) {
3759                                                 if (styles[prop] !== UNDEFINED) {
3760                                                         textStyles[prop] = styles[prop];
3761                                                         delete styles[prop];
3762                                                 }
3763                                         });
3764                                         text.css(textStyles);
3765                                 }
3766                                 return baseCss.call(wrapper, styles);
3767                         },
3768                         /**
3769                          * Return the bounding box of the box, not the group
3770                          */
3771                         getBBox: function () {
3772                                 return box.getBBox();
3773                         },
3774                         /**
3775                          * Apply the shadow to the box
3776                          */
3777                         shadow: function (b) {
3778                                 box.shadow(b);
3779                                 return wrapper;
3780                         },
3781                         /**
3782                          * Destroy and release memory.
3783                          */
3784                         destroy: function () {
3785                                 removeEvent(wrapper, 'add', getSizeAfterAdd);
3786
3787                                 // Added by button implementation
3788                                 removeEvent(wrapper.element, 'mouseenter');
3789                                 removeEvent(wrapper.element, 'mouseleave');
3790
3791                                 if (text) {
3792                                         // Destroy the text element
3793                                         text = text.destroy();
3794                                 }
3795                                 // Call base implementation to destroy the rest
3796                                 SVGElement.prototype.destroy.call(wrapper);
3797                         }
3798                 });
3799         }
3800 }; // end SVGRenderer
3801
3802
3803 // general renderer
3804 Renderer = SVGRenderer;
3805
3806
3807 /* ****************************************************************************
3808  *                                                                            *
3809  * START OF INTERNET EXPLORER <= 8 SPECIFIC CODE                              *
3810  *                                                                            *
3811  * For applications and websites that don't need IE support, like platform    *
3812  * targeted mobile apps and web apps, this code can be removed.               *
3813  *                                                                            *
3814  *****************************************************************************/
3815
3816 /**
3817  * @constructor
3818  */
3819 var VMLRenderer;
3820 if (!hasSVG) {
3821
3822 /**
3823  * The VML element wrapper.
3824  */
3825 var VMLElement = extendClass(SVGElement, {
3826
3827         /**
3828          * Initialize a new VML element wrapper. It builds the markup as a string
3829          * to minimize DOM traffic.
3830          * @param {Object} renderer
3831          * @param {Object} nodeName
3832          */
3833         init: function (renderer, nodeName) {
3834                 var wrapper = this,
3835                         markup =  ['<', nodeName, ' filled="f" stroked="f"'],
3836                         style = ['position: ', ABSOLUTE, ';'];
3837
3838                 // divs and shapes need size
3839                 if (nodeName === 'shape' || nodeName === DIV) {
3840                         style.push('left:0;top:0;width:10px;height:10px;');
3841                 }
3842                 if (docMode8) {
3843                         style.push('visibility: ', nodeName === DIV ? HIDDEN : VISIBLE);
3844                 }
3845
3846                 markup.push(' style="', style.join(''), '"/>');
3847
3848                 // create element with default attributes and style
3849                 if (nodeName) {
3850                         markup = nodeName === DIV || nodeName === 'span' || nodeName === 'img' ?
3851                                 markup.join('')
3852                                 : renderer.prepVML(markup);
3853                         wrapper.element = createElement(markup);
3854                 }
3855
3856                 wrapper.renderer = renderer;
3857                 wrapper.attrSetters = {};
3858         },
3859
3860         /**
3861          * Add the node to the given parent
3862          * @param {Object} parent
3863          */
3864         add: function (parent) {
3865                 var wrapper = this,
3866                         renderer = wrapper.renderer,
3867                         element = wrapper.element,
3868                         box = renderer.box,
3869                         inverted = parent && parent.inverted,
3870
3871                         // get the parent node
3872                         parentNode = parent ?
3873                                 parent.element || parent :
3874                                 box;
3875
3876
3877                 // if the parent group is inverted, apply inversion on all children
3878                 if (inverted) { // only on groups
3879                         renderer.invertChild(element, parentNode);
3880                 }
3881
3882                 // issue #140 workaround - related to #61 and #74
3883                 if (docMode8 && parentNode.gVis === HIDDEN) {
3884                         css(element, { visibility: HIDDEN });
3885                 }
3886
3887                 // append it
3888                 parentNode.appendChild(element);
3889
3890                 // align text after adding to be able to read offset
3891                 wrapper.added = true;
3892                 if (wrapper.alignOnAdd && !wrapper.deferUpdateTransform) {
3893                         wrapper.updateTransform();
3894                 }
3895
3896                 // fire an event for internal hooks
3897                 fireEvent(wrapper, 'add');
3898
3899                 return wrapper;
3900         },
3901
3902         /**
3903          * In IE8 documentMode 8, we need to recursively set the visibility down in the DOM
3904          * tree for nested groups. Related to #61, #586.
3905          */
3906         toggleChildren: function (element, visibility) {
3907                 var childNodes = element.childNodes,
3908                         i = childNodes.length;
3909                         
3910                 while (i--) {
3911                         
3912                         // apply the visibility
3913                         css(childNodes[i], { visibility: visibility });
3914                         
3915                         // we have a nested group, apply it to its children again
3916                         if (childNodes[i].nodeName === 'DIV') {
3917                                 this.toggleChildren(childNodes[i], visibility);
3918                         }
3919                 }
3920         },
3921
3922         /**
3923          * Get or set attributes
3924          */
3925         attr: function (hash, val) {
3926                 var wrapper = this,
3927                         key,
3928                         value,
3929                         i,
3930                         result,
3931                         element = wrapper.element || {},
3932                         elemStyle = element.style,
3933                         nodeName = element.nodeName,
3934                         renderer = wrapper.renderer,
3935                         symbolName = wrapper.symbolName,
3936                         hasSetSymbolSize,
3937                         shadows = wrapper.shadows,
3938                         skipAttr,
3939                         attrSetters = wrapper.attrSetters,
3940                         ret = wrapper;
3941
3942                 // single key-value pair
3943                 if (isString(hash) && defined(val)) {
3944                         key = hash;
3945                         hash = {};
3946                         hash[key] = val;
3947                 }
3948
3949                 // used as a getter, val is undefined
3950                 if (isString(hash)) {
3951                         key = hash;
3952                         if (key === 'strokeWidth' || key === 'stroke-width') {
3953                                 ret = wrapper.strokeweight;
3954                         } else {
3955                                 ret = wrapper[key];
3956                         }
3957
3958                 // setter
3959                 } else {
3960                         for (key in hash) {
3961                                 value = hash[key];
3962                                 skipAttr = false;
3963
3964                                 // check for a specific attribute setter
3965                                 result = attrSetters[key] && attrSetters[key](value, key);
3966
3967                                 if (result !== false && value !== null) { // #620
3968
3969                                         if (result !== UNDEFINED) {
3970                                                 value = result; // the attribute setter has returned a new value to set
3971                                         }
3972
3973
3974                                         // prepare paths
3975                                         // symbols
3976                                         if (symbolName && /^(x|y|r|start|end|width|height|innerR|anchorX|anchorY)/.test(key)) {
3977                                                 // if one of the symbol size affecting parameters are changed,
3978                                                 // check all the others only once for each call to an element's
3979                                                 // .attr() method
3980                                                 if (!hasSetSymbolSize) {
3981
3982                                                         wrapper.symbolAttr(hash);
3983
3984                                                         hasSetSymbolSize = true;
3985                                                 }
3986                                                 skipAttr = true;
3987
3988                                         } else if (key === 'd') {
3989                                                 value = value || [];
3990                                                 wrapper.d = value.join(' '); // used in getter for animation
3991
3992                                                 // convert paths
3993                                                 i = value.length;
3994                                                 var convertedPath = [];
3995                                                 while (i--) {
3996
3997                                                         // Multiply by 10 to allow subpixel precision.
3998                                                         // Substracting half a pixel seems to make the coordinates
3999                                                         // align with SVG, but this hasn't been tested thoroughly
4000                                                         if (isNumber(value[i])) {
4001                                                                 convertedPath[i] = mathRound(value[i] * 10) - 5;
4002                                                         } else if (value[i] === 'Z') { // close the path
4003                                                                 convertedPath[i] = 'x';
4004                                                         } else {
4005                                                                 convertedPath[i] = value[i];
4006                                                         }
4007
4008                                                 }
4009                                                 value = convertedPath.join(' ') || 'x';
4010                                                 element.path = value;
4011
4012                                                 // update shadows
4013                                                 if (shadows) {
4014                                                         i = shadows.length;
4015                                                         while (i--) {
4016                                                                 shadows[i].path = value;
4017                                                         }
4018                                                 }
4019                                                 skipAttr = true;
4020
4021                                         // directly mapped to css
4022                                         } else if (key === 'zIndex' || key === 'visibility') {
4023
4024                                                 // workaround for #61 and #586
4025                                                 if (docMode8 && key === 'visibility' && nodeName === 'DIV') {
4026                                                         element.gVis = value;
4027                                                         wrapper.toggleChildren(element, value);
4028                                                         if (value === VISIBLE) { // #74
4029                                                                 value = null;
4030                                                         }
4031                                                 }
4032
4033                                                 if (value) {
4034                                                         elemStyle[key] = value;
4035                                                 }
4036
4037
4038
4039                                                 skipAttr = true;
4040
4041                                         // width and height
4042                                         } else if (key === 'width' || key === 'height') {
4043                                                 
4044                                                 value = mathMax(0, value); // don't set width or height below zero (#311)
4045                                                 
4046                                                 this[key] = value; // used in getter
4047
4048                                                 // clipping rectangle special
4049                                                 if (wrapper.updateClipping) {
4050                                                         wrapper[key] = value;
4051                                                         wrapper.updateClipping();
4052                                                 } else {
4053                                                         // normal
4054                                                         elemStyle[key] = value;
4055                                                 }
4056
4057                                                 skipAttr = true;
4058
4059                                         // x and y
4060                                         } else if (/^(x|y)$/.test(key)) {
4061
4062                                                 wrapper[key] = value; // used in getter
4063
4064                                                 if (element.tagName === 'SPAN') {
4065                                                         wrapper.updateTransform();
4066
4067                                                 } else {
4068                                                         elemStyle[{ x: 'left', y: 'top' }[key]] = value;
4069                                                 }
4070
4071                                         // class name
4072                                         } else if (key === 'class') {
4073                                                 // IE8 Standards mode has problems retrieving the className
4074                                                 element.className = value;
4075
4076                                         // stroke
4077                                         } else if (key === 'stroke') {
4078
4079                                                 value = renderer.color(value, element, key);
4080
4081                                                 key = 'strokecolor';
4082
4083                                         // stroke width
4084                                         } else if (key === 'stroke-width' || key === 'strokeWidth') {
4085                                                 element.stroked = value ? true : false;
4086                                                 key = 'strokeweight';
4087                                                 wrapper[key] = value; // used in getter, issue #113
4088                                                 if (isNumber(value)) {
4089                                                         value += PX;
4090                                                 }
4091
4092                                         // dashStyle
4093                                         } else if (key === 'dashstyle') {
4094                                                 var strokeElem = element.getElementsByTagName('stroke')[0] ||
4095                                                         createElement(renderer.prepVML(['<stroke/>']), null, null, element);
4096                                                 strokeElem[key] = value || 'solid';
4097                                                 wrapper.dashstyle = value; /* because changing stroke-width will change the dash length
4098                                                         and cause an epileptic effect */
4099                                                 skipAttr = true;
4100
4101                                         // fill
4102                                         } else if (key === 'fill') {
4103
4104                                                 if (nodeName === 'SPAN') { // text color
4105                                                         elemStyle.color = value;
4106                                                 } else {
4107                                                         element.filled = value !== NONE ? true : false;
4108
4109                                                         value = renderer.color(value, element, key);
4110
4111                                                         key = 'fillcolor';
4112                                                 }
4113
4114                                         // translation for animation
4115                                         } else if (key === 'translateX' || key === 'translateY' || key === 'rotation' || key === 'align') {
4116                                                 if (key === 'align') {
4117                                                         key = 'textAlign';
4118                                                 }
4119                                                 wrapper[key] = value;
4120                                                 wrapper.updateTransform();
4121
4122                                                 skipAttr = true;
4123
4124                                         // text for rotated and non-rotated elements
4125                                         } else if (key === 'text') {
4126                                                 this.bBox = null;
4127                                                 element.innerHTML = value;
4128                                                 skipAttr = true;
4129                                         }
4130
4131                                         // let the shadow follow the main element
4132                                         if (shadows && key === 'visibility') {
4133                                                 i = shadows.length;
4134                                                 while (i--) {
4135                                                         shadows[i].style[key] = value;
4136                                                 }
4137                                         }
4138
4139
4140
4141                                         if (!skipAttr) {
4142                                                 if (docMode8) { // IE8 setAttribute bug
4143                                                         element[key] = value;
4144                                                 } else {
4145                                                         attr(element, key, value);
4146                                                 }
4147                                         }
4148
4149                                 }
4150                         }
4151                 }
4152                 return ret;
4153         },
4154
4155         /**
4156          * Set the element's clipping to a predefined rectangle
4157          *
4158          * @param {String} id The id of the clip rectangle
4159          */
4160         clip: function (clipRect) {
4161                 var wrapper = this,
4162                         clipMembers = clipRect.members;
4163
4164                 clipMembers.push(wrapper);
4165                 wrapper.destroyClip = function () {
4166                         erase(clipMembers, wrapper);
4167                 };
4168                 return wrapper.css(clipRect.getCSS(wrapper.inverted));
4169         },
4170
4171         /**
4172          * Set styles for the element
4173          * @param {Object} styles
4174          */
4175         css: function (styles) {
4176                 var wrapper = this,
4177                         element = wrapper.element,
4178                         textWidth = styles && element.tagName === 'SPAN' && styles.width;
4179
4180                 if (textWidth) {
4181                         delete styles.width;
4182                         wrapper.textWidth = textWidth;
4183                         wrapper.updateTransform();
4184                 }
4185
4186                 wrapper.styles = extend(wrapper.styles, styles);
4187                 css(wrapper.element, styles);
4188
4189                 return wrapper;
4190         },
4191
4192         /**
4193          * Removes a child either by removeChild or move to garbageBin.
4194          * Issue 490; in VML removeChild results in Orphaned nodes according to sIEve, discardElement does not.
4195          */
4196         safeRemoveChild: function (element) {
4197                 // discardElement will detach the node from its parent before attaching it
4198                 // to the garbage bin. Therefore it is important that the node is attached and have parent.
4199                 var parentNode = element.parentNode;
4200                 if (parentNode) {
4201                         discardElement(element);
4202                 }
4203         },
4204
4205         /**
4206          * Extend element.destroy by removing it from the clip members array
4207          */
4208         destroy: function () {
4209                 var wrapper = this;
4210
4211                 if (wrapper.destroyClip) {
4212                         wrapper.destroyClip();
4213                 }
4214
4215                 return SVGElement.prototype.destroy.apply(wrapper);
4216         },
4217
4218         /**
4219          * Remove all child nodes of a group, except the v:group element
4220          */
4221         empty: function () {
4222                 var element = this.element,
4223                         childNodes = element.childNodes,
4224                         i = childNodes.length,
4225                         node;
4226
4227                 while (i--) {
4228                         node = childNodes[i];
4229                         node.parentNode.removeChild(node);
4230                 }
4231         },
4232
4233         /**
4234          * VML override for calculating the bounding box based on offsets
4235          * @param {Boolean} refresh Whether to force a fresh value from the DOM or to
4236          * use the cached value
4237          *
4238          * @return {Object} A hash containing values for x, y, width and height
4239          */
4240
4241         getBBox: function (refresh) {
4242                 var wrapper = this,
4243                         element = wrapper.element,
4244                         bBox = wrapper.bBox;
4245
4246                 // faking getBBox in exported SVG in legacy IE
4247                 if (!bBox || refresh) {
4248                         // faking getBBox in exported SVG in legacy IE
4249                         if (element.nodeName === 'text') {
4250                                 element.style.position = ABSOLUTE;
4251                         }
4252
4253                         bBox = wrapper.bBox = {
4254                                 x: element.offsetLeft,
4255                                 y: element.offsetTop,
4256                                 width: element.offsetWidth,
4257                                 height: element.offsetHeight
4258                         };
4259                 }
4260
4261                 return bBox;
4262         },
4263
4264         /**
4265          * Add an event listener. VML override for normalizing event parameters.
4266          * @param {String} eventType
4267          * @param {Function} handler
4268          */
4269         on: function (eventType, handler) {
4270                 // simplest possible event model for internal use
4271                 this.element['on' + eventType] = function () {
4272                         var evt = win.event;
4273                         evt.target = evt.srcElement;
4274                         handler(evt);
4275                 };
4276                 return this;
4277         },
4278
4279
4280         /**
4281          * VML override private method to update elements based on internal
4282          * properties based on SVG transform
4283          */
4284         updateTransform: function () {
4285                 // aligning non added elements is expensive
4286                 if (!this.added) {
4287                         this.alignOnAdd = true;
4288                         return;
4289                 }
4290
4291                 var wrapper = this,
4292                         elem = wrapper.element,
4293                         translateX = wrapper.translateX || 0,
4294                         translateY = wrapper.translateY || 0,
4295                         x = wrapper.x || 0,
4296                         y = wrapper.y || 0,
4297                         align = wrapper.textAlign || 'left',
4298                         alignCorrection = { left: 0, center: 0.5, right: 1 }[align],
4299                         nonLeft = align && align !== 'left',
4300                         shadows = wrapper.shadows;
4301
4302                 // apply translate
4303                 if (translateX || translateY) {
4304                         css(elem, {
4305                                 marginLeft: translateX,
4306                                 marginTop: translateY
4307                         });
4308                         if (shadows) { // used in labels/tooltip
4309                                 each(shadows, function (shadow) {
4310                                         css(shadow, {
4311                                                 marginLeft: translateX + 1,
4312                                                 marginTop: translateY + 1
4313                                         });
4314                                 });
4315                         }
4316                 }
4317
4318                 // apply inversion
4319                 if (wrapper.inverted) { // wrapper is a group
4320                         each(elem.childNodes, function (child) {
4321                                 wrapper.renderer.invertChild(child, elem);
4322                         });
4323                 }
4324
4325                 if (elem.tagName === 'SPAN') {
4326
4327                         var width, height,
4328                                 rotation = wrapper.rotation,
4329                                 lineHeight,
4330                                 radians = 0,
4331                                 costheta = 1,
4332                                 sintheta = 0,
4333                                 quad,
4334                                 textWidth = pInt(wrapper.textWidth),
4335                                 xCorr = wrapper.xCorr || 0,
4336                                 yCorr = wrapper.yCorr || 0,
4337                                 currentTextTransform = [rotation, align, elem.innerHTML, wrapper.textWidth].join(',');
4338
4339                         if (currentTextTransform !== wrapper.cTT) { // do the calculations and DOM access only if properties changed
4340
4341                                 if (defined(rotation)) {
4342                                         radians = rotation * deg2rad; // deg to rad
4343                                         costheta = mathCos(radians);
4344                                         sintheta = mathSin(radians);
4345
4346                                         // Adjust for alignment and rotation.
4347                                         // Test case: http://highcharts.com/tests/?file=text-rotation
4348                                         css(elem, {
4349                                                 filter: rotation ? ['progid:DXImageTransform.Microsoft.Matrix(M11=', costheta,
4350                                                         ', M12=', -sintheta, ', M21=', sintheta, ', M22=', costheta,
4351                                                         ', sizingMethod=\'auto expand\')'].join('') : NONE
4352                                         });
4353                                 }
4354
4355                                 width = pick(wrapper.elemWidth, elem.offsetWidth);
4356                                 height = pick(wrapper.elemHeight, elem.offsetHeight);
4357
4358                                 // update textWidth
4359                                 if (width > textWidth) {
4360                                         css(elem, {
4361                                                 width: textWidth + PX,
4362                                                 display: 'block',
4363                                                 whiteSpace: 'normal'
4364                                         });
4365                                         width = textWidth;
4366                                 }
4367
4368                                 // correct x and y
4369                                 lineHeight = mathRound((pInt(elem.style.fontSize) || 12) * 1.2);
4370                                 xCorr = costheta < 0 && -width;
4371                                 yCorr = sintheta < 0 && -height;
4372
4373                                 // correct for lineHeight and corners spilling out after rotation
4374                                 quad = costheta * sintheta < 0;
4375                                 xCorr += sintheta * lineHeight * (quad ? 1 - alignCorrection : alignCorrection);
4376                                 yCorr -= costheta * lineHeight * (rotation ? (quad ? alignCorrection : 1 - alignCorrection) : 1);
4377
4378                                 // correct for the length/height of the text
4379                                 if (nonLeft) {
4380                                         xCorr -= width * alignCorrection * (costheta < 0 ? -1 : 1);
4381                                         if (rotation) {
4382                                                 yCorr -= height * alignCorrection * (sintheta < 0 ? -1 : 1);
4383                                         }
4384                                         css(elem, {
4385                                                 textAlign: align
4386                                         });
4387                                 }
4388
4389                                 // record correction
4390                                 wrapper.xCorr = xCorr;
4391                                 wrapper.yCorr = yCorr;
4392                         }
4393
4394                         // apply position with correction
4395                         css(elem, {
4396                                 left: x + xCorr,
4397                                 top: y + yCorr
4398                         });
4399
4400                         // record current text transform
4401                         wrapper.cTT = currentTextTransform;
4402                 }
4403         },
4404
4405         /**
4406          * Apply a drop shadow by copying elements and giving them different strokes
4407          * @param {Boolean} apply
4408          */
4409         shadow: function (apply, group) {
4410                 var shadows = [],
4411                         i,
4412                         element = this.element,
4413                         renderer = this.renderer,
4414                         shadow,
4415                         elemStyle = element.style,
4416                         markup,
4417                         path = element.path;
4418
4419                 // some times empty paths are not strings
4420                 if (path && typeof path.value !== 'string') {
4421                         path = 'x';
4422                 }
4423
4424                 if (apply) {
4425                         for (i = 1; i <= 3; i++) {
4426                                 markup = ['<shape isShadow="true" strokeweight="', (7 - 2 * i),
4427                                         '" filled="false" path="', path,
4428                                         '" coordsize="100,100" style="', element.style.cssText, '" />'];
4429                                 shadow = createElement(renderer.prepVML(markup),
4430                                         null, {
4431                                                 left: pInt(elemStyle.left) + 1,
4432                                                 top: pInt(elemStyle.top) + 1
4433                                         }
4434                                 );
4435
4436                                 // apply the opacity
4437                                 markup = ['<stroke color="black" opacity="', (0.05 * i), '"/>'];
4438                                 createElement(renderer.prepVML(markup), null, null, shadow);
4439
4440
4441                                 // insert it
4442                                 if (group) {
4443                                         group.element.appendChild(shadow);
4444                                 } else {
4445                                         element.parentNode.insertBefore(shadow, element);
4446                                 }
4447
4448                                 // record it
4449                                 shadows.push(shadow);
4450
4451                         }
4452
4453                         this.shadows = shadows;
4454                 }
4455                 return this;
4456
4457         }
4458 });
4459
4460 /**
4461  * The VML renderer
4462  */
4463 VMLRenderer = function () {
4464         this.init.apply(this, arguments);
4465 };
4466 VMLRenderer.prototype = merge(SVGRenderer.prototype, { // inherit SVGRenderer
4467
4468         Element: VMLElement,
4469         isIE8: userAgent.indexOf('MSIE 8.0') > -1,
4470
4471
4472         /**
4473          * Initialize the VMLRenderer
4474          * @param {Object} container
4475          * @param {Number} width
4476          * @param {Number} height
4477          */
4478         init: function (container, width, height) {
4479                 var renderer = this,
4480                         boxWrapper;
4481
4482                 renderer.alignedObjects = [];
4483
4484                 boxWrapper = renderer.createElement(DIV);
4485                 container.appendChild(boxWrapper.element);
4486
4487
4488                 // generate the containing box
4489                 renderer.box = boxWrapper.element;
4490                 renderer.boxWrapper = boxWrapper;
4491
4492
4493                 renderer.setSize(width, height, false);
4494
4495                 // The only way to make IE6 and IE7 print is to use a global namespace. However,
4496                 // with IE8 the only way to make the dynamic shapes visible in screen and print mode
4497                 // seems to be to add the xmlns attribute and the behaviour style inline.
4498                 if (!doc.namespaces.hcv) {
4499
4500                         doc.namespaces.add('hcv', 'urn:schemas-microsoft-com:vml');
4501
4502                         // setup default css
4503                         doc.createStyleSheet().cssText =
4504                                 'hcv\\:fill, hcv\\:path, hcv\\:shape, hcv\\:stroke' +
4505                                 '{ behavior:url(#default#VML); display: inline-block; } ';
4506
4507                 }
4508         },
4509
4510         /**
4511          * Define a clipping rectangle. In VML it is accomplished by storing the values
4512          * for setting the CSS style to all associated members.
4513          *
4514          * @param {Number} x
4515          * @param {Number} y
4516          * @param {Number} width
4517          * @param {Number} height
4518          */
4519         clipRect: function (x, y, width, height) {
4520
4521                 // create a dummy element
4522                 var clipRect = this.createElement();
4523
4524                 // mimic a rectangle with its style object for automatic updating in attr
4525                 return extend(clipRect, {
4526                         members: [],
4527                         left: x,
4528                         top: y,
4529                         width: width,
4530                         height: height,
4531                         getCSS: function (inverted) {
4532                                 var rect = this,//clipRect.element.style,
4533                                         top = rect.top,
4534                                         left = rect.left,
4535                                         right = left + rect.width,
4536                                         bottom = top + rect.height,
4537                                         ret = {
4538                                                 clip: 'rect(' +
4539                                                         mathRound(inverted ? left : top) + 'px,' +
4540                                                         mathRound(inverted ? bottom : right) + 'px,' +
4541                                                         mathRound(inverted ? right : bottom) + 'px,' +
4542                                                         mathRound(inverted ? top : left) + 'px)'
4543                                         };
4544
4545                                 // issue 74 workaround
4546                                 if (!inverted && docMode8) {
4547                                         extend(ret, {
4548                                                 width: right + PX,
4549                                                 height: bottom + PX
4550                                         });
4551                                 }
4552                                 return ret;
4553                         },
4554
4555                         // used in attr and animation to update the clipping of all members
4556                         updateClipping: function () {
4557                                 each(clipRect.members, function (member) {
4558                                         member.css(clipRect.getCSS(member.inverted));
4559                                 });
4560                         }
4561                 });
4562
4563         },
4564
4565
4566         /**
4567          * Take a color and return it if it's a string, make it a gradient if it's a
4568          * gradient configuration object, and apply opacity.
4569          *
4570          * @param {Object} color The color or config object
4571          */
4572         color: function (color, elem, prop) {
4573                 var colorObject,
4574                         regexRgba = /^rgba/,
4575                         markup;
4576
4577                 if (color && color[LINEAR_GRADIENT]) {
4578
4579                         var stopColor,
4580                                 stopOpacity,
4581                                 linearGradient = color[LINEAR_GRADIENT],
4582                                 x1 = linearGradient.x1 || linearGradient[0] || 0,
4583                                 y1 = linearGradient.y1 || linearGradient[1] || 0,
4584                                 x2 = linearGradient.x2 || linearGradient[2] || 0,
4585                                 y2 = linearGradient.y2 || linearGradient[3] || 0,
4586                                 angle,
4587                                 color1,
4588                                 opacity1,
4589                                 color2,
4590                                 opacity2;
4591
4592                         each(color.stops, function (stop, i) {
4593                                 if (regexRgba.test(stop[1])) {
4594                                         colorObject = Color(stop[1]);
4595                                         stopColor = colorObject.get('rgb');
4596                                         stopOpacity = colorObject.get('a');
4597                                 } else {
4598                                         stopColor = stop[1];
4599                                         stopOpacity = 1;
4600                                 }
4601
4602                                 if (!i) { // first
4603                                         color1 = stopColor;
4604                                         opacity1 = stopOpacity;
4605                                 } else {
4606                                         color2 = stopColor;
4607                                         opacity2 = stopOpacity;
4608                                 }
4609                         });
4610
4611                         // Apply the gradient to fills only.
4612                         if (prop === 'fill') {
4613                                 // calculate the angle based on the linear vector
4614                                 angle = 90  - math.atan(
4615                                         (y2 - y1) / // y vector
4616                                         (x2 - x1) // x vector
4617                                         ) * 180 / mathPI;
4618         
4619         
4620                                 // when colors attribute is used, the meanings of opacity and o:opacity2
4621                                 // are reversed.
4622                                 markup = ['<fill colors="0% ', color1, ',100% ', color2, '" angle="', angle,
4623                                         '" opacity="', opacity2, '" o:opacity2="', opacity1,
4624                                         '" type="gradient" focus="100%" method="any" />'];
4625                                 createElement(this.prepVML(markup), null, null, elem);
4626                         
4627                         // Gradients are not supported for VML stroke, return the first color. #722.
4628                         } else {
4629                                 return stopColor;
4630                         }
4631
4632
4633                 // if the color is an rgba color, split it and add a fill node
4634                 // to hold the opacity component
4635                 } else if (regexRgba.test(color) && elem.tagName !== 'IMG') {
4636
4637                         colorObject = Color(color);
4638
4639                         markup = ['<', prop, ' opacity="', colorObject.get('a'), '"/>'];
4640                         createElement(this.prepVML(markup), null, null, elem);
4641
4642                         return colorObject.get('rgb');
4643
4644
4645                 } else {
4646                         var strokeNodes = elem.getElementsByTagName(prop);
4647                         if (strokeNodes.length) {
4648                                 strokeNodes[0].opacity = 1;
4649                         }
4650                         return color;
4651                 }
4652
4653         },
4654
4655         /**
4656          * Take a VML string and prepare it for either IE8 or IE6/IE7.
4657          * @param {Array} markup A string array of the VML markup to prepare
4658          */
4659         prepVML: function (markup) {
4660                 var vmlStyle = 'display:inline-block;behavior:url(#default#VML);',
4661                         isIE8 = this.isIE8;
4662
4663                 markup = markup.join('');
4664
4665                 if (isIE8) { // add xmlns and style inline
4666                         markup = markup.replace('/>', ' xmlns="urn:schemas-microsoft-com:vml" />');
4667                         if (markup.indexOf('style="') === -1) {
4668                                 markup = markup.replace('/>', ' style="' + vmlStyle + '" />');
4669                         } else {
4670                                 markup = markup.replace('style="', 'style="' + vmlStyle);
4671                         }
4672
4673                 } else { // add namespace
4674                         markup = markup.replace('<', '<hcv:');
4675                 }
4676
4677                 return markup;
4678         },
4679
4680         /**
4681          * Create rotated and aligned text
4682          * @param {String} str
4683          * @param {Number} x
4684          * @param {Number} y
4685          */
4686         text: function (str, x, y) {
4687
4688                 var defaultChartStyle = defaultOptions.chart.style;
4689
4690                 return this.createElement('span')
4691                         .attr({
4692                                 text: str,
4693                                 x: mathRound(x),
4694                                 y: mathRound(y)
4695                         })
4696                         .css({
4697                                 whiteSpace: 'nowrap',
4698                                 fontFamily: defaultChartStyle.fontFamily,
4699                                 fontSize: defaultChartStyle.fontSize
4700                         });
4701         },
4702
4703         /**
4704          * Create and return a path element
4705          * @param {Array} path
4706          */
4707         path: function (path) {
4708                 // create the shape
4709                 return this.createElement('shape').attr({
4710                         // subpixel precision down to 0.1 (width and height = 10px)
4711                         coordsize: '100 100',
4712                         d: path
4713                 });
4714         },
4715
4716         /**
4717          * Create and return a circle element. In VML circles are implemented as
4718          * shapes, which is faster than v:oval
4719          * @param {Number} x
4720          * @param {Number} y
4721          * @param {Number} r
4722          */
4723         circle: function (x, y, r) {
4724                 return this.symbol('circle').attr({ x: x, y: y, r: r});
4725         },
4726
4727         /**
4728          * Create a group using an outer div and an inner v:group to allow rotating
4729          * and flipping. A simple v:group would have problems with positioning
4730          * child HTML elements and CSS clip.
4731          *
4732          * @param {String} name The name of the group
4733          */
4734         g: function (name) {
4735                 var wrapper,
4736                         attribs;
4737
4738                 // set the class name
4739                 if (name) {
4740                         attribs = { 'className': PREFIX + name, 'class': PREFIX + name };
4741                 }
4742
4743                 // the div to hold HTML and clipping
4744                 wrapper = this.createElement(DIV).attr(attribs);
4745
4746                 return wrapper;
4747         },
4748
4749         /**
4750          * VML override to create a regular HTML image
4751          * @param {String} src
4752          * @param {Number} x
4753          * @param {Number} y
4754          * @param {Number} width
4755          * @param {Number} height
4756          */
4757         image: function (src, x, y, width, height) {
4758                 var obj = this.createElement('img')
4759                         .attr({ src: src });
4760
4761                 if (arguments.length > 1) {
4762                         obj.css({
4763                                 left: x,
4764                                 top: y,
4765                                 width: width,
4766                                 height: height
4767                         });
4768                 }
4769                 return obj;
4770         },
4771
4772         /**
4773          * VML uses a shape for rect to overcome bugs and rotation problems
4774          */
4775         rect: function (x, y, width, height, r, strokeWidth) {
4776
4777                 if (isObject(x)) {
4778                         y = x.y;
4779                         width = x.width;
4780                         height = x.height;
4781                         strokeWidth = x.strokeWidth;
4782                         x = x.x;
4783                 }
4784                 var wrapper = this.symbol('rect');
4785                 wrapper.r = r;
4786
4787                 return wrapper.attr(wrapper.crisp(strokeWidth, x, y, mathMax(width, 0), mathMax(height, 0)));
4788         },
4789
4790         /**
4791          * In the VML renderer, each child of an inverted div (group) is inverted
4792          * @param {Object} element
4793          * @param {Object} parentNode
4794          */
4795         invertChild: function (element, parentNode) {
4796                 var parentStyle = parentNode.style;
4797
4798                 css(element, {
4799                         flip: 'x',
4800                         left: pInt(parentStyle.width) - 10,
4801                         top: pInt(parentStyle.height) - 10,
4802                         rotation: -90
4803                 });
4804         },
4805
4806         /**
4807          * Symbol definitions that override the parent SVG renderer's symbols
4808          *
4809          */
4810         symbols: {
4811                 // VML specific arc function
4812                 arc: function (x, y, w, h, options) {
4813                         var start = options.start,
4814                                 end = options.end,
4815                                 radius = options.r || w || h,
4816                                 cosStart = mathCos(start),
4817                                 sinStart = mathSin(start),
4818                                 cosEnd = mathCos(end),
4819                                 sinEnd = mathSin(end),
4820                                 innerRadius = options.innerR,
4821                                 circleCorrection = 0.07 / radius,
4822                                 innerCorrection = (innerRadius && 0.1 / innerRadius) || 0;
4823
4824                         if (end - start === 0) { // no angle, don't show it.
4825                                 return ['x'];
4826
4827                         //} else if (end - start == 2 * mathPI) { // full circle
4828                         } else if (2 * mathPI - end + start < circleCorrection) { // full circle
4829                                 // empirical correction found by trying out the limits for different radii
4830                                 cosEnd = -circleCorrection;
4831                         } else if (end - start < innerCorrection) { // issue #186, another mysterious VML arc problem
4832                                 cosEnd = mathCos(start + innerCorrection);
4833                         }
4834
4835                         return [
4836                                 'wa', // clockwise arc to
4837                                 x - radius, // left
4838                                 y - radius, // top
4839                                 x + radius, // right
4840                                 y + radius, // bottom
4841                                 x + radius * cosStart, // start x
4842                                 y + radius * sinStart, // start y
4843                                 x + radius * cosEnd, // end x
4844                                 y + radius * sinEnd, // end y
4845
4846
4847                                 'at', // anti clockwise arc to
4848                                 x - innerRadius, // left
4849                                 y - innerRadius, // top
4850                                 x + innerRadius, // right
4851                                 y + innerRadius, // bottom
4852                                 x + innerRadius * cosEnd, // start x
4853                                 y + innerRadius * sinEnd, // start y
4854                                 x + innerRadius * cosStart, // end x
4855                                 y + innerRadius * sinStart, // end y
4856
4857                                 'x', // finish path
4858                                 'e' // close
4859                         ];
4860
4861                 },
4862                 // Add circle symbol path. This performs significantly faster than v:oval.
4863                 circle: function (x, y, w, h) {
4864
4865                         return [
4866                                 'wa', // clockwisearcto
4867                                 x, // left
4868                                 y, // top
4869                                 x + w, // right
4870                                 y + h, // bottom
4871                                 x + w, // start x
4872                                 y + h / 2,     // start y
4873                                 x + w, // end x
4874                                 y + h / 2,     // end y
4875                                 //'x', // finish path
4876                                 'e' // close
4877                         ];
4878                 },
4879                 /**
4880                  * Add rectangle symbol path which eases rotation and omits arcsize problems
4881                  * compared to the built-in VML roundrect shape
4882                  *
4883                  * @param {Number} left Left position
4884                  * @param {Number} top Top position
4885                  * @param {Number} r Border radius
4886                  * @param {Object} options Width and height
4887                  */
4888
4889                 rect: function (left, top, width, height, options) {
4890                         /*for (var n in r) {
4891                                 logTime && console .log(n)
4892                                 }*/
4893
4894                         if (!defined(options)) {
4895                                 return [];
4896                         }
4897                         var right = left + width,
4898                                 bottom = top + height,
4899                                 r = mathMin(options.r || 0, width, height);
4900
4901                         return [
4902                                 M,
4903                                 left + r, top,
4904
4905                                 L,
4906                                 right - r, top,
4907                                 'wa',
4908                                 right - 2 * r, top,
4909                                 right, top + 2 * r,
4910                                 right - r, top,
4911                                 right, top + r,
4912
4913                                 L,
4914                                 right, bottom - r,
4915                                 'wa',
4916                                 right - 2 * r, bottom - 2 * r,
4917                                 right, bottom,
4918                                 right, bottom - r,
4919                                 right - r, bottom,
4920
4921                                 L,
4922                                 left + r, bottom,
4923                                 'wa',
4924                                 left, bottom - 2 * r,
4925                                 left + 2 * r, bottom,
4926                                 left + r, bottom,
4927                                 left, bottom - r,
4928
4929                                 L,
4930                                 left, top + r,
4931                                 'wa',
4932                                 left, top,
4933                                 left + 2 * r, top + 2 * r,
4934                                 left, top + r,
4935                                 left + r, top,
4936
4937
4938                                 'x',
4939                                 'e'
4940                         ];
4941
4942                 }
4943         }
4944 });
4945
4946         // general renderer
4947         Renderer = VMLRenderer;
4948 }
4949
4950 /* ****************************************************************************
4951  *                                                                            *
4952  * END OF INTERNET EXPLORER <= 8 SPECIFIC CODE                                *
4953  *                                                                            *
4954  *****************************************************************************/
4955
4956 /**
4957  * The chart class
4958  * @param {Object} options
4959  * @param {Function} callback Function to run when the chart has loaded
4960  */
4961 function Chart(options, callback) {
4962
4963         // Handle regular options
4964         var seriesOptions = options.series; // skip merging data points to increase performance
4965         options.series = null;
4966         options = merge(defaultOptions, options); // do the merge
4967         options.series = seriesOptions; // set back the series data
4968
4969         // Define chart variables
4970         var optionsChart = options.chart,
4971                 optionsMargin = optionsChart.margin,
4972                 margin = isObject(optionsMargin) ?
4973                         optionsMargin :
4974                         [optionsMargin, optionsMargin, optionsMargin, optionsMargin],
4975                 optionsMarginTop = pick(optionsChart.marginTop, margin[0]),
4976                 optionsMarginRight = pick(optionsChart.marginRight, margin[1]),
4977                 optionsMarginBottom = pick(optionsChart.marginBottom, margin[2]),
4978                 optionsMarginLeft = pick(optionsChart.marginLeft, margin[3]),
4979                 spacingTop = optionsChart.spacingTop,
4980                 spacingRight = optionsChart.spacingRight,
4981                 spacingBottom = optionsChart.spacingBottom,
4982                 spacingLeft = optionsChart.spacingLeft,
4983                 spacingBox,
4984                 chartTitleOptions,
4985                 chartSubtitleOptions,
4986                 plotTop,
4987                 marginRight,
4988                 marginBottom,
4989                 plotLeft,
4990                 axisOffset,
4991                 renderTo,
4992                 renderToClone,
4993                 container,
4994                 containerId,
4995                 containerWidth,
4996                 containerHeight,
4997                 chartWidth,
4998                 chartHeight,
4999                 oldChartWidth,
5000                 oldChartHeight,
5001                 chartBackground,
5002                 plotBackground,
5003                 plotBGImage,
5004                 plotBorder,
5005                 chart = this,
5006                 chartEvents = optionsChart.events,
5007                 runChartClick = chartEvents && !!chartEvents.click,
5008                 eventType,
5009                 isInsidePlot, // function
5010                 tooltip,
5011                 mouseIsDown,
5012                 loadingDiv,
5013                 loadingSpan,
5014                 loadingShown,
5015                 plotHeight,
5016                 plotWidth,
5017                 tracker,
5018                 trackerGroup,
5019                 placeTrackerGroup,
5020                 legend,
5021                 legendWidth,
5022                 legendHeight,
5023                 chartPosition,
5024                 hasCartesianSeries = optionsChart.showAxes,
5025                 isResizing = 0,
5026                 axes = [],
5027                 maxTicks, // handle the greatest amount of ticks on grouped axes
5028                 series = [],
5029                 inverted,
5030                 renderer,
5031                 tooltipTick,
5032                 tooltipInterval,
5033                 hoverX,
5034                 drawChartBox, // function
5035                 getMargins, // function
5036                 resetMargins, // function
5037                 setChartSize, // function
5038                 resize,
5039                 zoom, // function
5040                 zoomOut; // function
5041
5042
5043         /**
5044          * Create a new axis object
5045          * @param {Object} options
5046          */
5047         function Axis(userOptions) {
5048
5049                 // Define variables
5050                 var isXAxis = userOptions.isX,
5051                         opposite = userOptions.opposite, // needed in setOptions
5052                         horiz = inverted ? !isXAxis : isXAxis,
5053                         side = horiz ?
5054                                 (opposite ? 0 : 2) : // top : bottom
5055                                 (opposite ? 1 : 3),  // right : left
5056                         stacks = {},
5057
5058                         options = merge(
5059                                 isXAxis ? defaultXAxisOptions : defaultYAxisOptions,
5060                                 [defaultTopAxisOptions, defaultRightAxisOptions,
5061                                         defaultBottomAxisOptions, defaultLeftAxisOptions][side],
5062                                 userOptions
5063                         ),
5064
5065                         axis = this,
5066                         axisTitle,
5067                         type = options.type,
5068                         isDatetimeAxis = type === 'datetime',
5069                         isLog = type === 'logarithmic',
5070                         offset = options.offset || 0,
5071                         xOrY = isXAxis ? 'x' : 'y',
5072                         axisLength = 0,
5073                         oldAxisLength,
5074                         transA, // translation factor
5075                         transB, // translation addend
5076                         oldTransA, // used for prerendering
5077                         axisLeft,
5078                         axisTop,
5079                         axisWidth,
5080                         axisHeight,
5081                         axisBottom,
5082                         axisRight,
5083                         translate, // fn
5084                         setAxisTranslation, // fn
5085                         getPlotLinePath, // fn
5086                         axisGroup,
5087                         gridGroup,
5088                         axisLine,
5089                         dataMin,
5090                         dataMax,
5091                         minRange = options.minRange || options.maxZoom,
5092                         range = options.range,
5093                         userMin,
5094                         userMax,
5095                         oldUserMin,
5096                         oldUserMax,
5097                         max = null,
5098                         min = null,
5099                         oldMin,
5100                         oldMax,
5101                         minPadding = options.minPadding,
5102                         maxPadding = options.maxPadding,
5103                         minPixelPadding = 0,
5104                         isLinked = defined(options.linkedTo),
5105                         ignoreMinPadding, // can be set to true by a column or bar series
5106                         ignoreMaxPadding,
5107                         usePercentage,
5108                         events = options.events,
5109                         eventType,
5110                         plotLinesAndBands = [],
5111                         tickInterval,
5112                         minorTickInterval,
5113                         magnitude,
5114                         tickPositions, // array containing predefined positions
5115                         tickPositioner = options.tickPositioner,
5116                         ticks = {},
5117                         minorTicks = {},
5118                         alternateBands = {},
5119                         tickAmount,
5120                         labelOffset,
5121                         axisTitleMargin,// = options.title.margin,
5122                         categories = options.categories,
5123                         labelFormatter = options.labels.formatter ||  // can be overwritten by dynamic format
5124                                 function () {
5125                                         var value = this.value,
5126                                                 dateTimeLabelFormat = this.dateTimeLabelFormat,
5127                                                 ret;
5128
5129                                         if (dateTimeLabelFormat) { // datetime axis
5130                                                 ret = dateFormat(dateTimeLabelFormat, value);
5131
5132                                         } else if (tickInterval % 1000000 === 0) { // use M abbreviation
5133                                                 ret = (value / 1000000) + 'M';
5134
5135                                         } else if (tickInterval % 1000 === 0) { // use k abbreviation
5136                                                 ret = (value / 1000) + 'k';
5137
5138                                         } else if (!categories && value >= 1000) { // add thousands separators
5139                                                 ret = numberFormat(value, 0);
5140
5141                                         } else { // strings (categories) and small numbers
5142                                                 ret = value;
5143                                         }
5144                                         return ret;
5145                                 },
5146
5147                         staggerLines = horiz && options.labels.staggerLines,
5148                         reversed = options.reversed,
5149                         tickmarkOffset = (categories && options.tickmarkPlacement === 'between') ? 0.5 : 0;
5150
5151                 /**
5152                  * The Tick class
5153                  */
5154                 function Tick(pos, type) {
5155                         var tick = this;
5156                         tick.pos = pos;
5157                         tick.type = type || '';
5158                         tick.isNew = true;
5159
5160                         if (!type) {
5161                                 tick.addLabel();
5162                         }
5163                 }
5164                 Tick.prototype = {
5165
5166                         /**
5167                          * Write the tick label
5168                          */
5169                         addLabel: function () {
5170                                 var tick = this,
5171                                         pos = tick.pos,
5172                                         labelOptions = options.labels,
5173                                         str,
5174                                         width = (categories && horiz && categories.length &&
5175                                                 !labelOptions.step && !labelOptions.staggerLines &&
5176                                                 !labelOptions.rotation &&
5177                                                 plotWidth / categories.length) ||
5178                                                 (!horiz && plotWidth / 2),
5179                                         isFirst = pos === tickPositions[0],
5180                                         isLast = pos === tickPositions[tickPositions.length - 1],
5181                                         css,
5182                                         value = categories && defined(categories[pos]) ? categories[pos] : pos,
5183                                         label = tick.label,
5184                                         tickPositionInfo = tickPositions.info,
5185                                         dateTimeLabelFormat;
5186
5187                                 // Set the datetime label format. If a higher rank is set for this position, use that. If not,
5188                                 // use the general format.
5189                                 if (isDatetimeAxis && tickPositionInfo) {
5190                                         dateTimeLabelFormat = options.dateTimeLabelFormats[tickPositionInfo.higherRanks[pos] || tickPositionInfo.unitName];
5191                                 }
5192
5193                                 // set properties for access in render method
5194                                 tick.isFirst = isFirst;
5195                                 tick.isLast = isLast;
5196
5197                                 // get the string
5198                                 str = labelFormatter.call({
5199                                                 axis: axis, // docs
5200                                                 chart: chart, // docs
5201                                                 isFirst: isFirst,
5202                                                 isLast: isLast,
5203                                                 dateTimeLabelFormat: dateTimeLabelFormat,
5204                                                 value: isLog ? lin2log(value) : value
5205                                         });
5206
5207
5208                                 // prepare CSS
5209                                 css = width && { width: mathMax(1, mathRound(width - 2 * (labelOptions.padding || 10))) + PX };
5210                                 css = extend(css, labelOptions.style);
5211
5212                                 // first call
5213                                 if (!defined(label)) {
5214                                         tick.label =
5215                                                 defined(str) && labelOptions.enabled ?
5216                                                         renderer.text(
5217                                                                         str,
5218                                                                         0,
5219                                                                         0,
5220                                                                         labelOptions.useHTML
5221                                                                 )
5222                                                                 .attr({
5223                                                                         align: labelOptions.align,
5224                                                                         rotation: labelOptions.rotation
5225                                                                 })
5226                                                                 // without position absolute, IE export sometimes is wrong
5227                                                                 .css(css)
5228                                                                 .add(axisGroup) :
5229                                                         null;
5230
5231                                 // update
5232                                 } else if (label) {
5233                                         label.attr({
5234                                                         text: str
5235                                                 })
5236                                                 .css(css);
5237                                 }
5238                         },
5239                         /**
5240                          * Get the offset height or width of the label
5241                          */
5242                         getLabelSize: function () {
5243                                 var label = this.label;
5244                                 return label ?
5245                                         ((this.labelBBox = label.getBBox()))[horiz ? 'height' : 'width'] :
5246                                         0;
5247                                 },
5248                         /**
5249                          * Put everything in place
5250                          *
5251                          * @param index {Number}
5252                          * @param old {Boolean} Use old coordinates to prepare an animation into new position
5253                          */
5254                         render: function (index, old) {
5255                                 var tick = this,
5256                                         type = tick.type,
5257                                         label = tick.label,
5258                                         pos = tick.pos,
5259                                         labelOptions = options.labels,
5260                                         gridLine = tick.gridLine,
5261                                         gridPrefix = type ? type + 'Grid' : 'grid',
5262                                         tickPrefix = type ? type + 'Tick' : 'tick',
5263                                         gridLineWidth = options[gridPrefix + 'LineWidth'],
5264                                         gridLineColor = options[gridPrefix + 'LineColor'],
5265                                         dashStyle = options[gridPrefix + 'LineDashStyle'],
5266                                         tickLength = options[tickPrefix + 'Length'],
5267                                         tickWidth = options[tickPrefix + 'Width'] || 0,
5268                                         tickColor = options[tickPrefix + 'Color'],
5269                                         tickPosition = options[tickPrefix + 'Position'],
5270                                         gridLinePath,
5271                                         mark = tick.mark,
5272                                         markPath,
5273                                         step = labelOptions.step,
5274                                         cHeight = (old && oldChartHeight) || chartHeight,
5275                                         attribs,
5276                                         x,
5277                                         y;
5278
5279                                 // get x and y position for ticks and labels
5280                                 x = horiz ?
5281                                         translate(pos + tickmarkOffset, null, null, old) + transB :
5282                                         axisLeft + offset + (opposite ? ((old && oldChartWidth) || chartWidth) - axisRight - axisLeft : 0);
5283
5284                                 y = horiz ?
5285                                         cHeight - axisBottom + offset - (opposite ? axisHeight : 0) :
5286                                         cHeight - translate(pos + tickmarkOffset, null, null, old) - transB;
5287
5288                                 // create the grid line
5289                                 if (gridLineWidth) {
5290                                         gridLinePath = getPlotLinePath(pos + tickmarkOffset, gridLineWidth, old);
5291
5292                                         if (gridLine === UNDEFINED) {
5293                                                 attribs = {
5294                                                         stroke: gridLineColor,
5295                                                         'stroke-width': gridLineWidth
5296                                                 };
5297                                                 if (dashStyle) {
5298                                                         attribs.dashstyle = dashStyle;
5299                                                 }
5300                                                 if (!type) {
5301                                                         attribs.zIndex = 1;
5302                                                 }
5303                                                 tick.gridLine = gridLine =
5304                                                         gridLineWidth ?
5305                                                                 renderer.path(gridLinePath)
5306                                                                         .attr(attribs).add(gridGroup) :
5307                                                                 null;
5308                                         }
5309
5310                                         // If the parameter 'old' is set, the current call will be followed
5311                                         // by another call, therefore do not do any animations this time
5312                                         if (!old && gridLine && gridLinePath) {
5313                                                 gridLine.animate({
5314                                                         d: gridLinePath
5315                                                 });
5316                                         }
5317                                 }
5318
5319                                 // create the tick mark
5320                                 if (tickWidth) {
5321
5322                                         // negate the length
5323                                         if (tickPosition === 'inside') {
5324                                                 tickLength = -tickLength;
5325                                         }
5326                                         if (opposite) {
5327                                                 tickLength = -tickLength;
5328                                         }
5329
5330                                         markPath = renderer.crispLine([
5331                                                 M,
5332                                                 x,
5333                                                 y,
5334                                                 L,
5335                                                 x + (horiz ? 0 : -tickLength),
5336                                                 y + (horiz ? tickLength : 0)
5337                                         ], tickWidth);
5338
5339                                         if (mark) { // updating
5340                                                 mark.animate({
5341                                                         d: markPath
5342                                                 });
5343                                         } else { // first time
5344                                                 tick.mark = renderer.path(
5345                                                         markPath
5346                                                 ).attr({
5347                                                         stroke: tickColor,
5348                                                         'stroke-width': tickWidth
5349                                                 }).add(axisGroup);
5350                                         }
5351                                 }
5352
5353                                 // the label is created on init - now move it into place
5354                                 if (label && !isNaN(x)) {
5355                                         x = x + labelOptions.x - (tickmarkOffset && horiz ?
5356                                                 tickmarkOffset * transA * (reversed ? -1 : 1) : 0);
5357                                         y = y + labelOptions.y - (tickmarkOffset && !horiz ?
5358                                                 tickmarkOffset * transA * (reversed ? 1 : -1) : 0);
5359
5360                                         // vertically centered
5361                                         if (!defined(labelOptions.y)) {
5362                                                 y += pInt(label.styles.lineHeight) * 0.9 - label.getBBox().height / 2;
5363                                         }
5364
5365
5366                                         // correct for staggered labels
5367                                         if (staggerLines) {
5368                                                 y += (index / (step || 1) % staggerLines) * 16;
5369                                         }
5370
5371                                         // apply show first and show last
5372                                         if ((tick.isFirst && !pick(options.showFirstLabel, 1)) ||
5373                                                         (tick.isLast && !pick(options.showLastLabel, 1))) {
5374                                                 label.hide();
5375                                         } else {
5376                                                 // show those that may have been previously hidden, either by show first/last, or by step
5377                                                 label.show();
5378                                         }
5379
5380                                         // apply step
5381                                         if (step && index % step) {
5382                                                 // show those indices dividable by step
5383                                                 label.hide();
5384                                         }
5385
5386                                         label[tick.isNew ? 'attr' : 'animate']({
5387                                                 x: x,
5388                                                 y: y
5389                                         });
5390                                 }
5391
5392                                 tick.isNew = false;
5393                         },
5394                         /**
5395                          * Destructor for the tick prototype
5396                          */
5397                         destroy: function () {
5398                                 destroyObjectProperties(this);
5399                         }
5400                 };
5401
5402                 /**
5403                  * The object wrapper for plot lines and plot bands
5404                  * @param {Object} options
5405                  */
5406                 function PlotLineOrBand(options) {
5407                         var plotLine = this;
5408                         if (options) {
5409                                 plotLine.options = options;
5410                                 plotLine.id = options.id;
5411                         }
5412
5413                         //plotLine.render()
5414                         return plotLine;
5415                 }
5416
5417                 PlotLineOrBand.prototype = {
5418
5419                 /**
5420                  * Render the plot line or plot band. If it is already existing,
5421                  * move it.
5422                  */
5423                 render: function () {
5424                         var plotLine = this,
5425                                 halfPointRange = (axis.pointRange || 0) / 2,
5426                                 options = plotLine.options,
5427                                 optionsLabel = options.label,
5428                                 label = plotLine.label,
5429                                 width = options.width,
5430                                 to = options.to,
5431                                 from = options.from,
5432                                 value = options.value,
5433                                 toPath, // bands only
5434                                 dashStyle = options.dashStyle,
5435                                 svgElem = plotLine.svgElem,
5436                                 path = [],
5437                                 addEvent,
5438                                 eventType,
5439                                 xs,
5440                                 ys,
5441                                 x,
5442                                 y,
5443                                 color = options.color,
5444                                 zIndex = options.zIndex,
5445                                 events = options.events,
5446                                 attribs;
5447
5448                         // logarithmic conversion
5449                         if (isLog) {
5450                                 from = log2lin(from);
5451                                 to = log2lin(to);
5452                                 value = log2lin(value);
5453                         }
5454
5455                         // plot line
5456                         if (width) {
5457                                 path = getPlotLinePath(value, width);
5458                                 attribs = {
5459                                         stroke: color,
5460                                         'stroke-width': width
5461                                 };
5462                                 if (dashStyle) {
5463                                         attribs.dashstyle = dashStyle;
5464                                 }
5465                         } else if (defined(from) && defined(to)) { // plot band
5466                                 // keep within plot area
5467                                 from = mathMax(from, min - halfPointRange);
5468                                 to = mathMin(to, max + halfPointRange);
5469
5470                                 toPath = getPlotLinePath(to);
5471                                 path = getPlotLinePath(from);
5472                                 if (path && toPath) {
5473                                         path.push(
5474                                                 toPath[4],
5475                                                 toPath[5],
5476                                                 toPath[1],
5477                                                 toPath[2]
5478                                         );
5479                                 } else { // outside the axis area
5480                                         path = null;
5481                                 }
5482                                 attribs = {
5483                                         fill: color
5484                                 };
5485                         } else {
5486                                 return;
5487                         }
5488                         // zIndex
5489                         if (defined(zIndex)) {
5490                                 attribs.zIndex = zIndex;
5491                         }
5492
5493                         // common for lines and bands
5494                         if (svgElem) {
5495                                 if (path) {
5496                                         svgElem.animate({
5497                                                 d: path
5498                                         }, null, svgElem.onGetPath);
5499                                 } else {
5500                                         svgElem.hide();
5501                                         svgElem.onGetPath = function () {
5502                                                 svgElem.show();
5503                                         };
5504                                 }
5505                         } else if (path && path.length) {
5506                                 plotLine.svgElem = svgElem = renderer.path(path)
5507                                         .attr(attribs).add();
5508
5509                                 // events
5510                                 if (events) {
5511                                         addEvent = function (eventType) {
5512                                                 svgElem.on(eventType, function (e) {
5513                                                         events[eventType].apply(plotLine, [e]);
5514                                                 });
5515                                         };
5516                                         for (eventType in events) {
5517                                                 addEvent(eventType);
5518                                         }
5519                                 }
5520                         }
5521
5522                         // the plot band/line label
5523                         if (optionsLabel && defined(optionsLabel.text) && path && path.length && axisWidth > 0 && axisHeight > 0) {
5524                                 // apply defaults
5525                                 optionsLabel = merge({
5526                                         align: horiz && toPath && 'center',
5527                                         x: horiz ? !toPath && 4 : 10,
5528                                         verticalAlign : !horiz && toPath && 'middle',
5529                                         y: horiz ? toPath ? 16 : 10 : toPath ? 6 : -4,
5530                                         rotation: horiz && !toPath && 90
5531                                 }, optionsLabel);
5532
5533                                 // add the SVG element
5534                                 if (!label) {
5535                                         plotLine.label = label = renderer.text(
5536                                                         optionsLabel.text,
5537                                                         0,
5538                                                         0
5539                                                 )
5540                                                 .attr({
5541                                                         align: optionsLabel.textAlign || optionsLabel.align,
5542                                                         rotation: optionsLabel.rotation,
5543                                                         zIndex: zIndex
5544                                                 })
5545                                                 .css(optionsLabel.style)
5546                                                 .add();
5547                                 }
5548
5549                                 // get the bounding box and align the label
5550                                 xs = [path[1], path[4], pick(path[6], path[1])];
5551                                 ys = [path[2], path[5], pick(path[7], path[2])];
5552                                 x = arrayMin(xs);
5553                                 y = arrayMin(ys);
5554
5555                                 label.align(optionsLabel, false, {
5556                                         x: x,
5557                                         y: y,
5558                                         width: arrayMax(xs) - x,
5559                                         height: arrayMax(ys) - y
5560                                 });
5561                                 label.show();
5562
5563                         } else if (label) { // move out of sight
5564                                 label.hide();
5565                         }
5566
5567                         // chainable
5568                         return plotLine;
5569                 },
5570
5571                 /**
5572                  * Remove the plot line or band
5573                  */
5574                 destroy: function () {
5575                         var obj = this;
5576
5577                         destroyObjectProperties(obj);
5578
5579                         // remove it from the lookup
5580                         erase(plotLinesAndBands, obj);
5581                 }
5582                 };
5583
5584                 /**
5585                  * The class for stack items
5586                  */
5587                 function StackItem(options, isNegative, x, stackOption) {
5588                         var stackItem = this;
5589
5590                         // Tells if the stack is negative
5591                         stackItem.isNegative = isNegative;
5592
5593                         // Save the options to be able to style the label
5594                         stackItem.options = options;
5595
5596                         // Save the x value to be able to position the label later
5597                         stackItem.x = x;
5598
5599                         // Save the stack option on the series configuration object
5600                         stackItem.stack = stackOption;
5601
5602                         // The align options and text align varies on whether the stack is negative and
5603                         // if the chart is inverted or not.
5604                         // First test the user supplied value, then use the dynamic.
5605                         stackItem.alignOptions = {
5606                                 align: options.align || (inverted ? (isNegative ? 'left' : 'right') : 'center'),
5607                                 verticalAlign: options.verticalAlign || (inverted ? 'middle' : (isNegative ? 'bottom' : 'top')),
5608                                 y: pick(options.y, inverted ? 4 : (isNegative ? 14 : -6)),
5609                                 x: pick(options.x, inverted ? (isNegative ? -6 : 6) : 0)
5610                         };
5611
5612                         stackItem.textAlign = options.textAlign || (inverted ? (isNegative ? 'right' : 'left') : 'center');
5613                 }
5614
5615                 StackItem.prototype = {
5616                         destroy: function () {
5617                                 destroyObjectProperties(this);
5618                         },
5619
5620                         /**
5621                          * Sets the total of this stack. Should be called when a serie is hidden or shown
5622                          * since that will affect the total of other stacks.
5623                          */
5624                         setTotal: function (total) {
5625                                 this.total = total;
5626                                 this.cum = total;
5627                         },
5628
5629                         /**
5630                          * Renders the stack total label and adds it to the stack label group.
5631                          */
5632                         render: function (group) {
5633                                 var stackItem = this,                                                                   // aliased this
5634                                         str = stackItem.options.formatter.call(stackItem);  // format the text in the label
5635
5636                                 // Change the text to reflect the new total and set visibility to hidden in case the serie is hidden
5637                                 if (stackItem.label) {
5638                                         stackItem.label.attr({text: str, visibility: HIDDEN});
5639                                 // Create new label
5640                                 } else {
5641                                         stackItem.label =
5642                                                 chart.renderer.text(str, 0, 0)                          // dummy positions, actual position updated with setOffset method in columnseries
5643                                                         .css(stackItem.options.style)                   // apply style
5644                                                         .attr({align: stackItem.textAlign,                      // fix the text-anchor
5645                                                                 rotation: stackItem.options.rotation,   // rotation
5646                                                                 visibility: HIDDEN })                                   // hidden until setOffset is called
5647                                                         .add(group);                                                    // add to the labels-group
5648                                 }
5649                         },
5650
5651                         /**
5652                          * Sets the offset that the stack has from the x value and repositions the label.
5653                          */
5654                         setOffset: function (xOffset, xWidth) {
5655                                 var stackItem = this,                                                                           // aliased this
5656                                         neg = stackItem.isNegative,                                                             // special treatment is needed for negative stacks
5657                                         y = axis.translate(stackItem.total),                                    // stack value translated mapped to chart coordinates
5658                                         yZero = axis.translate(0),                                                              // stack origin
5659                                         h = mathAbs(y - yZero),                                                                 // stack height
5660                                         x = chart.xAxis[0].translate(stackItem.x) + xOffset,    // stack x position
5661                                         plotHeight = chart.plotHeight,
5662                                         stackBox = {    // this is the box for the complete stack
5663                                                         x: inverted ? (neg ? y : y - h) : x,
5664                                                         y: inverted ? plotHeight - x - xWidth : (neg ? (plotHeight - y - h) : plotHeight - y),
5665                                                         width: inverted ? h : xWidth,
5666                                                         height: inverted ? xWidth : h
5667                                         };
5668
5669                                 if (stackItem.label) {
5670                                         stackItem.label
5671                                                 .align(stackItem.alignOptions, null, stackBox)  // align the label to the box
5672                                                 .attr({visibility: VISIBLE});                                   // set visibility
5673                                 }
5674                         }
5675                 };
5676
5677                 /**
5678                  * Get the minimum and maximum for the series of each axis
5679                  */
5680                 function getSeriesExtremes() {
5681                         var posStack = [],
5682                                 negStack = [],
5683                                 i;
5684
5685                         // reset dataMin and dataMax in case we're redrawing
5686                         dataMin = dataMax = null;
5687
5688                         // loop through this axis' series
5689                         each(axis.series, function (series) {
5690
5691                                 if (series.visible || !optionsChart.ignoreHiddenSeries) {
5692
5693                                         var seriesOptions = series.options,
5694                                                 stacking,
5695                                                 posPointStack,
5696                                                 negPointStack,
5697                                                 stackKey,
5698                                                 stackOption,
5699                                                 negKey,
5700                                                 xData,
5701                                                 yData,
5702                                                 x,
5703                                                 y,
5704                                                 threshold = seriesOptions.threshold,
5705                                                 yDataLength,
5706                                                 activeYData = [],
5707                                                 activeCounter = 0;
5708
5709                                         // Get dataMin and dataMax for X axes
5710                                         if (isXAxis) {
5711                                                 xData = series.xData;
5712                                                 if (xData.length) {
5713                                                         dataMin = mathMin(pick(dataMin, xData[0]), arrayMin(xData));
5714                                                         dataMax = mathMax(pick(dataMax, xData[0]), arrayMax(xData));
5715                                                 }
5716
5717                                         // Get dataMin and dataMax for Y axes, as well as handle stacking and processed data
5718                                         } else {
5719                                                 var isNegative,
5720                                                         pointStack,
5721                                                         key,
5722                                                         cropped = series.cropped,
5723                                                         xExtremes = series.xAxis.getExtremes(),
5724                                                         //findPointRange,
5725                                                         //pointRange,
5726                                                         j,
5727                                                         hasModifyValue = !!series.modifyValue;
5728
5729
5730                                                 // Handle stacking
5731                                                 stacking = seriesOptions.stacking;
5732                                                 usePercentage = stacking === 'percent';
5733
5734                                                 // create a stack for this particular series type
5735                                                 if (stacking) {
5736                                                         stackOption = seriesOptions.stack;
5737                                                         stackKey = series.type + pick(stackOption, '');
5738                                                         negKey = '-' + stackKey;
5739                                                         series.stackKey = stackKey; // used in translate
5740
5741                                                         posPointStack = posStack[stackKey] || []; // contains the total values for each x
5742                                                         posStack[stackKey] = posPointStack;
5743
5744                                                         negPointStack = negStack[negKey] || [];
5745                                                         negStack[negKey] = negPointStack;
5746                                                 }
5747                                                 if (usePercentage) {
5748                                                         dataMin = 0;
5749                                                         dataMax = 99;
5750                                                 }
5751
5752
5753                                                 // processData can alter series.pointRange, so this goes after
5754                                                 //findPointRange = series.pointRange === null;
5755
5756                                                 xData = series.processedXData;
5757                                                 yData = series.processedYData;
5758                                                 yDataLength = yData.length;
5759
5760
5761                                                 // loop over the non-null y values and read them into a local array
5762                                                 for (i = 0; i < yDataLength; i++) {
5763                                                         x = xData[i];
5764                                                         y = yData[i];
5765                                                         if (y !== null && y !== UNDEFINED) {
5766
5767                                                                 // read stacked values into a stack based on the x value,
5768                                                                 // the sign of y and the stack key
5769                                                                 if (stacking) {
5770                                                                         isNegative = y < threshold;
5771                                                                         pointStack = isNegative ? negPointStack : posPointStack;
5772                                                                         key = isNegative ? negKey : stackKey;
5773
5774                                                                         y = pointStack[x] =
5775                                                                                 defined(pointStack[x]) ?
5776                                                                                 pointStack[x] + y : y;
5777
5778
5779                                                                         // add the series
5780                                                                         if (!stacks[key]) {
5781                                                                                 stacks[key] = {};
5782                                                                         }
5783
5784                                                                         // If the StackItem is there, just update the values,
5785                                                                         // if not, create one first
5786                                                                         if (!stacks[key][x]) {
5787                                                                                 stacks[key][x] = new StackItem(options.stackLabels, isNegative, x, stackOption);
5788                                                                         }
5789                                                                         stacks[key][x].setTotal(y);
5790
5791
5792                                                                 // general hook, used for Highstock compare values feature
5793                                                                 } else if (hasModifyValue) {
5794                                                                         y = series.modifyValue(y);
5795                                                                 }
5796
5797                                                                 // get the smallest distance between points
5798                                                                 /*if (i) {
5799                                                                         distance = mathAbs(xData[i] - xData[i - 1]);
5800                                                                         pointRange = pointRange === UNDEFINED ? distance : mathMin(distance, pointRange);
5801                                                                 }*/
5802
5803                                                                 // for points within the visible range, including the first point outside the
5804                                                                 // visible range, consider y extremes
5805                                                                 if (cropped || ((xData[i + 1] || x) >= xExtremes.min && (xData[i - 1] || x) <= xExtremes.max)) {
5806
5807                                                                         j = y.length;
5808                                                                         if (j) { // array, like ohlc data
5809                                                                                 while (j--) {
5810                                                                                         if (y[j] !== null) {
5811                                                                                                 activeYData[activeCounter++] = y[j];
5812                                                                                         }
5813                                                                                 }
5814                                                                         } else {
5815                                                                                 activeYData[activeCounter++] = y;
5816                                                                         }
5817                                                                 }
5818                                                         }
5819                                                 }
5820
5821                                                 // record the least unit distance
5822                                                 /*if (findPointRange) {
5823                                                         series.pointRange = pointRange || 1;
5824                                                 }
5825                                                 series.closestPointRange = pointRange;*/
5826
5827
5828                                                 // Get the dataMin and dataMax so far. If percentage is used, the min and max are
5829                                                 // always 0 and 100. If the length of activeYData is 0, continue with null values.
5830                                                 if (!usePercentage && activeYData.length) {
5831                                                         dataMin = mathMin(pick(dataMin, activeYData[0]), arrayMin(activeYData));
5832                                                         dataMax = mathMax(pick(dataMax, activeYData[0]), arrayMax(activeYData));
5833                                                 }
5834
5835
5836                                                 // todo: instead of checking useThreshold, just set the threshold to 0
5837                                                 // in area and column-like chart types
5838                                                 if (series.useThreshold && threshold !== null) {
5839                                                         if (dataMin >= threshold) {
5840                                                                 dataMin = threshold;
5841                                                                 ignoreMinPadding = true;
5842                                                         } else if (dataMax < threshold) {
5843                                                                 dataMax = threshold;
5844                                                                 ignoreMaxPadding = true;
5845                                                         }
5846                                                 }
5847                                         }
5848                                 }
5849                         });
5850
5851                 }
5852
5853                 /**
5854                  * Translate from axis value to pixel position on the chart, or back
5855                  *
5856                  */
5857                 translate = function (val, backwards, cvsCoord, old, handleLog) {
5858                         var sign = 1,
5859                                 cvsOffset = 0,
5860                                 localA = old ? oldTransA : transA,
5861                                 localMin = old ? oldMin : min,
5862                                 returnValue,
5863                                 postTranslate = options.ordinal || (isLog && handleLog);
5864
5865                         if (!localA) {
5866                                 localA = transA;
5867                         }
5868
5869                         if (cvsCoord) {
5870                                 sign *= -1; // canvas coordinates inverts the value
5871                                 cvsOffset = axisLength;
5872                         }
5873                         if (reversed) { // reversed axis
5874                                 sign *= -1;
5875                                 cvsOffset -= sign * axisLength;
5876                         }
5877
5878                         if (backwards) { // reverse translation
5879                                 if (reversed) {
5880                                         val = axisLength - val;
5881                                 }
5882                                 returnValue = val / localA + localMin; // from chart pixel to value
5883                                 if (postTranslate) { // log and ordinal axes
5884                                         returnValue = axis.lin2val(returnValue);
5885                                 }
5886
5887                         } else { // normal translation, from axis value to pixel, relative to plot
5888                                 if (postTranslate) { // log and ordinal axes
5889                                         val = axis.val2lin(val);
5890                                 }
5891
5892                                 returnValue = sign * (val - localMin) * localA + cvsOffset + (sign * minPixelPadding);
5893                         }
5894
5895                         return returnValue;
5896                 };
5897
5898                 /**
5899                  * Create the path for a plot line that goes from the given value on
5900                  * this axis, across the plot to the opposite side
5901                  * @param {Number} value
5902                  * @param {Number} lineWidth Used for calculation crisp line
5903                  * @param {Number] old Use old coordinates (for resizing and rescaling)
5904                  */
5905                 getPlotLinePath = function (value, lineWidth, old) {
5906                         var x1,
5907                                 y1,
5908                                 x2,
5909                                 y2,
5910                                 translatedValue = translate(value, null, null, old),
5911                                 cHeight = (old && oldChartHeight) || chartHeight,
5912                                 cWidth = (old && oldChartWidth) || chartWidth,
5913                                 skip;
5914
5915                         x1 = x2 = mathRound(translatedValue + transB);
5916                         y1 = y2 = mathRound(cHeight - translatedValue - transB);
5917
5918                         if (isNaN(translatedValue)) { // no min or max
5919                                 skip = true;
5920
5921                         } else if (horiz) {
5922                                 y1 = axisTop;
5923                                 y2 = cHeight - axisBottom;
5924                                 if (x1 < axisLeft || x1 > axisLeft + axisWidth) {
5925                                         skip = true;
5926                                 }
5927                         } else {
5928                                 x1 = axisLeft;
5929                                 x2 = cWidth - axisRight;
5930
5931                                 if (y1 < axisTop || y1 > axisTop + axisHeight) {
5932                                         skip = true;
5933                                 }
5934                         }
5935                         return skip ?
5936                                 null :
5937                                 renderer.crispLine([M, x1, y1, L, x2, y2], lineWidth || 0);
5938                 };
5939
5940                 /**
5941                  * Fix JS round off float errors
5942                  * @param {Number} num
5943                  */
5944                 function correctFloat(num) {
5945                         var invMag, ret = num;
5946                         magnitude = pick(magnitude, math.pow(10, mathFloor(math.log(tickInterval) / math.LN10)));
5947
5948                         if (magnitude < 1) {
5949                                 invMag = mathRound(1 / magnitude)  * 10;
5950                                 ret = mathRound(num * invMag) / invMag;
5951                         }
5952                         return ret;
5953                 }
5954
5955                 /**
5956                  * Set the tick positions of a linear axis to round values like whole tens or every five.
5957                  */
5958                 function setLinearTickPositions() {
5959
5960                         var pos,
5961                                 lastPos,
5962                                 roundedMin = correctFloat(mathFloor(min / tickInterval) * tickInterval),
5963                                 roundedMax = correctFloat(mathCeil(max / tickInterval) * tickInterval);
5964
5965                         tickPositions = [];
5966
5967                         // Populate the intermediate values
5968                         pos = roundedMin;
5969                         while (pos <= roundedMax) {
5970
5971                                 // Place the tick on the rounded value
5972                                 tickPositions.push(pos);
5973
5974                                 // Always add the raw tickInterval, not the corrected one.
5975                                 pos = correctFloat(pos + tickInterval);
5976
5977                                 // If the interval is not big enough in the current min - max range to actually increase
5978                                 // the loop variable, we need to break out to prevent endless loop. Issue #619
5979                                 if (pos === lastPos) {
5980                                         break;
5981                                 }
5982
5983                                 // Record the last value
5984                                 lastPos = pos;
5985                         }
5986                 }
5987
5988                 /**
5989                  * Adjust the min and max for the minimum range. Keep in mind that the series data is 
5990                  * not yet processed, so we don't have information on data cropping and grouping, or 
5991                  * updated axis.pointRange or series.pointRange. The data can't be processed until
5992                  * we have finally established min and max.
5993                  */
5994                 function adjustForMinRange() {
5995                         var zoomOffset,
5996                                 spaceAvailable = dataMax - dataMin >= minRange,
5997                                 closestDataRange,
5998                                 i,
5999                                 distance,
6000                                 xData,
6001                                 loopLength,
6002                                 minArgs,
6003                                 maxArgs;
6004                                 
6005                         // Set the automatic minimum range based on the closest point distance
6006                         if (isXAxis && minRange === UNDEFINED) {
6007
6008                                 if (defined(options.min) || defined(options.max)) {
6009                                         minRange = null; // don't do this again
6010
6011                                 } else {
6012
6013                                         // Find the closest distance between raw data points, as opposed to
6014                                         // closestPointRange that applies to processed points (cropped and grouped)
6015                                         each(axis.series, function (series) {
6016                                                 xData = series.xData;
6017                                                 loopLength = series.xIncrement ? 1 : xData.length - 1;
6018                                                 for (i = loopLength; i > 0; i--) {
6019                                                         distance = xData[i] - xData[i - 1];
6020                                                         if (closestDataRange === UNDEFINED || distance < closestDataRange) {
6021                                                                 closestDataRange = distance;
6022                                                         }
6023                                                 }
6024                                         });
6025                                         minRange = mathMin(closestDataRange * 5, dataMax - dataMin);
6026                                 }
6027                         }
6028                         
6029                         // if minRange is exceeded, adjust
6030                         if (max - min < minRange) {
6031
6032                                 zoomOffset = (minRange - max + min) / 2;
6033
6034                                 // if min and max options have been set, don't go beyond it
6035                                 minArgs = [min - zoomOffset, pick(options.min, min - zoomOffset)];
6036                                 if (spaceAvailable) { // if space is available, stay within the data range
6037                                         minArgs[2] = dataMin;
6038                                 }
6039                                 min = arrayMax(minArgs);
6040
6041                                 maxArgs = [min + minRange, pick(options.max, min + minRange)];
6042                                 if (spaceAvailable) { // if space is availabe, stay within the data range
6043                                         maxArgs[2] = dataMax;
6044                                 }
6045                                 
6046                                 max = arrayMin(maxArgs);
6047
6048                                 // now if the max is adjusted, adjust the min back
6049                                 if (max - min < minRange) {
6050                                         minArgs[0] = max - minRange;
6051                                         minArgs[1] = pick(options.min, max - minRange);
6052                                         min = arrayMax(minArgs);
6053                                 }
6054                         }
6055                 }
6056
6057                 /**
6058                  * Set the tick positions to round values and optionally extend the extremes
6059                  * to the nearest tick
6060                  */
6061                 function setTickPositions(secondPass) {
6062
6063                         var length,
6064                                 linkedParent,
6065                                 linkedParentExtremes,
6066                                 tickIntervalOption = options.tickInterval,
6067                                 tickPixelIntervalOption = options.tickPixelInterval;
6068
6069                         // linked axis gets the extremes from the parent axis
6070                         if (isLinked) {
6071                                 linkedParent = chart[isXAxis ? 'xAxis' : 'yAxis'][options.linkedTo];
6072                                 linkedParentExtremes = linkedParent.getExtremes();
6073                                 min = pick(linkedParentExtremes.min, linkedParentExtremes.dataMin);
6074                                 max = pick(linkedParentExtremes.max, linkedParentExtremes.dataMax);
6075                         } else { // initial min and max from the extreme data values
6076                                 min = pick(userMin, options.min, dataMin);
6077                                 max = pick(userMax, options.max, dataMax);
6078                         }
6079
6080                         if (isLog) {
6081                                 min = log2lin(min);
6082                                 max = log2lin(max);
6083                         }
6084
6085                         // handle zoomed range
6086                         if (range) {
6087                                 userMin = min = mathMax(min, max - range); // #618
6088                                 userMax = max;
6089                                 if (secondPass) {
6090                                         range = null;  // don't use it when running setExtremes
6091                                 }
6092                         }
6093
6094                         // adjust min and max for the minimum range
6095                         adjustForMinRange();
6096
6097                         // pad the values to get clear of the chart's edges
6098                         if (!categories && !usePercentage && !isLinked && defined(min) && defined(max)) {
6099                                 length = (max - min) || 1;
6100                                 if (!defined(options.min) && !defined(userMin) && minPadding && (dataMin < 0 || !ignoreMinPadding)) {
6101                                         min -= length * minPadding;
6102                                 }
6103                                 if (!defined(options.max) && !defined(userMax)  && maxPadding && (dataMax > 0 || !ignoreMaxPadding)) {
6104                                         max += length * maxPadding;
6105                                 }
6106                         }
6107
6108                         // get tickInterval
6109                         if (min === max || min === undefined || max === undefined) {
6110                                 tickInterval = 1;
6111                         } else if (isLinked && !tickIntervalOption &&
6112                                         tickPixelIntervalOption === linkedParent.options.tickPixelInterval) {
6113                                 tickInterval = linkedParent.tickInterval;
6114                         } else {
6115                                 tickInterval = pick(
6116                                         tickIntervalOption,
6117                                         categories ? // for categoried axis, 1 is default, for linear axis use tickPix
6118                                                 1 :
6119                                                 (max - min) * tickPixelIntervalOption / (axisLength || 1)
6120                                 );
6121                         }
6122
6123                         // Now we're finished detecting min and max, crop and group series data. This
6124                         // is in turn needed in order to find tick positions in ordinal axes. 
6125                         if (isXAxis && !secondPass) {
6126                                 each(axis.series, function (series) {
6127                                         series.processData(min !== oldMin || max !== oldMax);             
6128                                 });
6129                         }
6130
6131
6132                         // set the translation factor used in translate function
6133                         setAxisTranslation();
6134
6135                         // hook for ordinal axes. To do: merge with below
6136                         if (axis.beforeSetTickPositions) {
6137                                 axis.beforeSetTickPositions();
6138                         }
6139                         
6140                         // hook for extensions, used in Highstock ordinal axes
6141                         if (axis.postProcessTickInterval) {
6142                                 tickInterval = axis.postProcessTickInterval(tickInterval);                              
6143                         }
6144
6145                         // for linear axes, get magnitude and normalize the interval
6146                         if (!isDatetimeAxis) { // linear
6147                                 magnitude = math.pow(10, mathFloor(math.log(tickInterval) / math.LN10));
6148                                 if (!defined(options.tickInterval)) {
6149                                         tickInterval = normalizeTickInterval(tickInterval, null, magnitude, options);
6150                                 }
6151                         }
6152
6153                         // record the tick interval for linked axis
6154                         axis.tickInterval = tickInterval;
6155
6156                         // get minorTickInterval
6157                         minorTickInterval = options.minorTickInterval === 'auto' && tickInterval ?
6158                                         tickInterval / 5 : options.minorTickInterval;
6159
6160                         // find the tick positions
6161                         tickPositions = options.tickPositions || (tickPositioner && tickPositioner.apply(axis, [min, max]));
6162                         if (!tickPositions) {
6163                                 if (isDatetimeAxis) {
6164                                         tickPositions = (axis.getNonLinearTimeTicks || getTimeTicks)(
6165                                                 normalizeTimeTickInterval(tickInterval, options.units), 
6166                                                 min, 
6167                                                 max, 
6168                                                 options.startOfWeek,
6169                                                 axis.ordinalPositions, 
6170                                                 axis.closestPointRange,
6171                                                 true
6172                                         );
6173                                 } else {
6174                                         setLinearTickPositions();
6175                                 }
6176                         }
6177
6178                         // post process positions, used in ordinal axes in Highstock. 
6179                         // TODO: combine with getNonLinearTimeTicks
6180                         fireEvent(axis, 'afterSetTickPositions', {
6181                                 tickPositions: tickPositions
6182                         });
6183
6184
6185                         if (!isLinked) {
6186
6187                                 // reset min/max or remove extremes based on start/end on tick
6188                                 var roundedMin = tickPositions[0],
6189                                         roundedMax = tickPositions[tickPositions.length - 1];
6190
6191                                 if (options.startOnTick) {
6192                                         min = roundedMin;
6193                                 } else if (min > roundedMin) {
6194                                         tickPositions.shift();
6195                                 }
6196
6197                                 if (options.endOnTick) {
6198                                         max = roundedMax;
6199                                 } else if (max < roundedMax) {
6200                                         tickPositions.pop();
6201                                 }
6202
6203                                 // record the greatest number of ticks for multi axis
6204                                 if (!maxTicks) { // first call, or maxTicks have been reset after a zoom operation
6205                                         maxTicks = {
6206                                                 x: 0,
6207                                                 y: 0
6208                                         };
6209                                 }
6210
6211                                 if (!isDatetimeAxis && tickPositions.length > maxTicks[xOrY] && options.alignTicks !== false) {
6212                                         maxTicks[xOrY] = tickPositions.length;
6213                                 }
6214                         }
6215                 }
6216
6217                 /**
6218                  * When using multiple axes, adjust the number of ticks to match the highest
6219                  * number of ticks in that group
6220                  */
6221                 function adjustTickAmount() {
6222
6223                         if (maxTicks && maxTicks[xOrY] && !isDatetimeAxis && !categories && !isLinked && options.alignTicks !== false) { // only apply to linear scale
6224                                 var oldTickAmount = tickAmount,
6225                                         calculatedTickAmount = tickPositions.length;
6226
6227                                 // set the axis-level tickAmount to use below
6228                                 tickAmount = maxTicks[xOrY];
6229
6230                                 if (calculatedTickAmount < tickAmount) {
6231                                         while (tickPositions.length < tickAmount) {
6232                                                 tickPositions.push(correctFloat(
6233                                                         tickPositions[tickPositions.length - 1] + tickInterval
6234                                                 ));
6235                                         }
6236                                         transA *= (calculatedTickAmount - 1) / (tickAmount - 1);
6237                                         max = tickPositions[tickPositions.length - 1];
6238
6239                                 }
6240                                 if (defined(oldTickAmount) && tickAmount !== oldTickAmount) {
6241                                         axis.isDirty = true;
6242                                 }
6243                         }
6244
6245
6246                 }
6247
6248                 /**
6249                  * Set the scale based on data min and max, user set min and max or options
6250                  *
6251                  */
6252                 function setScale() {
6253                         var type,
6254                                 i,
6255                                 isDirtyData;
6256
6257                         oldMin = min;
6258                         oldMax = max;
6259                         oldAxisLength = axisLength;
6260
6261                         // set the new axisLength
6262                         axisLength = horiz ? axisWidth : axisHeight;
6263
6264                         // is there new data?
6265                         each(axis.series, function (series) {
6266                                 if (series.isDirtyData || series.isDirty ||
6267                                                 series.xAxis.isDirty) { // when x axis is dirty, we need new data extremes for y as well
6268                                         isDirtyData = true;
6269                                 }
6270                         });
6271
6272                         // do we really need to go through all this?
6273                         if (axisLength !== oldAxisLength || isDirtyData || isLinked ||
6274                                 userMin !== oldUserMin || userMax !== oldUserMax) {
6275
6276                                 // get data extremes if needed
6277                                 getSeriesExtremes();
6278
6279                                 // get fixed positions based on tickInterval
6280                                 setTickPositions();
6281
6282                                 // record old values to decide whether a rescale is necessary later on (#540)
6283                                 oldUserMin = userMin;
6284                                 oldUserMax = userMax;
6285
6286                                 // reset stacks
6287                                 if (!isXAxis) {
6288                                         for (type in stacks) {
6289                                                 for (i in stacks[type]) {
6290                                                         stacks[type][i].cum = stacks[type][i].total;
6291                                                 }
6292                                         }
6293                                 }
6294
6295                                 // Mark as dirty if it is not already set to dirty and extremes have changed. #595.
6296                                 if (!axis.isDirty) {
6297                                         axis.isDirty = chart.isDirtyBox || min !== oldMin || max !== oldMax;
6298                                 }
6299                         }
6300                 }
6301
6302                 /**
6303                  * Set the extremes and optionally redraw
6304                  * @param {Number} newMin
6305                  * @param {Number} newMax
6306                  * @param {Boolean} redraw
6307                  * @param {Boolean|Object} animation Whether to apply animation, and optionally animation
6308                  *    configuration
6309                  *
6310                  */
6311                 function setExtremes(newMin, newMax, redraw, animation) {
6312
6313                         redraw = pick(redraw, true); // defaults to true
6314
6315                         fireEvent(axis, 'setExtremes', { // fire an event to enable syncing of multiple charts
6316                                 min: newMin,
6317                                 max: newMax
6318                         }, function () { // the default event handler
6319
6320                                 userMin = newMin;
6321                                 userMax = newMax;
6322                                 
6323                                 // redraw
6324                                 if (redraw) {
6325                                         chart.redraw(animation);
6326                                 }
6327                         });
6328                 }
6329                 
6330                 /**
6331                  * Update translation information
6332                  */
6333                 setAxisTranslation = function () {
6334                         var range = max - min,
6335                                 pointRange = 0,
6336                                 closestPointRange,
6337                                 seriesClosestPointRange;
6338                         
6339                         // adjust translation for padding
6340                         if (isXAxis) {
6341                                 each(axis.series, function (series) {
6342                                         pointRange = mathMax(pointRange, series.pointRange);
6343                                         seriesClosestPointRange = series.closestPointRange;
6344                                         if (!series.noSharedTooltip && defined(seriesClosestPointRange)) {
6345                                                 closestPointRange = defined(closestPointRange) ?
6346                                                         mathMin(closestPointRange, seriesClosestPointRange) :
6347                                                         seriesClosestPointRange;
6348                                         }
6349                                 });
6350                                 // pointRange means the width reserved for each point, like in a column chart
6351                                 axis.pointRange = pointRange;
6352
6353                                 // closestPointRange means the closest distance between points. In columns
6354                                 // it is mostly equal to pointRange, but in lines pointRange is 0 while closestPointRange
6355                                 // is some other value
6356                                 axis.closestPointRange = closestPointRange;
6357                         }
6358
6359                         // secondary values
6360                         oldTransA = transA;
6361                         axis.translationSlope = transA = axisLength / ((range + pointRange) || 1);
6362                         transB = horiz ? axisLeft : axisBottom; // translation addend
6363                         minPixelPadding = transA * (pointRange / 2);
6364                 };
6365
6366                 /**
6367                  * Update the axis metrics
6368                  */
6369                 function setAxisSize() {
6370
6371                         var offsetLeft = options.offsetLeft || 0,
6372                                 offsetRight = options.offsetRight || 0;
6373
6374                         // basic values
6375                         axisLeft = pick(options.left, plotLeft + offsetLeft);
6376                         axisTop = pick(options.top, plotTop);
6377                         axisWidth = pick(options.width, plotWidth - offsetLeft + offsetRight);
6378                         axisHeight = pick(options.height, plotHeight);
6379                         axisBottom = chartHeight - axisHeight - axisTop;
6380                         axisRight = chartWidth - axisWidth - axisLeft;
6381                         axisLength = horiz ? axisWidth : axisHeight;
6382
6383                         // expose to use in Series object and navigator
6384                         axis.left = axisLeft;
6385                         axis.top = axisTop;
6386                         axis.len = axisLength;
6387
6388                 }
6389
6390                 /**
6391                  * Get the actual axis extremes
6392                  */
6393                 function getExtremes() {
6394                         return {
6395                                 min: min,
6396                                 max: max,
6397                                 dataMin: dataMin,
6398                                 dataMax: dataMax,
6399                                 userMin: userMin,
6400                                 userMax: userMax
6401                         };
6402                 }
6403
6404                 /**
6405                  * Get the zero plane either based on zero or on the min or max value.
6406                  * Used in bar and area plots
6407                  */
6408                 function getThreshold(threshold) {
6409                         if (min > threshold || threshold === null) {
6410                                 threshold = min;
6411                         } else if (max < threshold) {
6412                                 threshold = max;
6413                         }
6414
6415                         return translate(threshold, 0, 1);
6416                 }
6417
6418                 /**
6419                  * Add a plot band or plot line after render time
6420                  *
6421                  * @param options {Object} The plotBand or plotLine configuration object
6422                  */
6423                 function addPlotBandOrLine(options) {
6424                         var obj = new PlotLineOrBand(options).render();
6425                         plotLinesAndBands.push(obj);
6426                         return obj;
6427                 }
6428
6429                 /**
6430                  * Render the tick labels to a preliminary position to get their sizes
6431                  */
6432                 function getOffset() {
6433
6434                         var hasData = axis.series.length && defined(min) && defined(max),
6435                                 showAxis = hasData || pick(options.showEmpty, true),
6436                                 titleOffset = 0,
6437                                 titleMargin = 0,
6438                                 axisTitleOptions = options.title,
6439                                 labelOptions = options.labels,
6440                                 directionFactor = [-1, 1, 1, -1][side],
6441                                 n;
6442
6443                         if (!axisGroup) {
6444                                 axisGroup = renderer.g('axis')
6445                                         .attr({ zIndex: 7 })
6446                                         .add();
6447                                 gridGroup = renderer.g('grid')
6448                                         .attr({ zIndex: options.gridZIndex || 1 })
6449                                         .add();
6450                         }
6451
6452                         labelOffset = 0; // reset
6453
6454                         if (hasData || isLinked) {
6455                                 each(tickPositions, function (pos) {
6456                                         if (!ticks[pos]) {
6457                                                 ticks[pos] = new Tick(pos);
6458                                         } else {
6459                                                 ticks[pos].addLabel(); // update labels depending on tick interval
6460                                         }
6461
6462                                 });
6463
6464                                 each(tickPositions, function (pos) {
6465                                         // left side must be align: right and right side must have align: left for labels
6466                                         if (side === 0 || side === 2 || { 1: 'left', 3: 'right' }[side] === labelOptions.align) {
6467
6468                                                 // get the highest offset
6469                                                 labelOffset = mathMax(
6470                                                         ticks[pos].getLabelSize(),
6471                                                         labelOffset
6472                                                 );
6473                                         }
6474
6475                                 });
6476
6477                                 if (staggerLines) {
6478                                         labelOffset += (staggerLines - 1) * 16;
6479                                 }
6480
6481                         } else { // doesn't have data
6482                                 for (n in ticks) {
6483                                         ticks[n].destroy();
6484                                         delete ticks[n];
6485                                 }
6486                         }
6487
6488                         if (axisTitleOptions && axisTitleOptions.text) {
6489                                 if (!axisTitle) {
6490                                         axisTitle = axis.axisTitle = renderer.text(
6491                                                 axisTitleOptions.text,
6492                                                 0,
6493                                                 0,
6494                                                 axisTitleOptions.useHTML
6495                                         )
6496                                         .attr({
6497                                                 zIndex: 7,
6498                                                 rotation: axisTitleOptions.rotation || 0,
6499                                                 align:
6500                                                         axisTitleOptions.textAlign ||
6501                                                         { low: 'left', middle: 'center', high: 'right' }[axisTitleOptions.align]
6502                                         })
6503                                         .css(axisTitleOptions.style)
6504                                         .add();
6505                                         axisTitle.isNew = true;
6506                                 }
6507
6508                                 if (showAxis) {
6509                                         titleOffset = axisTitle.getBBox()[horiz ? 'height' : 'width'];
6510                                         titleMargin = pick(axisTitleOptions.margin, horiz ? 5 : 10);
6511                                 }
6512
6513                                 // hide or show the title depending on whether showEmpty is set
6514                                 axisTitle[showAxis ? 'show' : 'hide']();
6515
6516
6517                         }
6518
6519                         // handle automatic or user set offset
6520                         offset = directionFactor * pick(options.offset, axisOffset[side]);
6521
6522                         axisTitleMargin =
6523                                 pick(axisTitleOptions.offset,
6524                                         labelOffset + titleMargin +
6525                                         (side !== 2 && labelOffset && directionFactor * options.labels[horiz ? 'y' : 'x'])
6526                                 );
6527
6528                         axisOffset[side] = mathMax(
6529                                 axisOffset[side],
6530                                 axisTitleMargin + titleOffset + directionFactor * offset
6531                         );
6532
6533                 }
6534
6535                 /**
6536                  * Render the axis
6537                  */
6538                 function render() {
6539                         var axisTitleOptions = options.title,
6540                                 stackLabelOptions = options.stackLabels,
6541                                 alternateGridColor = options.alternateGridColor,
6542                                 lineWidth = options.lineWidth,
6543                                 lineLeft,
6544                                 lineTop,
6545                                 linePath,
6546                                 hasRendered = chart.hasRendered,
6547                                 slideInTicks = hasRendered && defined(oldMin) && !isNaN(oldMin),
6548                                 hasData = axis.series.length && defined(min) && defined(max),
6549                                 showAxis = hasData || pick(options.showEmpty, true);
6550
6551                         // If the series has data draw the ticks. Else only the line and title
6552                         if (hasData || isLinked) {
6553
6554                                 // minor ticks
6555                                 if (minorTickInterval && !categories) {
6556                                         var pos = min + (tickPositions[0] - min) % minorTickInterval;
6557                                         for (; pos <= max; pos += minorTickInterval) {
6558                                                 if (!minorTicks[pos]) {
6559                                                         minorTicks[pos] = new Tick(pos, 'minor');
6560                                                 }
6561
6562                                                 // render new ticks in old position
6563                                                 if (slideInTicks && minorTicks[pos].isNew) {
6564                                                         minorTicks[pos].render(null, true);
6565                                                 }
6566
6567
6568                                                 minorTicks[pos].isActive = true;
6569                                                 minorTicks[pos].render();
6570                                         }
6571                                 }
6572
6573                                 // major ticks
6574                                 each(tickPositions, function (pos, i) {
6575                                         // linked axes need an extra check to find out if
6576                                         if (!isLinked || (pos >= min && pos <= max)) {
6577
6578                                                 if (!ticks[pos]) {
6579                                                         ticks[pos] = new Tick(pos);
6580                                                 }
6581
6582                                                 // render new ticks in old position
6583                                                 if (slideInTicks && ticks[pos].isNew) {
6584                                                         ticks[pos].render(i, true);
6585                                                 }
6586
6587                                                 ticks[pos].isActive = true;
6588                                                 ticks[pos].render(i);
6589                                         }
6590
6591                                 });
6592
6593                                 // alternate grid color
6594                                 if (alternateGridColor) {
6595                                         each(tickPositions, function (pos, i) {
6596                                                 if (i % 2 === 0 && pos < max) {
6597                                                         if (!alternateBands[pos]) {
6598                                                                 alternateBands[pos] = new PlotLineOrBand();
6599                                                         }
6600                                                         alternateBands[pos].options = {
6601                                                                 from: pos,
6602                                                                 to: tickPositions[i + 1] !== UNDEFINED ? tickPositions[i + 1] : max,
6603                                                                 color: alternateGridColor
6604                                                         };
6605                                                         alternateBands[pos].render();
6606                                                         alternateBands[pos].isActive = true;
6607                                                 }
6608                                         });
6609                                 }
6610
6611                                 // custom plot lines and bands
6612                                 if (!axis._addedPlotLB) { // only first time
6613                                         each((options.plotLines || []).concat(options.plotBands || []), function (plotLineOptions) {
6614                                                 //plotLinesAndBands.push(new PlotLineOrBand(plotLineOptions).render());
6615                                                 addPlotBandOrLine(plotLineOptions);
6616                                         });
6617                                         axis._addedPlotLB = true;
6618                                 }
6619
6620
6621
6622                         } // end if hasData
6623
6624                         // remove inactive ticks
6625                         each([ticks, minorTicks, alternateBands], function (coll) {
6626                                 var pos;
6627                                 for (pos in coll) {
6628                                         if (!coll[pos].isActive) {
6629                                                 coll[pos].destroy();
6630                                                 delete coll[pos];
6631                                         } else {
6632                                                 coll[pos].isActive = false; // reset
6633                                         }
6634                                 }
6635                         });
6636
6637
6638
6639
6640                         // Static items. As the axis group is cleared on subsequent calls
6641                         // to render, these items are added outside the group.
6642                         // axis line
6643                         if (lineWidth) {
6644                                 lineLeft = axisLeft + (opposite ? axisWidth : 0) + offset;
6645                                 lineTop = chartHeight - axisBottom - (opposite ? axisHeight : 0) + offset;
6646
6647                                 linePath = renderer.crispLine([
6648                                                 M,
6649                                                 horiz ?
6650                                                         axisLeft :
6651                                                         lineLeft,
6652                                                 horiz ?
6653                                                         lineTop :
6654                                                         axisTop,
6655                                                 L,
6656                                                 horiz ?
6657                                                         chartWidth - axisRight :
6658                                                         lineLeft,
6659                                                 horiz ?
6660                                                         lineTop :
6661                                                         chartHeight - axisBottom
6662                                         ], lineWidth);
6663                                 if (!axisLine) {
6664                                         axisLine = renderer.path(linePath)
6665                                                 .attr({
6666                                                         stroke: options.lineColor,
6667                                                         'stroke-width': lineWidth,
6668                                                         zIndex: 7
6669                                                 })
6670                                                 .add();
6671                                 } else {
6672                                         axisLine.animate({ d: linePath });
6673                                 }
6674
6675                                 // show or hide the line depending on options.showEmpty
6676                                 axisLine[showAxis ? 'show' : 'hide']();
6677
6678                         }
6679
6680                         if (axisTitle && showAxis) {
6681                                 // compute anchor points for each of the title align options
6682                                 var margin = horiz ? axisLeft : axisTop,
6683                                         fontSize = pInt(axisTitleOptions.style.fontSize || 12),
6684                                 // the position in the length direction of the axis
6685                                 alongAxis = {
6686                                         low: margin + (horiz ? 0 : axisLength),
6687                                         middle: margin + axisLength / 2,
6688                                         high: margin + (horiz ? axisLength : 0)
6689                                 }[axisTitleOptions.align],
6690
6691                                 // the position in the perpendicular direction of the axis
6692                                 offAxis = (horiz ? axisTop + axisHeight : axisLeft) +
6693                                         (horiz ? 1 : -1) * // horizontal axis reverses the margin
6694                                         (opposite ? -1 : 1) * // so does opposite axes
6695                                         axisTitleMargin +
6696                                         (side === 2 ? fontSize : 0);
6697
6698                                 axisTitle[axisTitle.isNew ? 'attr' : 'animate']({
6699                                         x: horiz ?
6700                                                 alongAxis :
6701                                                 offAxis + (opposite ? axisWidth : 0) + offset +
6702                                                         (axisTitleOptions.x || 0), // x
6703                                         y: horiz ?
6704                                                 offAxis - (opposite ? axisHeight : 0) + offset :
6705                                                 alongAxis + (axisTitleOptions.y || 0) // y
6706                                 });
6707                                 axisTitle.isNew = false;
6708                         }
6709
6710                         // Stacked totals:
6711                         if (stackLabelOptions && stackLabelOptions.enabled) {
6712                                 var stackKey, oneStack, stackCategory,
6713                                         stackTotalGroup = axis.stackTotalGroup;
6714
6715                                 // Create a separate group for the stack total labels
6716                                 if (!stackTotalGroup) {
6717                                         axis.stackTotalGroup = stackTotalGroup =
6718                                                 renderer.g('stack-labels')
6719                                                         .attr({
6720                                                                 visibility: VISIBLE,
6721                                                                 zIndex: 6
6722                                                         })
6723                                                         .translate(plotLeft, plotTop)
6724                                                         .add();
6725                                 }
6726
6727                                 // Render each stack total
6728                                 for (stackKey in stacks) {
6729                                         oneStack = stacks[stackKey];
6730                                         for (stackCategory in oneStack) {
6731                                                 oneStack[stackCategory].render(stackTotalGroup);
6732                                         }
6733                                 }
6734                         }
6735                         // End stacked totals
6736
6737                         axis.isDirty = false;
6738                 }
6739
6740                 /**
6741                  * Remove a plot band or plot line from the chart by id
6742                  * @param {Object} id
6743                  */
6744                 function removePlotBandOrLine(id) {
6745                         var i = plotLinesAndBands.length;
6746                         while (i--) {
6747                                 if (plotLinesAndBands[i].id === id) {
6748                                         plotLinesAndBands[i].destroy();
6749                                 }
6750                         }
6751                 }
6752
6753                 /**
6754                  * Redraw the axis to reflect changes in the data or axis extremes
6755                  */
6756                 function redraw() {
6757
6758                         // hide tooltip and hover states
6759                         if (tracker.resetTracker) {
6760                                 tracker.resetTracker();
6761                         }
6762
6763                         // render the axis
6764                         render();
6765
6766                         // move plot lines and bands
6767                         each(plotLinesAndBands, function (plotLine) {
6768                                 plotLine.render();
6769                         });
6770
6771                         // mark associated series as dirty and ready for redraw
6772                         each(axis.series, function (series) {
6773                                 series.isDirty = true;
6774                         });
6775
6776                 }
6777
6778                 /**
6779                  * Set new axis categories and optionally redraw
6780                  * @param {Array} newCategories
6781                  * @param {Boolean} doRedraw
6782                  */
6783                 function setCategories(newCategories, doRedraw) {
6784                                 // set the categories
6785                                 axis.categories = userOptions.categories = categories = newCategories;
6786
6787                                 // force reindexing tooltips
6788                                 each(axis.series, function (series) {
6789                                         series.translate();
6790                                         series.setTooltipPoints(true);
6791                                 });
6792
6793
6794                                 // optionally redraw
6795                                 axis.isDirty = true;
6796
6797                                 if (pick(doRedraw, true)) {
6798                                         chart.redraw();
6799                                 }
6800                 }
6801
6802                 /**
6803                  * Destroys an Axis instance.
6804                  */
6805                 function destroy() {
6806                         var stackKey;
6807
6808                         // Remove the events
6809                         removeEvent(axis);
6810
6811                         // Destroy each stack total
6812                         for (stackKey in stacks) {
6813                                 destroyObjectProperties(stacks[stackKey]);
6814
6815                                 stacks[stackKey] = null;
6816                         }
6817
6818                         // Destroy stack total group
6819                         if (axis.stackTotalGroup) {
6820                                 axis.stackTotalGroup = axis.stackTotalGroup.destroy();
6821                         }
6822
6823                         // Destroy collections
6824                         each([ticks, minorTicks, alternateBands, plotLinesAndBands], function (coll) {
6825                                 destroyObjectProperties(coll);
6826                         });
6827
6828                         // Destroy local variables
6829                         each([axisLine, axisGroup, gridGroup, axisTitle], function (obj) {
6830                                 if (obj) {
6831                                         obj.destroy();
6832                                 }
6833                         });
6834                         axisLine = axisGroup = gridGroup = axisTitle = null;
6835                 }
6836
6837
6838                 // Run Axis
6839
6840                 // Register
6841                 axes.push(axis);
6842                 chart[isXAxis ? 'xAxis' : 'yAxis'].push(axis);
6843
6844                 // inverted charts have reversed xAxes as default
6845                 if (inverted && isXAxis && reversed === UNDEFINED) {
6846                         reversed = true;
6847                 }
6848
6849
6850                 // expose some variables
6851                 extend(axis, {
6852                         addPlotBand: addPlotBandOrLine,
6853                         addPlotLine: addPlotBandOrLine,
6854                         adjustTickAmount: adjustTickAmount,
6855                         categories: categories,
6856                         getExtremes: getExtremes,
6857                         getPlotLinePath: getPlotLinePath,
6858                         getThreshold: getThreshold,
6859                         isXAxis: isXAxis,
6860                         options: options,
6861                         plotLinesAndBands: plotLinesAndBands,
6862                         getOffset: getOffset,
6863                         render: render,
6864                         setAxisSize: setAxisSize,
6865                         setAxisTranslation: setAxisTranslation,
6866                         setCategories: setCategories,
6867                         setExtremes: setExtremes,
6868                         setScale: setScale,
6869                         setTickPositions: setTickPositions,
6870                         translate: translate,
6871                         redraw: redraw,
6872                         removePlotBand: removePlotBandOrLine,
6873                         removePlotLine: removePlotBandOrLine,
6874                         reversed: reversed,
6875                         series: [], // populated by Series
6876                         stacks: stacks,
6877                         destroy: destroy
6878                 });
6879
6880                 // register event listeners
6881                 for (eventType in events) {
6882                         addEvent(axis, eventType, events[eventType]);
6883                 }
6884
6885                 // extend logarithmic axis
6886                 if (isLog) {
6887                         axis.val2lin = log2lin;
6888                         axis.lin2val = lin2log;
6889                 }
6890
6891         } // end Axis
6892
6893
6894         /**
6895          * The tooltip object
6896          * @param {Object} options Tooltip options
6897          */
6898         function Tooltip(options) {
6899                 var currentSeries,
6900                         borderWidth = options.borderWidth,
6901                         crosshairsOptions = options.crosshairs,
6902                         crosshairs = [],
6903                         style = options.style,
6904                         shared = options.shared,
6905                         padding = pInt(style.padding),
6906                         tooltipIsHidden = true,
6907                         currentX = 0,
6908                         currentY = 0;
6909
6910                 // remove padding CSS and apply padding on box instead
6911                 style.padding = 0;
6912
6913                 // create the label
6914                 var label = renderer.label('', 0, 0)
6915                         .attr({
6916                                 padding: padding,
6917                                 fill: options.backgroundColor,
6918                                 'stroke-width': borderWidth,
6919                                 r: options.borderRadius,
6920                                 zIndex: 8
6921                         })
6922                         .css(style)
6923                         .hide()
6924                         .add()
6925                         .shadow(options.shadow);
6926
6927                 /**
6928                  * Destroy the tooltip and its elements.
6929                  */
6930                 function destroy() {
6931                         each(crosshairs, function (crosshair) {
6932                                 if (crosshair) {
6933                                         crosshair.destroy();
6934                                 }
6935                         });
6936
6937                         // Destroy and clear local variables
6938                         if (label) {
6939                                 label = label.destroy();
6940                         }
6941                 }
6942
6943                 /**
6944                  * In case no user defined formatter is given, this will be used
6945                  */
6946                 function defaultFormatter() {
6947                         var pThis = this,
6948                                 items = pThis.points || splat(pThis),
6949                                 series = items[0].series,
6950                                 s;
6951
6952                         // build the header
6953                         s = [series.tooltipHeaderFormatter(items[0].key)];
6954
6955                         // build the values
6956                         each(items, function (item) {
6957                                 series = item.series;
6958                                 s.push((series.tooltipFormatter && series.tooltipFormatter(item)) ||
6959                                         item.point.tooltipFormatter(series.tooltipOptions.pointFormat));
6960                         });
6961                         return s.join('');
6962                 }
6963
6964                 /**
6965                  * Provide a soft movement for the tooltip
6966                  *
6967                  * @param {Number} finalX
6968                  * @param {Number} finalY
6969                  */
6970                 function move(finalX, finalY) {
6971
6972                         // get intermediate values for animation
6973                         currentX = tooltipIsHidden ? finalX : (2 * currentX + finalX) / 3;
6974                         currentY = tooltipIsHidden ? finalY : (currentY + finalY) / 2;
6975
6976                         // move to the intermediate value
6977                         label.attr({ x: currentX, y: currentY });
6978
6979                         // run on next tick of the mouse tracker
6980                         if (mathAbs(finalX - currentX) > 1 || mathAbs(finalY - currentY) > 1) {
6981                                 tooltipTick = function () {
6982                                         move(finalX, finalY);
6983                                 };
6984                         } else {
6985                                 tooltipTick = null;
6986                         }
6987                 }
6988
6989                 /**
6990                  * Hide the tooltip
6991                  */
6992                 function hide() {
6993                         if (!tooltipIsHidden) {
6994                                 var hoverPoints = chart.hoverPoints;
6995
6996                                 label.hide();
6997
6998                                 // hide previous hoverPoints and set new
6999                                 if (hoverPoints) {
7000                                         each(hoverPoints, function (point) {
7001                                                 point.setState();
7002                                         });
7003                                 }
7004                                 chart.hoverPoints = null;
7005
7006
7007                                 tooltipIsHidden = true;
7008                         }
7009
7010                 }
7011
7012                 /**
7013                  * Hide the crosshairs
7014                  */
7015                 function hideCrosshairs() {
7016                         each(crosshairs, function (crosshair) {
7017                                 if (crosshair) {
7018                                         crosshair.hide();
7019                                 }
7020                         });
7021                 }
7022
7023                 /**
7024                  * Refresh the tooltip's text and position.
7025                  * @param {Object} point
7026                  *
7027                  */
7028                 function refresh(point) {
7029                         var x,
7030                                 y,
7031                                 show,
7032                                 plotX,
7033                                 plotY,
7034                                 textConfig = {},
7035                                 text,
7036                                 pointConfig = [],
7037                                 tooltipPos = point.tooltipPos,
7038                                 formatter = options.formatter || defaultFormatter,
7039                                 hoverPoints = chart.hoverPoints,
7040                                 placedTooltipPoint;
7041
7042                         // shared tooltip, array is sent over
7043                         if (shared && !(point.series && point.series.noSharedTooltip)) {
7044                                 plotY = 0;
7045
7046                                 // hide previous hoverPoints and set new
7047                                 if (hoverPoints) {
7048                                         each(hoverPoints, function (point) {
7049                                                 point.setState();
7050                                         });
7051                                 }
7052                                 chart.hoverPoints = point;
7053
7054                                 each(point, function (item) {
7055                                         item.setState(HOVER_STATE);
7056                                         plotY += item.plotY; // for average
7057
7058                                         pointConfig.push(item.getLabelConfig());
7059                                 });
7060
7061                                 plotX = point[0].plotX;
7062                                 plotY = mathRound(plotY) / point.length; // mathRound because Opera 10 has problems here
7063
7064                                 textConfig = {
7065                                         x: point[0].category
7066                                 };
7067                                 textConfig.points = pointConfig;
7068                                 point = point[0];
7069
7070                         // single point tooltip
7071                         } else {
7072                                 textConfig = point.getLabelConfig();
7073                         }
7074                         text = formatter.call(textConfig);
7075
7076                         // register the current series
7077                         currentSeries = point.series;
7078
7079                         // get the reference point coordinates (pie charts use tooltipPos)
7080                         plotX = pick(plotX, point.plotX);
7081                         plotY = pick(plotY, point.plotY);
7082
7083                         x = mathRound(tooltipPos ? tooltipPos[0] : (inverted ? plotWidth - plotY : plotX));
7084                         y = mathRound(tooltipPos ? tooltipPos[1] : (inverted ? plotHeight - plotX : plotY));
7085
7086
7087                         // For line type series, hide tooltip if the point falls outside the plot
7088                         show = shared || !currentSeries.isCartesian || currentSeries.tooltipOutsidePlot || isInsidePlot(x, y);
7089
7090                         // update the inner HTML
7091                         if (text === false || !show) {
7092                                 hide();
7093                         } else {
7094
7095                                 // show it
7096                                 if (tooltipIsHidden) {
7097                                         label.show();
7098                                         tooltipIsHidden = false;
7099                                 }
7100
7101                                 // update text
7102                                 label.attr({
7103                                         text: text
7104                                 });
7105
7106                                 // set the stroke color of the box
7107                                 label.attr({
7108                                         stroke: options.borderColor || point.color || currentSeries.color || '#606060'
7109                                 });
7110
7111                                 placedTooltipPoint = placeBox(
7112                                         label.width,
7113                                         label.height,
7114                                         plotLeft,
7115                                         plotTop,
7116                                         plotWidth,
7117                                         plotHeight,
7118                                         {x: x, y: y},
7119                                         pick(options.distance, 12),
7120                                         inverted
7121                                 );
7122
7123                                 // do the move
7124                                 move(mathRound(placedTooltipPoint.x), mathRound(placedTooltipPoint.y));
7125                         }
7126
7127
7128                         // crosshairs
7129                         if (crosshairsOptions) {
7130                                 crosshairsOptions = splat(crosshairsOptions); // [x, y]
7131
7132                                 var path,
7133                                         i = crosshairsOptions.length,
7134                                         attribs,
7135                                         axis;
7136
7137                                 while (i--) {
7138                                         axis = point.series[i ? 'yAxis' : 'xAxis'];
7139                                         if (crosshairsOptions[i] && axis) {
7140                                                 path = axis
7141                                                         .getPlotLinePath(point[i ? 'y' : 'x'], 1);
7142                                                 if (crosshairs[i]) {
7143                                                         crosshairs[i].attr({ d: path, visibility: VISIBLE });
7144
7145                                                 } else {
7146                                                         attribs = {
7147                                                                 'stroke-width': crosshairsOptions[i].width || 1,
7148                                                                 stroke: crosshairsOptions[i].color || '#C0C0C0',
7149                                                                 zIndex: crosshairsOptions[i].zIndex || 2
7150                                                         };
7151                                                         if (crosshairsOptions[i].dashStyle) {
7152                                                                 attribs.dashstyle = crosshairsOptions[i].dashStyle;
7153                                                         }
7154                                                         crosshairs[i] = renderer.path(path)
7155                                                                 .attr(attribs)
7156                                                                 .add();
7157                                                 }
7158                                         }
7159                                 }
7160                         }
7161                 }
7162
7163
7164
7165                 // public members
7166                 return {
7167                         shared: shared,
7168                         refresh: refresh,
7169                         hide: hide,
7170                         hideCrosshairs: hideCrosshairs,
7171                         destroy: destroy
7172                 };
7173         }
7174
7175         /**
7176          * The mouse tracker object
7177          * @param {Object} options
7178          */
7179         function MouseTracker(options) {
7180
7181
7182                 var mouseDownX,
7183                         mouseDownY,
7184                         hasDragged,
7185                         selectionMarker,
7186                         zoomType = optionsChart.zoomType,
7187                         zoomX = /x/.test(zoomType),
7188                         zoomY = /y/.test(zoomType),
7189                         zoomHor = (zoomX && !inverted) || (zoomY && inverted),
7190                         zoomVert = (zoomY && !inverted) || (zoomX && inverted);
7191
7192                 /**
7193                  * Add crossbrowser support for chartX and chartY
7194                  * @param {Object} e The event object in standard browsers
7195                  */
7196                 function normalizeMouseEvent(e) {
7197                         var ePos,
7198                                 chartPosLeft,
7199                                 chartPosTop,
7200                                 chartX,
7201                                 chartY;
7202
7203                         // common IE normalizing
7204                         e = e || win.event;
7205                         if (!e.target) {
7206                                 e.target = e.srcElement;
7207                         }
7208
7209                         // jQuery only copies over some properties. IE needs e.x and iOS needs touches.
7210                         if (e.originalEvent) {
7211                                 e = e.originalEvent;
7212                         }
7213
7214                         // The same for MooTools. It renames e.pageX to e.page.x. #445.
7215                         if (e.event) {
7216                                 e = e.event;
7217                         }
7218
7219                         // iOS
7220                         ePos = e.touches ? e.touches.item(0) : e;
7221
7222                         // get mouse position
7223                         chartPosition = offset(container);
7224                         chartPosLeft = chartPosition.left;
7225                         chartPosTop = chartPosition.top;
7226
7227                         // chartX and chartY
7228                         if (isIE) { // IE including IE9 that has pageX but in a different meaning
7229                                 chartX = e.x;
7230                                 chartY = e.y;
7231                         } else {
7232                                 chartX = ePos.pageX - chartPosLeft;
7233                                 chartY = ePos.pageY - chartPosTop;
7234                         }
7235
7236                         return extend(e, {
7237                                 chartX: mathRound(chartX),
7238                                 chartY: mathRound(chartY)
7239                         });
7240                 }
7241
7242                 /**
7243                  * Get the click position in terms of axis values.
7244                  *
7245                  * @param {Object} e A mouse event
7246                  */
7247                 function getMouseCoordinates(e) {
7248                         var coordinates = {
7249                                 xAxis: [],
7250                                 yAxis: []
7251                         };
7252                         each(axes, function (axis) {
7253                                 var translate = axis.translate,
7254                                         isXAxis = axis.isXAxis,
7255                                         isHorizontal = inverted ? !isXAxis : isXAxis;
7256
7257                                 coordinates[isXAxis ? 'xAxis' : 'yAxis'].push({
7258                                         axis: axis,
7259                                         value: translate(
7260                                                 isHorizontal ?
7261                                                         e.chartX - plotLeft  :
7262                                                         plotHeight - e.chartY + plotTop,
7263                                                 true
7264                                         )
7265                                 });
7266                         });
7267                         return coordinates;
7268                 }
7269
7270                 /**
7271                  * With line type charts with a single tracker, get the point closest to the mouse
7272                  */
7273                 function onmousemove(e) {
7274                         var point,
7275                                 points,
7276                                 hoverPoint = chart.hoverPoint,
7277                                 hoverSeries = chart.hoverSeries,
7278                                 i,
7279                                 j,
7280                                 distance = chartWidth,
7281                                 index = inverted ? e.chartY : e.chartX - plotLeft; // wtf?
7282
7283                         // shared tooltip
7284                         if (tooltip && options.shared && !(hoverSeries && hoverSeries.noSharedTooltip)) {
7285                                 points = [];
7286
7287                                 // loop over all series and find the ones with points closest to the mouse
7288                                 i = series.length;
7289                                 for (j = 0; j < i; j++) {
7290                                         if (series[j].visible &&
7291                                                         series[j].options.enableMouseTracking !== false &&
7292                                                         !series[j].noSharedTooltip && series[j].tooltipPoints.length) {
7293                                                 point = series[j].tooltipPoints[index];
7294                                                 point._dist = mathAbs(index - point.plotX);
7295                                                 distance = mathMin(distance, point._dist);
7296                                                 points.push(point);
7297                                         }
7298                                 }
7299                                 // remove furthest points
7300                                 i = points.length;
7301                                 while (i--) {
7302                                         if (points[i]._dist > distance) {
7303                                                 points.splice(i, 1);
7304                                         }
7305                                 }
7306                                 // refresh the tooltip if necessary
7307                                 if (points.length && (points[0].plotX !== hoverX)) {
7308                                         tooltip.refresh(points);
7309                                         hoverX = points[0].plotX;
7310                                 }
7311                         }
7312
7313                         // separate tooltip and general mouse events
7314                         if (hoverSeries && hoverSeries.tracker) { // only use for line-type series with common tracker
7315
7316                                 // get the point
7317                                 point = hoverSeries.tooltipPoints[index];
7318
7319                                 // a new point is hovered, refresh the tooltip
7320                                 if (point && point !== hoverPoint) {
7321
7322                                         // trigger the events
7323                                         point.onMouseOver();
7324
7325                                 }
7326                         }
7327                 }
7328
7329
7330
7331                 /**
7332                  * Reset the tracking by hiding the tooltip, the hover series state and the hover point
7333                  */
7334                 function resetTracker() {
7335                         var hoverSeries = chart.hoverSeries,
7336                                 hoverPoint = chart.hoverPoint;
7337
7338                         if (hoverPoint) {
7339                                 hoverPoint.onMouseOut();
7340                         }
7341
7342                         if (hoverSeries) {
7343                                 hoverSeries.onMouseOut();
7344                         }
7345
7346                         if (tooltip) {
7347                                 tooltip.hide();
7348                                 tooltip.hideCrosshairs();
7349                         }
7350
7351                         hoverX = null;
7352                 }
7353
7354                 /**
7355                  * Mouse up or outside the plot area
7356                  */
7357                 function drop() {
7358                         if (selectionMarker) {
7359                                 var selectionData = {
7360                                                 xAxis: [],
7361                                                 yAxis: []
7362                                         },
7363                                         selectionBox = selectionMarker.getBBox(),
7364                                         selectionLeft = selectionBox.x - plotLeft,
7365                                         selectionTop = selectionBox.y - plotTop;
7366
7367
7368                                 // a selection has been made
7369                                 if (hasDragged) {
7370
7371                                         // record each axis' min and max
7372                                         each(axes, function (axis) {
7373                                                 if (axis.options.zoomEnabled !== false) {
7374                                                         var translate = axis.translate,
7375                                                                 isXAxis = axis.isXAxis,
7376                                                                 isHorizontal = inverted ? !isXAxis : isXAxis,
7377                                                                 selectionMin = translate(
7378                                                                         isHorizontal ?
7379                                                                                 selectionLeft :
7380                                                                                 plotHeight - selectionTop - selectionBox.height,
7381                                                                         true,
7382                                                                         0,
7383                                                                         0,
7384                                                                         1
7385                                                                 ),
7386                                                                 selectionMax = translate(
7387                                                                         isHorizontal ?
7388                                                                                 selectionLeft + selectionBox.width :
7389                                                                                 plotHeight - selectionTop,
7390                                                                         true,
7391                                                                         0,
7392                                                                         0,
7393                                                                         1
7394                                                                 );
7395
7396                                                                 selectionData[isXAxis ? 'xAxis' : 'yAxis'].push({
7397                                                                         axis: axis,
7398                                                                         min: mathMin(selectionMin, selectionMax), // for reversed axes,
7399                                                                         max: mathMax(selectionMin, selectionMax)
7400                                                                 });
7401                                                 }
7402                                         });
7403                                         fireEvent(chart, 'selection', selectionData, zoom);
7404
7405                                 }
7406                                 selectionMarker = selectionMarker.destroy();
7407                         }
7408
7409                         css(container, { cursor: 'auto' });
7410
7411                         chart.mouseIsDown = mouseIsDown = hasDragged = false;
7412                         removeEvent(doc, hasTouch ? 'touchend' : 'mouseup', drop);
7413
7414                 }
7415
7416                 /**
7417                  * Special handler for mouse move that will hide the tooltip when the mouse leaves the plotarea.
7418                  */
7419                 function hideTooltipOnMouseMove(e) {
7420                         var pageX = defined(e.pageX) ? e.pageX : e.page.x, // In mootools the event is wrapped and the page x/y position is named e.page.x
7421                                 pageY = defined(e.pageX) ? e.pageY : e.page.y; // Ref: http://mootools.net/docs/core/Types/DOMEvent
7422
7423                         if (chartPosition &&
7424                                         !isInsidePlot(pageX - chartPosition.left - plotLeft,
7425                                                 pageY - chartPosition.top - plotTop)) {
7426                                 resetTracker();
7427                         }
7428                 }
7429
7430                 /**
7431                  * When mouse leaves the container, hide the tooltip.
7432                  */
7433                 function hideTooltipOnMouseLeave() {
7434                         resetTracker();
7435                         chartPosition = null; // also reset the chart position, used in #149 fix
7436                 }
7437
7438                 /**
7439                  * Set the JS events on the container element
7440                  */
7441                 function setDOMEvents() {
7442                         var lastWasOutsidePlot = true;
7443                         /*
7444                          * Record the starting position of a dragoperation
7445                          */
7446                         container.onmousedown = function (e) {
7447                                 e = normalizeMouseEvent(e);
7448
7449                                 // issue #295, dragging not always working in Firefox
7450                                 if (!hasTouch && e.preventDefault) {
7451                                         e.preventDefault();
7452                                 }
7453
7454                                 // record the start position
7455                                 chart.mouseIsDown = mouseIsDown = true;
7456                                 chart.mouseDownX = mouseDownX = e.chartX;
7457                                 mouseDownY = e.chartY;
7458
7459                                 addEvent(doc, hasTouch ? 'touchend' : 'mouseup', drop);
7460                         };
7461
7462                         // The mousemove, touchmove and touchstart event handler
7463                         var mouseMove = function (e) {
7464
7465                                 // let the system handle multitouch operations like two finger scroll
7466                                 // and pinching
7467                                 if (e && e.touches && e.touches.length > 1) {
7468                                         return;
7469                                 }
7470
7471                                 // normalize
7472                                 e = normalizeMouseEvent(e);
7473                                 if (!hasTouch) { // not for touch devices
7474                                         e.returnValue = false;
7475                                 }
7476
7477                                 var chartX = e.chartX,
7478                                         chartY = e.chartY,
7479                                         isOutsidePlot = !isInsidePlot(chartX - plotLeft, chartY - plotTop);
7480
7481                                 // on touch devices, only trigger click if a handler is defined
7482                                 if (hasTouch && e.type === 'touchstart') {
7483                                         if (attr(e.target, 'isTracker')) {
7484                                                 if (!chart.runTrackerClick) {
7485                                                         e.preventDefault();
7486                                                 }
7487                                         } else if (!runChartClick && !isOutsidePlot) {
7488                                                 e.preventDefault();
7489                                         }
7490                                 }
7491
7492                                 // cancel on mouse outside
7493                                 if (isOutsidePlot) {
7494
7495                                         /*if (!lastWasOutsidePlot) {
7496                                                 // reset the tracker
7497                                                 resetTracker();
7498                                         }*/
7499
7500                                         // drop the selection if any and reset mouseIsDown and hasDragged
7501                                         //drop();
7502                                         if (chartX < plotLeft) {
7503                                                 chartX = plotLeft;
7504                                         } else if (chartX > plotLeft + plotWidth) {
7505                                                 chartX = plotLeft + plotWidth;
7506                                         }
7507
7508                                         if (chartY < plotTop) {
7509                                                 chartY = plotTop;
7510                                         } else if (chartY > plotTop + plotHeight) {
7511                                                 chartY = plotTop + plotHeight;
7512                                         }
7513
7514                                 }
7515
7516                                 if (mouseIsDown && e.type !== 'touchstart') { // make selection
7517
7518                                         // determine if the mouse has moved more than 10px
7519                                         hasDragged = Math.sqrt(
7520                                                 Math.pow(mouseDownX - chartX, 2) +
7521                                                 Math.pow(mouseDownY - chartY, 2)
7522                                         );
7523                                         if (hasDragged > 10) {
7524                                                 var clickedInside = isInsidePlot(mouseDownX - plotLeft, mouseDownY - plotTop);
7525
7526                                                 // make a selection
7527                                                 if (hasCartesianSeries && (zoomX || zoomY) && clickedInside) {
7528                                                         if (!selectionMarker) {
7529                                                                 selectionMarker = renderer.rect(
7530                                                                         plotLeft,
7531                                                                         plotTop,
7532                                                                         zoomHor ? 1 : plotWidth,
7533                                                                         zoomVert ? 1 : plotHeight,
7534                                                                         0
7535                                                                 )
7536                                                                 .attr({
7537                                                                         fill: optionsChart.selectionMarkerFill || 'rgba(69,114,167,0.25)',
7538                                                                         zIndex: 7
7539                                                                 })
7540                                                                 .add();
7541                                                         }
7542                                                 }
7543
7544                                                 // adjust the width of the selection marker
7545                                                 if (selectionMarker && zoomHor) {
7546                                                         var xSize = chartX - mouseDownX;
7547                                                         selectionMarker.attr({
7548                                                                 width: mathAbs(xSize),
7549                                                                 x: (xSize > 0 ? 0 : xSize) + mouseDownX
7550                                                         });
7551                                                 }
7552                                                 // adjust the height of the selection marker
7553                                                 if (selectionMarker && zoomVert) {
7554                                                         var ySize = chartY - mouseDownY;
7555                                                         selectionMarker.attr({
7556                                                                 height: mathAbs(ySize),
7557                                                                 y: (ySize > 0 ? 0 : ySize) + mouseDownY
7558                                                         });
7559                                                 }
7560
7561                                                 // panning
7562                                                 if (clickedInside && !selectionMarker && optionsChart.panning) {
7563                                                         chart.pan(chartX);
7564                                                 }
7565                                         }
7566
7567                                 } else if (!isOutsidePlot) {
7568                                         // show the tooltip
7569                                         onmousemove(e);
7570                                 }
7571
7572                                 lastWasOutsidePlot = isOutsidePlot;
7573
7574                                 // when outside plot, allow touch-drag by returning true
7575                                 return isOutsidePlot || !hasCartesianSeries;
7576                         };
7577
7578                         /*
7579                          * When the mouse enters the container, run mouseMove
7580                          */
7581                         container.onmousemove = mouseMove;
7582
7583                         /*
7584                          * When the mouse leaves the container, hide the tracking (tooltip).
7585                          */
7586                         addEvent(container, 'mouseleave', hideTooltipOnMouseLeave);
7587
7588                         // issue #149 workaround
7589                         // The mouseleave event above does not always fire. Whenever the mouse is moving
7590                         // outside the plotarea, hide the tooltip
7591                         addEvent(doc, 'mousemove', hideTooltipOnMouseMove);
7592
7593                         container.ontouchstart = function (e) {
7594                                 // For touch devices, use touchmove to zoom
7595                                 if (zoomX || zoomY) {
7596                                         container.onmousedown(e);
7597                                 }
7598                                 // Show tooltip and prevent the lower mouse pseudo event
7599                                 mouseMove(e);
7600                         };
7601
7602                         /*
7603                          * Allow dragging the finger over the chart to read the values on touch
7604                          * devices
7605                          */
7606                         container.ontouchmove = mouseMove;
7607
7608                         /*
7609                          * Allow dragging the finger over the chart to read the values on touch
7610                          * devices
7611                          */
7612                         container.ontouchend = function () {
7613                                 if (hasDragged) {
7614                                         resetTracker();
7615                                 }
7616                         };
7617
7618
7619                         // MooTools 1.2.3 doesn't fire this in IE when using addEvent
7620                         container.onclick = function (e) {
7621                                 var hoverPoint = chart.hoverPoint;
7622                                 e = normalizeMouseEvent(e);
7623
7624                                 e.cancelBubble = true; // IE specific
7625
7626
7627                                 if (!hasDragged) {
7628                                         if (hoverPoint && attr(e.target, 'isTracker')) {
7629                                                 var plotX = hoverPoint.plotX,
7630                                                         plotY = hoverPoint.plotY;
7631
7632                                                 // add page position info
7633                                                 extend(hoverPoint, {
7634                                                         pageX: chartPosition.left + plotLeft +
7635                                                                 (inverted ? plotWidth - plotY : plotX),
7636                                                         pageY: chartPosition.top + plotTop +
7637                                                                 (inverted ? plotHeight - plotX : plotY)
7638                                                 });
7639
7640                                                 // the series click event
7641                                                 fireEvent(hoverPoint.series, 'click', extend(e, {
7642                                                         point: hoverPoint
7643                                                 }));
7644
7645                                                 // the point click event
7646                                                 hoverPoint.firePointEvent('click', e);
7647
7648                                         } else {
7649                                                 extend(e, getMouseCoordinates(e));
7650
7651                                                 // fire a click event in the chart
7652                                                 if (isInsidePlot(e.chartX - plotLeft, e.chartY - plotTop)) {
7653                                                         fireEvent(chart, 'click', e);
7654                                                 }
7655                                         }
7656
7657
7658                                 }
7659                                 // reset mouseIsDown and hasDragged
7660                                 hasDragged = false;
7661                         };
7662
7663                 }
7664
7665                 /**
7666                  * Destroys the MouseTracker object and disconnects DOM events.
7667                  */
7668                 function destroy() {
7669                         // Destroy the tracker group element
7670                         if (chart.trackerGroup) {
7671                                 chart.trackerGroup = trackerGroup = chart.trackerGroup.destroy();
7672                         }
7673
7674                         removeEvent(container, 'mouseleave', hideTooltipOnMouseLeave);
7675                         removeEvent(doc, 'mousemove', hideTooltipOnMouseMove);
7676                         container.onclick = container.onmousedown = container.onmousemove = container.ontouchstart = container.ontouchend = container.ontouchmove = null;
7677                 }
7678
7679                 /**
7680                  * Create the image map that listens for mouseovers
7681                  */
7682                 placeTrackerGroup = function () {
7683
7684                         // first create - plot positions is not final at this stage
7685                         if (!trackerGroup) {
7686                                 chart.trackerGroup = trackerGroup = renderer.g('tracker')
7687                                         .attr({ zIndex: 9 })
7688                                         .add();
7689
7690                         // then position - this happens on load and after resizing and changing
7691                         // axis or box positions
7692                         } else {
7693                                 trackerGroup.translate(plotLeft, plotTop);
7694                                 if (inverted) {
7695                                         trackerGroup.attr({
7696                                                 width: chart.plotWidth,
7697                                                 height: chart.plotHeight
7698                                         }).invert();
7699                                 }
7700                         }
7701                 };
7702
7703
7704                 // Run MouseTracker
7705                 placeTrackerGroup();
7706                 if (options.enabled) {
7707                         chart.tooltip = tooltip = Tooltip(options);
7708                         
7709                         // set the fixed interval ticking for the smooth tooltip
7710                         tooltipInterval = setInterval(function () {
7711                                 if (tooltipTick) {
7712                                         tooltipTick();
7713                                 }
7714                         }, 32);
7715                 }
7716
7717                 setDOMEvents();
7718
7719                 // expose properties
7720                 extend(this, {
7721                         zoomX: zoomX,
7722                         zoomY: zoomY,
7723                         resetTracker: resetTracker,
7724                         normalizeMouseEvent: normalizeMouseEvent,
7725                         destroy: destroy
7726                 });
7727         }
7728
7729
7730
7731         /**
7732          * The overview of the chart's series
7733          */
7734         var Legend = function () {
7735
7736                 var options = chart.options.legend;
7737
7738                 if (!options.enabled) {
7739                         return;
7740                 }
7741
7742                 var horizontal = options.layout === 'horizontal',
7743                         symbolWidth = options.symbolWidth,
7744                         symbolPadding = options.symbolPadding,
7745                         allItems,
7746                         style = options.style,
7747                         itemStyle = options.itemStyle,
7748                         itemHoverStyle = options.itemHoverStyle,
7749                         itemHiddenStyle = merge(itemStyle, options.itemHiddenStyle),
7750                         padding = options.padding || pInt(style.padding),
7751                         y = 18,
7752                         initialItemX = 4 + padding + symbolWidth + symbolPadding,
7753                         itemX,
7754                         itemY,
7755                         lastItemY,
7756                         itemHeight = 0,
7757                         itemMarginTop = options.itemMarginTop || 0,
7758                         itemMarginBottom = options.itemMarginBottom || 0,
7759                         box,
7760                         legendBorderWidth = options.borderWidth,
7761                         legendBackgroundColor = options.backgroundColor,
7762                         legendGroup,
7763                         offsetWidth,
7764                         widthOption = options.width,
7765                         series = chart.series,
7766                         reversedLegend = options.reversed;
7767
7768
7769
7770                 /**
7771                  * Set the colors for the legend item
7772                  * @param {Object} item A Series or Point instance
7773                  * @param {Object} visible Dimmed or colored
7774                  */
7775                 function colorizeItem(item, visible) {
7776                         var legendItem = item.legendItem,
7777                                 legendLine = item.legendLine,
7778                                 legendSymbol = item.legendSymbol,
7779                                 hiddenColor = itemHiddenStyle.color,
7780                                 textColor = visible ? options.itemStyle.color : hiddenColor,
7781                                 symbolColor = visible ? item.color : hiddenColor;
7782
7783                         if (legendItem) {
7784                                 legendItem.css({ fill: textColor });
7785                         }
7786                         if (legendLine) {
7787                                 legendLine.attr({ stroke: symbolColor });
7788                         }
7789                         if (legendSymbol) {
7790                                 legendSymbol.attr({
7791                                         stroke: symbolColor,
7792                                         fill: symbolColor
7793                                 });
7794                         }
7795                 }
7796
7797                 /**
7798                  * Position the legend item
7799                  * @param {Object} item A Series or Point instance
7800                  * @param {Object} visible Dimmed or colored
7801                  */
7802                 function positionItem(item, itemX, itemY) {
7803                         var legendItem = item.legendItem,
7804                                 legendLine = item.legendLine,
7805                                 legendSymbol = item.legendSymbol,
7806                                 checkbox = item.checkbox;
7807                         if (legendItem) {
7808                                 legendItem.attr({
7809                                         x: itemX,
7810                                         y: itemY
7811                                 });
7812                         }
7813                         if (legendLine) {
7814                                 legendLine.translate(itemX, itemY - 4);
7815                         }
7816                         if (legendSymbol) {
7817                                 legendSymbol.attr({
7818                                         x: itemX + legendSymbol.xOff,
7819                                         y: itemY + legendSymbol.yOff
7820                                 });
7821                         }
7822                         if (checkbox) {
7823                                 checkbox.x = itemX;
7824                                 checkbox.y = itemY;
7825                         }
7826                 }
7827
7828                 /**
7829                  * Destroy a single legend item
7830                  * @param {Object} item The series or point
7831                  */
7832                 function destroyItem(item) {
7833                         var checkbox = item.checkbox;
7834
7835                         // destroy SVG elements
7836                         each(['legendItem', 'legendLine', 'legendSymbol'], function (key) {
7837                                 if (item[key]) {
7838                                         item[key].destroy();
7839                                 }
7840                         });
7841
7842                         if (checkbox) {
7843                                 discardElement(item.checkbox);
7844                         }
7845
7846
7847                 }
7848
7849                 /**
7850                  * Destroys the legend.
7851                  */
7852                 function destroy() {
7853                         if (box) {
7854                                 box = box.destroy();
7855                         }
7856
7857                         if (legendGroup) {
7858                                 legendGroup = legendGroup.destroy();
7859                         }
7860                 }
7861
7862                 /**
7863                  * Position the checkboxes after the width is determined
7864                  */
7865                 function positionCheckboxes() {
7866                         each(allItems, function (item) {
7867                                 var checkbox = item.checkbox,
7868                                         alignAttr = legendGroup.alignAttr;
7869                                 if (checkbox) {
7870                                         css(checkbox, {
7871                                                 left: (alignAttr.translateX + item.legendItemWidth + checkbox.x - 40) + PX,
7872                                                 top: (alignAttr.translateY + checkbox.y - 11) + PX
7873                                         });
7874                                 }
7875                         });
7876                 }
7877
7878                 /**
7879                  * Render a single specific legend item
7880                  * @param {Object} item A series or point
7881                  */
7882                 function renderItem(item) {
7883                         var bBox,
7884                                 itemWidth,
7885                                 legendSymbol,
7886                                 symbolX,
7887                                 symbolY,
7888                                 simpleSymbol,
7889                                 radius,
7890                                 li = item.legendItem,
7891                                 series = item.series || item,
7892                                 itemOptions = series.options,
7893                                 strokeWidth = (itemOptions && itemOptions.borderWidth) || 0;
7894
7895
7896                         if (!li) { // generate it once, later move it
7897
7898                                 // let these series types use a simple symbol
7899                                 simpleSymbol = /^(bar|pie|area|column)$/.test(series.type);
7900
7901                                 // generate the list item text
7902                                 item.legendItem = li = renderer.text(
7903                                                 options.labelFormatter.call(item),
7904                                                 0,
7905                                                 0
7906                                         )
7907                                         .css(item.visible ? itemStyle : itemHiddenStyle)
7908                                         .on('mouseover', function () {
7909                                                 item.setState(HOVER_STATE);
7910                                                 li.css(itemHoverStyle);
7911                                         })
7912                                         .on('mouseout', function () {
7913                                                 li.css(item.visible ? itemStyle : itemHiddenStyle);
7914                                                 item.setState();
7915                                         })
7916                                         .on('click', function () {
7917                                                 var strLegendItemClick = 'legendItemClick',
7918                                                         fnLegendItemClick = function () {
7919                                                                 item.setVisible();
7920                                                         };
7921
7922                                                 // click the name or symbol
7923                                                 if (item.firePointEvent) { // point
7924                                                         item.firePointEvent(strLegendItemClick, null, fnLegendItemClick);
7925                                                 } else {
7926                                                         fireEvent(item, strLegendItemClick, null, fnLegendItemClick);
7927                                                 }
7928                                         })
7929                                         .attr({ zIndex: 2 })
7930                                         .add(legendGroup);
7931
7932                                 // draw the line
7933                                 if (!simpleSymbol && itemOptions && itemOptions.lineWidth) {
7934                                         var attrs = {
7935                                                         'stroke-width': itemOptions.lineWidth,
7936                                                         zIndex: 2
7937                                                 };
7938                                         if (itemOptions.dashStyle) {
7939                                                 attrs.dashstyle = itemOptions.dashStyle;
7940                                         }
7941                                         item.legendLine = renderer.path([
7942                                                 M,
7943                                                 -symbolWidth - symbolPadding,
7944                                                 0,
7945                                                 L,
7946                                                 -symbolPadding,
7947                                                 0
7948                                         ])
7949                                         .attr(attrs)
7950                                         .add(legendGroup);
7951                                 }
7952
7953                                 // draw a simple symbol
7954                                 if (simpleSymbol) { // bar|pie|area|column
7955
7956                                         legendSymbol = renderer.rect(
7957                                                 (symbolX = -symbolWidth - symbolPadding),
7958                                                 (symbolY = -11),
7959                                                 symbolWidth,
7960                                                 12,
7961                                                 2
7962                                         ).attr({
7963                                                 //'stroke-width': 0,
7964                                                 zIndex: 3
7965                                         }).add(legendGroup);
7966                                 } else if (itemOptions && itemOptions.marker && itemOptions.marker.enabled) { // draw the marker
7967                                         radius = itemOptions.marker.radius;
7968                                         legendSymbol = renderer.symbol(
7969                                                 item.symbol,
7970                                                 (symbolX = -symbolWidth / 2 - symbolPadding - radius),
7971                                                 (symbolY = -4 - radius),
7972                                                 2 * radius,
7973                                                 2 * radius
7974                                         )
7975                                         .attr(item.pointAttr[NORMAL_STATE])
7976                                         .attr({ zIndex: 3 })
7977                                         .add(legendGroup);
7978
7979                                 }
7980                                 if (legendSymbol) {
7981                                         legendSymbol.xOff = symbolX + (strokeWidth % 2 / 2);
7982                                         legendSymbol.yOff = symbolY + (strokeWidth % 2 / 2);
7983                                 }
7984
7985                                 item.legendSymbol = legendSymbol;
7986
7987                                 // colorize the items
7988                                 colorizeItem(item, item.visible);
7989
7990
7991                                 // add the HTML checkbox on top
7992                                 if (itemOptions && itemOptions.showCheckbox) {
7993                                         item.checkbox = createElement('input', {
7994                                                 type: 'checkbox',
7995                                                 checked: item.selected,
7996                                                 defaultChecked: item.selected // required by IE7
7997                                         }, options.itemCheckboxStyle, container);
7998
7999                                         addEvent(item.checkbox, 'click', function (event) {
8000                                                 var target = event.target;
8001                                                 fireEvent(item, 'checkboxClick', {
8002                                                                 checked: target.checked
8003                                                         },
8004                                                         function () {
8005                                                                 item.select();
8006                                                         }
8007                                                 );
8008                                         });
8009                                 }
8010                         }
8011
8012
8013                         // calculate the positions for the next line
8014                         bBox = li.getBBox();
8015
8016                         itemWidth = item.legendItemWidth =
8017                                 options.itemWidth || symbolWidth + symbolPadding + bBox.width + padding;
8018                         itemHeight = bBox.height;
8019
8020                         // if the item exceeds the width, start a new line
8021                         if (horizontal && itemX - initialItemX + itemWidth >
8022                                         (widthOption || (chartWidth - 2 * padding - initialItemX))) {
8023                                 itemX = initialItemX;
8024                                 itemY += itemMarginTop + itemHeight + itemMarginBottom;
8025                         }
8026                         lastItemY = itemY + itemMarginBottom;
8027
8028                         // position the newly generated or reordered items
8029                         positionItem(item, itemX, itemY);
8030
8031                         // advance
8032                         if (horizontal) {
8033                                 itemX += itemWidth;
8034                         } else {
8035                                 itemY += itemMarginTop + itemHeight + itemMarginBottom;
8036                         }
8037
8038                         // the width of the widest item
8039                         offsetWidth = widthOption || mathMax(
8040                                 horizontal ? itemX - initialItemX : itemWidth,
8041                                 offsetWidth
8042                         );
8043
8044                 }
8045
8046                 /**
8047                  * Render the legend. This method can be called both before and after
8048                  * chart.render. If called after, it will only rearrange items instead
8049                  * of creating new ones.
8050                  */
8051                 function renderLegend() {
8052                         itemX = initialItemX;
8053                         itemY = padding + itemMarginTop + y - 5; // 5 is the number of pixels above the text
8054                         offsetWidth = 0;
8055                         lastItemY = 0;
8056
8057                         if (!legendGroup) {
8058                                 legendGroup = renderer.g('legend')
8059                                         .attr({ zIndex: 10 }) // in front of trackers, #414
8060                                         .add();
8061                         }
8062
8063
8064                         // add each series or point
8065                         allItems = [];
8066                         each(series, function (serie) {
8067                                 var seriesOptions = serie.options;
8068
8069                                 if (!seriesOptions.showInLegend) {
8070                                         return;
8071                                 }
8072
8073                                 // use points or series for the legend item depending on legendType
8074                                 allItems = allItems.concat(
8075                                                 serie.legendItems ||
8076                                                 (seriesOptions.legendType === 'point' ?
8077                                                                 serie.data :
8078                                                                 serie)
8079                                 );
8080
8081                         });
8082
8083                         // sort by legendIndex
8084                         stableSort(allItems, function (a, b) {
8085                                 return (a.options.legendIndex || 0) - (b.options.legendIndex || 0);
8086                         });
8087
8088                         // reversed legend
8089                         if (reversedLegend) {
8090                                 allItems.reverse();
8091                         }
8092
8093                         // render the items
8094                         each(allItems, renderItem);
8095
8096
8097                         // Draw the border
8098                         legendWidth = widthOption || offsetWidth;
8099                         legendHeight = lastItemY - y + itemHeight;
8100
8101                         if (legendBorderWidth || legendBackgroundColor) {
8102                                 legendWidth += 2 * padding;
8103                                 legendHeight += 2 * padding;
8104
8105                                 if (!box) {
8106                                         box = renderer.rect(
8107                                                 0,
8108                                                 0,
8109                                                 legendWidth,
8110                                                 legendHeight,
8111                                                 options.borderRadius,
8112                                                 legendBorderWidth || 0
8113                                         ).attr({
8114                                                 stroke: options.borderColor,
8115                                                 'stroke-width': legendBorderWidth || 0,
8116                                                 fill: legendBackgroundColor || NONE
8117                                         })
8118                                         .add(legendGroup)
8119                                         .shadow(options.shadow);
8120                                         box.isNew = true;
8121
8122                                 } else if (legendWidth > 0 && legendHeight > 0) {
8123                                         box[box.isNew ? 'attr' : 'animate'](
8124                                                 box.crisp(null, null, null, legendWidth, legendHeight)
8125                                         );
8126                                         box.isNew = false;
8127                                 }
8128
8129                                 // hide the border if no items
8130                                 box[allItems.length ? 'show' : 'hide']();
8131                         }
8132
8133                         // 1.x compatibility: positioning based on style
8134                         var props = ['left', 'right', 'top', 'bottom'],
8135                                 prop,
8136                                 i = 4;
8137                         while (i--) {
8138                                 prop = props[i];
8139                                 if (style[prop] && style[prop] !== 'auto') {
8140                                         options[i < 2 ? 'align' : 'verticalAlign'] = prop;
8141                                         options[i < 2 ? 'x' : 'y'] = pInt(style[prop]) * (i % 2 ? -1 : 1);
8142                                 }
8143                         }
8144
8145                         if (allItems.length) {
8146                                 legendGroup.align(extend(options, {
8147                                         width: legendWidth,
8148                                         height: legendHeight
8149                                 }), true, spacingBox);
8150                         }
8151
8152                         if (!isResizing) {
8153                                 positionCheckboxes();
8154                         }
8155                 }
8156
8157
8158                 // run legend
8159                 renderLegend();
8160
8161                 // move checkboxes
8162                 addEvent(chart, 'endResize', positionCheckboxes);
8163
8164                 // expose
8165                 return {
8166                         colorizeItem: colorizeItem,
8167                         destroyItem: destroyItem,
8168                         renderLegend: renderLegend,
8169                         destroy: destroy
8170                 };
8171         };
8172
8173
8174
8175
8176
8177
8178         /**
8179          * Initialize an individual series, called internally before render time
8180          */
8181         function initSeries(options) {
8182                 var type = options.type || optionsChart.type || optionsChart.defaultSeriesType,
8183                         typeClass = seriesTypes[type],
8184                         serie,
8185                         hasRendered = chart.hasRendered;
8186
8187                 // an inverted chart can't take a column series and vice versa
8188                 if (hasRendered) {
8189                         if (inverted && type === 'column') {
8190                                 typeClass = seriesTypes.bar;
8191                         } else if (!inverted && type === 'bar') {
8192                                 typeClass = seriesTypes.column;
8193                         }
8194                 }
8195
8196                 serie = new typeClass();
8197
8198                 serie.init(chart, options);
8199
8200                 // set internal chart properties
8201                 if (!hasRendered && serie.inverted) {
8202                         inverted = true;
8203                 }
8204                 if (serie.isCartesian) {
8205                         hasCartesianSeries = serie.isCartesian;
8206                 }
8207
8208                 series.push(serie);
8209
8210                 return serie;
8211         }
8212
8213         /**
8214          * Add a series dynamically after  time
8215          *
8216          * @param {Object} options The config options
8217          * @param {Boolean} redraw Whether to redraw the chart after adding. Defaults to true.
8218          * @param {Boolean|Object} animation Whether to apply animation, and optionally animation
8219          *    configuration
8220          *
8221          * @return {Object} series The newly created series object
8222          */
8223         function addSeries(options, redraw, animation) {
8224                 var series;
8225
8226                 if (options) {
8227                         setAnimation(animation, chart);
8228                         redraw = pick(redraw, true); // defaults to true
8229
8230                         fireEvent(chart, 'addSeries', { options: options }, function () {
8231                                 series = initSeries(options);
8232                                 series.isDirty = true;
8233
8234                                 chart.isDirtyLegend = true; // the series array is out of sync with the display
8235                                 if (redraw) {
8236                                         chart.redraw();
8237                                 }
8238                         });
8239                 }
8240
8241                 return series;
8242         }
8243
8244         /**
8245          * Check whether a given point is within the plot area
8246          *
8247          * @param {Number} x Pixel x relative to the plot area
8248          * @param {Number} y Pixel y relative to the plot area
8249          */
8250         isInsidePlot = function (x, y) {
8251                 return x >= 0 &&
8252                         x <= plotWidth &&
8253                         y >= 0 &&
8254                         y <= plotHeight;
8255         };
8256
8257         /**
8258          * Adjust all axes tick amounts
8259          */
8260         function adjustTickAmounts() {
8261                 if (optionsChart.alignTicks !== false) {
8262                         each(axes, function (axis) {
8263                                 axis.adjustTickAmount();
8264                         });
8265                 }
8266                 maxTicks = null;
8267         }
8268
8269         /**
8270          * Redraw legend, axes or series based on updated data
8271          *
8272          * @param {Boolean|Object} animation Whether to apply animation, and optionally animation
8273          *    configuration
8274          */
8275         function redraw(animation) {
8276                 var redrawLegend = chart.isDirtyLegend,
8277                         hasStackedSeries,
8278                         isDirtyBox = chart.isDirtyBox, // todo: check if it has actually changed?
8279                         seriesLength = series.length,
8280                         i = seriesLength,
8281                         clipRect = chart.clipRect,
8282                         serie;
8283
8284                 setAnimation(animation, chart);
8285
8286                 // link stacked series
8287                 while (i--) {
8288                         serie = series[i];
8289                         if (serie.isDirty && serie.options.stacking) {
8290                                 hasStackedSeries = true;
8291                                 break;
8292                         }
8293                 }
8294                 if (hasStackedSeries) { // mark others as dirty
8295                         i = seriesLength;
8296                         while (i--) {
8297                                 serie = series[i];
8298                                 if (serie.options.stacking) {
8299                                         serie.isDirty = true;
8300                                 }
8301                         }
8302                 }
8303
8304                 // handle updated data in the series
8305                 each(series, function (serie) {
8306                         if (serie.isDirty) { // prepare the data so axis can read it
8307                                 if (serie.options.legendType === 'point') {
8308                                         redrawLegend = true;
8309                                 }
8310                         }
8311                 });
8312
8313                 // handle added or removed series
8314                 if (redrawLegend && legend.renderLegend) { // series or pie points are added or removed
8315                         // draw legend graphics
8316                         legend.renderLegend();
8317
8318                         chart.isDirtyLegend = false;
8319                 }
8320
8321
8322                 if (hasCartesianSeries) {
8323                         if (!isResizing) {
8324
8325                                 // reset maxTicks
8326                                 maxTicks = null;
8327
8328                                 // set axes scales
8329                                 each(axes, function (axis) {
8330                                         axis.setScale();
8331                                 });
8332                         }
8333                         adjustTickAmounts();
8334                         getMargins();
8335
8336                         // redraw axes
8337                         each(axes, function (axis) {
8338                                 fireEvent(axis, 'afterSetExtremes', axis.getExtremes()); // #747, #751                                  
8339                                 if (axis.isDirty) {                                     
8340                                         axis.redraw();                                  
8341                                 }
8342                         });
8343
8344
8345                 }
8346
8347                 // the plot areas size has changed
8348                 if (isDirtyBox) {
8349                         drawChartBox();
8350                         placeTrackerGroup();
8351
8352                         // move clip rect
8353                         if (clipRect) {
8354                                 stop(clipRect);
8355                                 clipRect.animate({ // for chart resize
8356                                         width: chart.plotSizeX,
8357                                         height: chart.plotSizeY + 1
8358                                 });
8359                         }
8360
8361                 }
8362
8363
8364                 // redraw affected series
8365                 each(series, function (serie) {
8366                         if (serie.isDirty && serie.visible &&
8367                                         (!serie.isCartesian || serie.xAxis)) { // issue #153
8368                                 serie.redraw();
8369                         }
8370                 });
8371
8372
8373                 // hide tooltip and hover states
8374                 if (tracker && tracker.resetTracker) {
8375                         tracker.resetTracker();
8376                 }
8377
8378                 // fire the event
8379                 fireEvent(chart, 'redraw'); // jQuery breaks this when calling it from addEvent. Overwrites chart.redraw
8380         }
8381
8382
8383
8384         /**
8385          * Dim the chart and show a loading text or symbol
8386          * @param {String} str An optional text to show in the loading label instead of the default one
8387          */
8388         function showLoading(str) {
8389                 var loadingOptions = options.loading;
8390
8391                 // create the layer at the first call
8392                 if (!loadingDiv) {
8393                         loadingDiv = createElement(DIV, {
8394                                 className: PREFIX + 'loading'
8395                         }, extend(loadingOptions.style, {
8396                                 left: plotLeft + PX,
8397                                 top: plotTop + PX,
8398                                 width: plotWidth + PX,
8399                                 height: plotHeight + PX,
8400                                 zIndex: 10,
8401                                 display: NONE
8402                         }), container);
8403
8404                         loadingSpan = createElement(
8405                                 'span',
8406                                 null,
8407                                 loadingOptions.labelStyle,
8408                                 loadingDiv
8409                         );
8410
8411                 }
8412
8413                 // update text
8414                 loadingSpan.innerHTML = str || options.lang.loading;
8415
8416                 // show it
8417                 if (!loadingShown) {
8418                         css(loadingDiv, { opacity: 0, display: '' });
8419                         animate(loadingDiv, {
8420                                 opacity: loadingOptions.style.opacity
8421                         }, {
8422                                 duration: loadingOptions.showDuration || 0
8423                         });
8424                         loadingShown = true;
8425                 }
8426         }
8427         /**
8428          * Hide the loading layer
8429          */
8430         function hideLoading() {
8431                 if (loadingDiv) {
8432                         animate(loadingDiv, {
8433                                 opacity: 0
8434                         }, {
8435                                 duration: options.loading.hideDuration || 100,
8436                                 complete: function () {
8437                                         css(loadingDiv, { display: NONE });
8438                                 }
8439                         });
8440                 }
8441                 loadingShown = false;
8442         }
8443
8444         /**
8445          * Get an axis, series or point object by id.
8446          * @param id {String} The id as given in the configuration options
8447          */
8448         function get(id) {
8449                 var i,
8450                         j,
8451                         points;
8452
8453                 // search axes
8454                 for (i = 0; i < axes.length; i++) {
8455                         if (axes[i].options.id === id) {
8456                                 return axes[i];
8457                         }
8458                 }
8459
8460                 // search series
8461                 for (i = 0; i < series.length; i++) {
8462                         if (series[i].options.id === id) {
8463                                 return series[i];
8464                         }
8465                 }
8466
8467                 // search points
8468                 for (i = 0; i < series.length; i++) {
8469                         points = series[i].points || [];
8470                         for (j = 0; j < points.length; j++) {
8471                                 if (points[j].id === id) {
8472                                         return points[j];
8473                                 }
8474                         }
8475                 }
8476                 return null;
8477         }
8478
8479         /**
8480          * Create the Axis instances based on the config options
8481          */
8482         function getAxes() {
8483                 var xAxisOptions = options.xAxis || {},
8484                         yAxisOptions = options.yAxis || {},
8485                         optionsArray,
8486                         axis;
8487
8488                 // make sure the options are arrays and add some members
8489                 xAxisOptions = splat(xAxisOptions);
8490                 each(xAxisOptions, function (axis, i) {
8491                         axis.index = i;
8492                         axis.isX = true;
8493                 });
8494
8495                 yAxisOptions = splat(yAxisOptions);
8496                 each(yAxisOptions, function (axis, i) {
8497                         axis.index = i;
8498                 });
8499
8500                 // concatenate all axis options into one array
8501                 optionsArray = xAxisOptions.concat(yAxisOptions);
8502
8503                 each(optionsArray, function (axisOptions) {
8504                         axis = new Axis(axisOptions);
8505                 });
8506
8507                 adjustTickAmounts();
8508         }
8509
8510
8511         /**
8512          * Get the currently selected points from all series
8513          */
8514         function getSelectedPoints() {
8515                 var points = [];
8516                 each(series, function (serie) {
8517                         points = points.concat(grep(serie.points, function (point) {
8518                                 return point.selected;
8519                         }));
8520                 });
8521                 return points;
8522         }
8523
8524         /**
8525          * Get the currently selected series
8526          */
8527         function getSelectedSeries() {
8528                 return grep(series, function (serie) {
8529                         return serie.selected;
8530                 });
8531         }
8532
8533         /**
8534          * Display the zoom button
8535          */
8536         function showResetZoom() {
8537                 var lang = defaultOptions.lang,
8538                         btnOptions = optionsChart.resetZoomButton,
8539                         box = btnOptions.relativeTo === 'plot' && {
8540                                 x: plotLeft,
8541                                 y: plotTop,
8542                                 width: plotWidth,
8543                                 height: plotHeight
8544                         };
8545
8546                 chart.resetZoomButton = renderer.button(lang.resetZoom, null, null, zoomOut, btnOptions.theme)
8547                         .attr({
8548                                 align: btnOptions.position.align,
8549                                 title: lang.resetZoomTitle
8550                         })
8551                         .add()
8552                         .align(btnOptions.position, false, box);
8553         }
8554
8555         /**
8556          * Zoom out to 1:1
8557          */
8558         zoomOut = function () {
8559                 var resetZoomButton = chart.resetZoomButton;
8560
8561                 fireEvent(chart, 'selection', { resetSelection: true }, zoom);
8562                 if (resetZoomButton) {
8563                         chart.resetZoomButton = resetZoomButton.destroy();
8564                 }
8565         };
8566         /**
8567          * Zoom into a given portion of the chart given by axis coordinates
8568          * @param {Object} event
8569          */
8570         zoom = function (event) {
8571
8572                 // add button to reset selection
8573                 var animate = chart.pointCount < 100,
8574                         hasZoomed;
8575
8576                 if (chart.resetZoomEnabled !== false && !chart.resetZoomButton) { // hook for Stock charts etc.
8577                         showResetZoom();
8578                 }
8579
8580                 // if zoom is called with no arguments, reset the axes
8581                 if (!event || event.resetSelection) {
8582                         each(axes, function (axis) {
8583                                 if (axis.options.zoomEnabled !== false) {
8584                                         axis.setExtremes(null, null, false);
8585                                         hasZoomed = true;
8586                                 }
8587                         });
8588                 } else { // else, zoom in on all axes
8589                         each(event.xAxis.concat(event.yAxis), function (axisData) {
8590                                 var axis = axisData.axis;
8591
8592                                 // don't zoom more than minRange
8593                                 if (chart.tracker[axis.isXAxis ? 'zoomX' : 'zoomY']) {
8594                                         axis.setExtremes(axisData.min, axisData.max, false);
8595                                         hasZoomed = true;
8596                                 }
8597                         });
8598                 }
8599                 
8600                 // Redraw
8601                 if (hasZoomed) {
8602                         redraw(true, animate);
8603                 }
8604         };
8605
8606         /**
8607          * Pan the chart by dragging the mouse across the pane. This function is called
8608          * on mouse move, and the distance to pan is computed from chartX compared to
8609          * the first chartX position in the dragging operation.
8610          */
8611         chart.pan = function (chartX) {
8612
8613                 var xAxis = chart.xAxis[0],
8614                         mouseDownX = chart.mouseDownX,
8615                         halfPointRange = xAxis.pointRange / 2,
8616                         extremes = xAxis.getExtremes(),
8617                         newMin = xAxis.translate(mouseDownX - chartX, true) + halfPointRange,
8618                         newMax = xAxis.translate(mouseDownX + plotWidth - chartX, true) - halfPointRange,
8619                         hoverPoints = chart.hoverPoints;
8620
8621                 // remove active points for shared tooltip
8622                 if (hoverPoints) {
8623                         each(hoverPoints, function (point) {
8624                                 point.setState();
8625                         });
8626                 }
8627
8628                 if (newMin > mathMin(extremes.dataMin, extremes.min) && newMax < mathMax(extremes.dataMax, extremes.max)) {
8629                         xAxis.setExtremes(newMin, newMax, true, false);
8630                 }
8631
8632                 chart.mouseDownX = chartX; // set new reference for next run
8633                 css(container, { cursor: 'move' });
8634         };
8635
8636         /**
8637          * Show the title and subtitle of the chart
8638          *
8639          * @param titleOptions {Object} New title options
8640          * @param subtitleOptions {Object} New subtitle options
8641          *
8642          */
8643         function setTitle(titleOptions, subtitleOptions) {
8644
8645                 chartTitleOptions = merge(options.title, titleOptions);
8646                 chartSubtitleOptions = merge(options.subtitle, subtitleOptions);
8647
8648                 // add title and subtitle
8649                 each([
8650                         ['title', titleOptions, chartTitleOptions],
8651                         ['subtitle', subtitleOptions, chartSubtitleOptions]
8652                 ], function (arr) {
8653                         var name = arr[0],
8654                                 title = chart[name],
8655                                 titleOptions = arr[1],
8656                                 chartTitleOptions = arr[2];
8657
8658                         if (title && titleOptions) {
8659                                 title = title.destroy(); // remove old
8660                         }
8661                         if (chartTitleOptions && chartTitleOptions.text && !title) {
8662                                 chart[name] = renderer.text(
8663                                         chartTitleOptions.text,
8664                                         0,
8665                                         0,
8666                                         chartTitleOptions.useHTML
8667                                 )
8668                                 .attr({
8669                                         align: chartTitleOptions.align,
8670                                         'class': PREFIX + name,
8671                                         zIndex: 1
8672                                 })
8673                                 .css(chartTitleOptions.style)
8674                                 .add()
8675                                 .align(chartTitleOptions, false, spacingBox);
8676                         }
8677                 });
8678
8679         }
8680
8681         /**
8682          * Get chart width and height according to options and container size
8683          */
8684         function getChartSize() {
8685
8686                 containerWidth = (renderToClone || renderTo).offsetWidth;
8687                 containerHeight = (renderToClone || renderTo).offsetHeight;
8688                 chart.chartWidth = chartWidth = optionsChart.width || containerWidth || 600;
8689                 chart.chartHeight = chartHeight = optionsChart.height ||
8690                         // the offsetHeight of an empty container is 0 in standard browsers, but 19 in IE7:
8691                         (containerHeight > 19 ? containerHeight : 400);
8692         }
8693
8694
8695         /**
8696          * Get the containing element, determine the size and create the inner container
8697          * div to hold the chart
8698          */
8699         function getContainer() {
8700                 renderTo = optionsChart.renderTo;
8701                 containerId = PREFIX + idCounter++;
8702
8703                 if (isString(renderTo)) {
8704                         renderTo = doc.getElementById(renderTo);
8705                 }
8706
8707                 // remove previous chart
8708                 renderTo.innerHTML = '';
8709
8710                 // If the container doesn't have an offsetWidth, it has or is a child of a node
8711                 // that has display:none. We need to temporarily move it out to a visible
8712                 // state to determine the size, else the legend and tooltips won't render
8713                 // properly
8714                 if (!renderTo.offsetWidth) {
8715                         renderToClone = renderTo.cloneNode(0);
8716                         css(renderToClone, {
8717                                 position: ABSOLUTE,
8718                                 top: '-9999px',
8719                                 display: ''
8720                         });
8721                         doc.body.appendChild(renderToClone);
8722                 }
8723
8724                 // get the width and height
8725                 getChartSize();
8726
8727                 // create the inner container
8728                 chart.container = container = createElement(DIV, {
8729                                 className: PREFIX + 'container' +
8730                                         (optionsChart.className ? ' ' + optionsChart.className : ''),
8731                                 id: containerId
8732                         }, extend({
8733                                 position: RELATIVE,
8734                                 overflow: HIDDEN, // needed for context menu (avoid scrollbars) and
8735                                         // content overflow in IE
8736                                 width: chartWidth + PX,
8737                                 height: chartHeight + PX,
8738                                 textAlign: 'left',
8739                                 lineHeight: 'normal' // #427
8740                         }, optionsChart.style),
8741                         renderToClone || renderTo
8742                 );
8743
8744                 chart.renderer = renderer =
8745                         optionsChart.forExport ? // force SVG, used for SVG export
8746                                 new SVGRenderer(container, chartWidth, chartHeight, true) :
8747                                 new Renderer(container, chartWidth, chartHeight);
8748
8749                 // Issue 110 workaround:
8750                 // In Firefox, if a div is positioned by percentage, its pixel position may land
8751                 // between pixels. The container itself doesn't display this, but an SVG element
8752                 // inside this container will be drawn at subpixel precision. In order to draw
8753                 // sharp lines, this must be compensated for. This doesn't seem to work inside
8754                 // iframes though (like in jsFiddle).
8755                 var subPixelFix, rect;
8756                 if (isFirefox && container.getBoundingClientRect) {
8757                         subPixelFix = function () {
8758                                 css(container, { left: 0, top: 0 });
8759                                 rect = container.getBoundingClientRect();
8760                                 css(container, {
8761                                         left: (-(rect.left - pInt(rect.left))) + PX,
8762                                         top: (-(rect.top - pInt(rect.top))) + PX
8763                                 });
8764                         };
8765
8766                         // run the fix now
8767                         subPixelFix();
8768
8769                         // run it on resize
8770                         addEvent(win, 'resize', subPixelFix);
8771
8772                         // remove it on chart destroy
8773                         addEvent(chart, 'destroy', function () {
8774                                 removeEvent(win, 'resize', subPixelFix);
8775                         });
8776                 }
8777         }
8778
8779         /**
8780          * Calculate margins by rendering axis labels in a preliminary position. Title,
8781          * subtitle and legend have already been rendered at this stage, but will be
8782          * moved into their final positions
8783          */
8784         getMargins = function () {
8785                 var legendOptions = options.legend,
8786                         legendMargin = pick(legendOptions.margin, 10),
8787                         legendX = legendOptions.x,
8788                         legendY = legendOptions.y,
8789                         align = legendOptions.align,
8790                         verticalAlign = legendOptions.verticalAlign,
8791                         titleOffset;
8792
8793                 resetMargins();
8794
8795                 // adjust for title and subtitle
8796                 if ((chart.title || chart.subtitle) && !defined(optionsMarginTop)) {
8797                         titleOffset = mathMax(
8798                                 (chart.title && !chartTitleOptions.floating && !chartTitleOptions.verticalAlign && chartTitleOptions.y) || 0,
8799                                 (chart.subtitle && !chartSubtitleOptions.floating && !chartSubtitleOptions.verticalAlign && chartSubtitleOptions.y) || 0
8800                         );
8801                         if (titleOffset) {
8802                                 plotTop = mathMax(plotTop, titleOffset + pick(chartTitleOptions.margin, 15) + spacingTop);
8803                         }
8804                 }
8805                 // adjust for legend
8806                 if (legendOptions.enabled && !legendOptions.floating) {
8807                         if (align === 'right') { // horizontal alignment handled first
8808                                 if (!defined(optionsMarginRight)) {
8809                                         marginRight = mathMax(
8810                                                 marginRight,
8811                                                 legendWidth - legendX + legendMargin + spacingRight
8812                                         );
8813                                 }
8814                         } else if (align === 'left') {
8815                                 if (!defined(optionsMarginLeft)) {
8816                                         plotLeft = mathMax(
8817                                                 plotLeft,
8818                                                 legendWidth + legendX + legendMargin + spacingLeft
8819                                         );
8820                                 }
8821
8822                         } else if (verticalAlign === 'top') {
8823                                 if (!defined(optionsMarginTop)) {
8824                                         plotTop = mathMax(
8825                                                 plotTop,
8826                                                 legendHeight + legendY + legendMargin + spacingTop
8827                                         );
8828                                 }
8829
8830                         } else if (verticalAlign === 'bottom') {
8831                                 if (!defined(optionsMarginBottom)) {
8832                                         marginBottom = mathMax(
8833                                                 marginBottom,
8834                                                 legendHeight - legendY + legendMargin + spacingBottom
8835                                         );
8836                                 }
8837                         }
8838                 }
8839
8840                 // adjust for scroller
8841                 if (chart.extraBottomMargin) {
8842                         marginBottom += chart.extraBottomMargin;
8843                 }
8844                 if (chart.extraTopMargin) {
8845                         plotTop += chart.extraTopMargin;
8846                 }
8847
8848                 // pre-render axes to get labels offset width
8849                 if (hasCartesianSeries) {
8850                         each(axes, function (axis) {
8851                                 axis.getOffset();
8852                         });
8853                 }
8854
8855                 if (!defined(optionsMarginLeft)) {
8856                         plotLeft += axisOffset[3];
8857                 }
8858                 if (!defined(optionsMarginTop)) {
8859                         plotTop += axisOffset[0];
8860                 }
8861                 if (!defined(optionsMarginBottom)) {
8862                         marginBottom += axisOffset[2];
8863                 }
8864                 if (!defined(optionsMarginRight)) {
8865                         marginRight += axisOffset[1];
8866                 }
8867
8868                 setChartSize();
8869
8870         };
8871
8872         /**
8873          * Add the event handlers necessary for auto resizing
8874          *
8875          */
8876         function initReflow() {
8877                 var reflowTimeout;
8878                 function reflow(e) {
8879                         var width = optionsChart.width || renderTo.offsetWidth,
8880                                 height = optionsChart.height || renderTo.offsetHeight,
8881                                 target = e.target;
8882                                 
8883                         // Width and height checks for display:none. Target is doc in IE8 and Opera,
8884                         // win in Firefox, Chrome and IE9.
8885                         if (width && height && (target === win || target === doc)) {
8886                                 
8887                                 if (width !== containerWidth || height !== containerHeight) {
8888                                         clearTimeout(reflowTimeout);
8889                                         reflowTimeout = setTimeout(function () {
8890                                                 resize(width, height, false);
8891                                         }, 100);
8892                                 }
8893                                 containerWidth = width;
8894                                 containerHeight = height;
8895                         }
8896                 }
8897                 addEvent(win, 'resize', reflow);
8898                 addEvent(chart, 'destroy', function () {
8899                         removeEvent(win, 'resize', reflow);
8900                 });
8901         }
8902
8903         /**
8904          * Fires endResize event on chart instance.
8905          */
8906         function fireEndResize() {
8907                 if (chart) {
8908                         fireEvent(chart, 'endResize', null, function () {
8909                                 isResizing -= 1;
8910                         });
8911                 }
8912         }
8913
8914         /**
8915          * Resize the chart to a given width and height
8916          * @param {Number} width
8917          * @param {Number} height
8918          * @param {Object|Boolean} animation
8919          */
8920         resize = function (width, height, animation) {
8921                 var chartTitle = chart.title,
8922                         chartSubtitle = chart.subtitle;
8923
8924                 isResizing += 1;
8925
8926                 // set the animation for the current process
8927                 setAnimation(animation, chart);
8928
8929                 oldChartHeight = chartHeight;
8930                 oldChartWidth = chartWidth;
8931                 if (defined(width)) {
8932                         chart.chartWidth = chartWidth = mathRound(width);
8933                 }
8934                 if (defined(height)) {
8935                         chart.chartHeight = chartHeight = mathRound(height);
8936                 }
8937
8938                 css(container, {
8939                         width: chartWidth + PX,
8940                         height: chartHeight + PX
8941                 });
8942                 renderer.setSize(chartWidth, chartHeight, animation);
8943
8944                 // update axis lengths for more correct tick intervals:
8945                 plotWidth = chartWidth - plotLeft - marginRight;
8946                 plotHeight = chartHeight - plotTop - marginBottom;
8947
8948                 // handle axes
8949                 maxTicks = null;
8950                 each(axes, function (axis) {
8951                         axis.isDirty = true;
8952                         axis.setScale();
8953                 });
8954
8955                 // make sure non-cartesian series are also handled
8956                 each(series, function (serie) {
8957                         serie.isDirty = true;
8958                 });
8959
8960                 chart.isDirtyLegend = true; // force legend redraw
8961                 chart.isDirtyBox = true; // force redraw of plot and chart border
8962
8963                 getMargins();
8964
8965                 // move titles
8966                 if (chartTitle) {
8967                         chartTitle.align(null, null, spacingBox);
8968                 }
8969                 if (chartSubtitle) {
8970                         chartSubtitle.align(null, null, spacingBox);
8971                 }
8972
8973                 redraw(animation);
8974
8975
8976                 oldChartHeight = null;
8977                 fireEvent(chart, 'resize');
8978
8979                 // fire endResize and set isResizing back
8980                 // If animation is disabled, fire without delay
8981                 if (globalAnimation === false) {
8982                         fireEndResize();
8983                 } else { // else set a timeout with the animation duration
8984                         setTimeout(fireEndResize, (globalAnimation && globalAnimation.duration) || 500);
8985                 }
8986         };
8987
8988         /**
8989          * Set the public chart properties. This is done before and after the pre-render
8990          * to determine margin sizes
8991          */
8992         setChartSize = function () {
8993
8994                 chart.plotLeft = plotLeft = mathRound(plotLeft);
8995                 chart.plotTop = plotTop = mathRound(plotTop);
8996                 chart.plotWidth = plotWidth = mathRound(chartWidth - plotLeft - marginRight);
8997                 chart.plotHeight = plotHeight = mathRound(chartHeight - plotTop - marginBottom);
8998
8999                 chart.plotSizeX = inverted ? plotHeight : plotWidth;
9000                 chart.plotSizeY = inverted ? plotWidth : plotHeight;
9001
9002                 spacingBox = {
9003                         x: spacingLeft,
9004                         y: spacingTop,
9005                         width: chartWidth - spacingLeft - spacingRight,
9006                         height: chartHeight - spacingTop - spacingBottom
9007                 };
9008
9009                 each(axes, function (axis) {
9010                         axis.setAxisSize();
9011                         axis.setAxisTranslation();
9012                 });
9013         };
9014
9015         /**
9016          * Initial margins before auto size margins are applied
9017          */
9018         resetMargins = function () {
9019                 plotTop = pick(optionsMarginTop, spacingTop);
9020                 marginRight = pick(optionsMarginRight, spacingRight);
9021                 marginBottom = pick(optionsMarginBottom, spacingBottom);
9022                 plotLeft = pick(optionsMarginLeft, spacingLeft);
9023                 axisOffset = [0, 0, 0, 0]; // top, right, bottom, left
9024         };
9025
9026         /**
9027          * Draw the borders and backgrounds for chart and plot area
9028          */
9029         drawChartBox = function () {
9030                 var chartBorderWidth = optionsChart.borderWidth || 0,
9031                         chartBackgroundColor = optionsChart.backgroundColor,
9032                         plotBackgroundColor = optionsChart.plotBackgroundColor,
9033                         plotBackgroundImage = optionsChart.plotBackgroundImage,
9034                         mgn,
9035                         plotSize = {
9036                                 x: plotLeft,
9037                                 y: plotTop,
9038                                 width: plotWidth,
9039                                 height: plotHeight
9040                         };
9041
9042                 // Chart area
9043                 mgn = chartBorderWidth + (optionsChart.shadow ? 8 : 0);
9044
9045                 if (chartBorderWidth || chartBackgroundColor) {
9046                         if (!chartBackground) {
9047                                 chartBackground = renderer.rect(mgn / 2, mgn / 2, chartWidth - mgn, chartHeight - mgn,
9048                                                 optionsChart.borderRadius, chartBorderWidth)
9049                                         .attr({
9050                                                 stroke: optionsChart.borderColor,
9051                                                 'stroke-width': chartBorderWidth,
9052                                                 fill: chartBackgroundColor || NONE
9053                                         })
9054                                         .add()
9055                                         .shadow(optionsChart.shadow);
9056                         } else { // resize
9057                                 chartBackground.animate(
9058                                         chartBackground.crisp(null, null, null, chartWidth - mgn, chartHeight - mgn)
9059                                 );
9060                         }
9061                 }
9062
9063
9064                 // Plot background
9065                 if (plotBackgroundColor) {
9066                         if (!plotBackground) {
9067                                 plotBackground = renderer.rect(plotLeft, plotTop, plotWidth, plotHeight, 0)
9068                                         .attr({
9069                                                 fill: plotBackgroundColor
9070                                         })
9071                                         .add()
9072                                         .shadow(optionsChart.plotShadow);
9073                         } else {
9074                                 plotBackground.animate(plotSize);
9075                         }
9076                 }
9077                 if (plotBackgroundImage) {
9078                         if (!plotBGImage) {
9079                                 plotBGImage = renderer.image(plotBackgroundImage, plotLeft, plotTop, plotWidth, plotHeight)
9080                                         .add();
9081                         } else {
9082                                 plotBGImage.animate(plotSize);
9083                         }
9084                 }
9085
9086                 // Plot area border
9087                 if (optionsChart.plotBorderWidth) {
9088                         if (!plotBorder) {
9089                                 plotBorder = renderer.rect(plotLeft, plotTop, plotWidth, plotHeight, 0, optionsChart.plotBorderWidth)
9090                                         .attr({
9091                                                 stroke: optionsChart.plotBorderColor,
9092                                                 'stroke-width': optionsChart.plotBorderWidth,
9093                                                 zIndex: 4
9094                                         })
9095                                         .add();
9096                         } else {
9097                                 plotBorder.animate(
9098                                         plotBorder.crisp(null, plotLeft, plotTop, plotWidth, plotHeight)
9099                                 );
9100                         }
9101                 }
9102
9103                 // reset
9104                 chart.isDirtyBox = false;
9105         };
9106
9107         /**
9108          * Detect whether the chart is inverted, either by setting the chart.inverted option
9109          * or adding a bar series to the configuration options
9110          */
9111         function setInverted() {
9112                 var BAR = 'bar',
9113                         isInverted = (
9114                                 inverted || // it is set before
9115                                 optionsChart.inverted ||
9116                                 optionsChart.type === BAR || // default series type
9117                                 optionsChart.defaultSeriesType === BAR // backwards compatible
9118                         ),
9119                         seriesOptions = options.series,
9120                         i = seriesOptions && seriesOptions.length;
9121
9122                 // check if a bar series is present in the config options
9123                 while (!isInverted && i--) {
9124                         if (seriesOptions[i].type === BAR) {
9125                                 isInverted = true;
9126                         }
9127                 }
9128
9129                 // set the chart property and the chart scope variable
9130                 chart.inverted = inverted = isInverted;
9131         }
9132
9133         /**
9134          * Render all graphics for the chart
9135          */
9136         function render() {
9137                 var labels = options.labels,
9138                         credits = options.credits,
9139                         creditsHref;
9140
9141                 // Title
9142                 setTitle();
9143
9144
9145                 // Legend
9146                 legend = chart.legend = new Legend();
9147
9148                 // Get margins by pre-rendering axes
9149                 // set axes scales
9150                 each(axes, function (axis) {
9151                         axis.setScale();
9152                 });
9153                 getMargins();
9154                 each(axes, function (axis) {
9155                         axis.setTickPositions(true); // update to reflect the new margins
9156                 });
9157                 adjustTickAmounts();
9158                 getMargins(); // second pass to check for new labels
9159
9160
9161                 // Draw the borders and backgrounds
9162                 drawChartBox();
9163
9164                 // Axes
9165                 if (hasCartesianSeries) {
9166                         each(axes, function (axis) {
9167                                 axis.render();
9168                         });
9169                 }
9170
9171
9172                 // The series
9173                 if (!chart.seriesGroup) {
9174                         chart.seriesGroup = renderer.g('series-group')
9175                                 .attr({ zIndex: 3 })
9176                                 .add();
9177                 }
9178                 each(series, function (serie) {
9179                         serie.translate();
9180                         serie.setTooltipPoints();
9181                         serie.render();
9182                 });
9183
9184
9185                 // Labels
9186                 if (labels.items) {
9187                         each(labels.items, function () {
9188                                 var style = extend(labels.style, this.style),
9189                                         x = pInt(style.left) + plotLeft,
9190                                         y = pInt(style.top) + plotTop + 12;
9191
9192                                 // delete to prevent rewriting in IE
9193                                 delete style.left;
9194                                 delete style.top;
9195
9196                                 renderer.text(
9197                                         this.html,
9198                                         x,
9199                                         y
9200                                 )
9201                                 .attr({ zIndex: 2 })
9202                                 .css(style)
9203                                 .add();
9204
9205                         });
9206                 }
9207
9208                 // Credits
9209                 if (credits.enabled && !chart.credits) {
9210                         creditsHref = credits.href;
9211                         chart.credits = renderer.text(
9212                                 credits.text,
9213                                 0,
9214                                 0
9215                         )
9216                         .on('click', function () {
9217                                 if (creditsHref) {
9218                                         location.href = creditsHref;
9219                                 }
9220                         })
9221                         .attr({
9222                                 align: credits.position.align,
9223                                 zIndex: 8
9224                         })
9225                         .css(credits.style)
9226                         .add()
9227                         .align(credits.position);
9228                 }
9229
9230                 placeTrackerGroup();
9231
9232                 // Set flag
9233                 chart.hasRendered = true;
9234
9235                 // If the chart was rendered outside the top container, put it back in
9236                 if (renderToClone) {
9237                         renderTo.appendChild(container);
9238                         discardElement(renderToClone);
9239                         //updatePosition(container);
9240                 }
9241         }
9242
9243         /**
9244          * Clean up memory usage
9245          */
9246         function destroy() {
9247                 var i,
9248                         parentNode = container && container.parentNode;
9249
9250                 // If the chart is destroyed already, do nothing.
9251                 // This will happen if if a script invokes chart.destroy and
9252                 // then it will be called again on win.unload
9253                 if (chart === null) {
9254                         return;
9255                 }
9256
9257                 // fire the chart.destoy event
9258                 fireEvent(chart, 'destroy');
9259
9260                 // remove events
9261                 removeEvent(chart);
9262
9263                 // ==== Destroy collections:
9264                 // Destroy axes
9265                 i = axes.length;
9266                 while (i--) {
9267                         axes[i] = axes[i].destroy();
9268                 }
9269
9270                 // Destroy each series
9271                 i = series.length;
9272                 while (i--) {
9273                         series[i] = series[i].destroy();
9274                 }
9275
9276                 // ==== Destroy chart properties:
9277                 each(['title', 'subtitle', 'seriesGroup', 'clipRect', 'credits', 'tracker', 'scroller', 'rangeSelector'], function (name) {
9278                         var prop = chart[name];
9279
9280                         if (prop) {
9281                                 chart[name] = prop.destroy();
9282                         }
9283                 });
9284
9285                 // ==== Destroy local variables:
9286                 each([chartBackground, plotBorder, plotBackground, legend, tooltip, renderer, tracker], function (obj) {
9287                         if (obj && obj.destroy) {
9288                                 obj.destroy();
9289                         }
9290                 });
9291                 chartBackground = plotBorder = plotBackground = legend = tooltip = renderer = tracker = null;
9292
9293                 // remove container and all SVG
9294                 if (container) { // can break in IE when destroyed before finished loading
9295                         container.innerHTML = '';
9296                         removeEvent(container);
9297                         if (parentNode) {
9298                                 discardElement(container);
9299                         }
9300
9301                         // IE6 leak
9302                         container = null;
9303                 }
9304
9305                 // memory and CPU leak
9306                 clearInterval(tooltipInterval);
9307
9308                 // clean it all up
9309                 for (i in chart) {
9310                         delete chart[i];
9311                 }
9312
9313                 chart = null;
9314                 options = null;
9315         }
9316         /**
9317          * Prepare for first rendering after all data are loaded
9318          */
9319         function firstRender() {
9320
9321                 // VML namespaces can't be added until after complete. Listening
9322                 // for Perini's doScroll hack is not enough.
9323                 var ONREADYSTATECHANGE = 'onreadystatechange',
9324                 COMPLETE = 'complete';
9325                 // Note: in spite of JSLint's complaints, win == win.top is required
9326                 /*jslint eqeq: true*/
9327                 if (!hasSVG && win == win.top && doc.readyState !== COMPLETE) {
9328                 /*jslint eqeq: false*/
9329                         doc.attachEvent(ONREADYSTATECHANGE, function () {
9330                                 doc.detachEvent(ONREADYSTATECHANGE, firstRender);
9331                                 if (doc.readyState === COMPLETE) {
9332                                         firstRender();
9333                                 }
9334                         });
9335                         return;
9336                 }
9337
9338                 // create the container
9339                 getContainer();
9340
9341                 // Run an early event after the container and renderer are established
9342                 fireEvent(chart, 'init');
9343
9344                 // Initialize range selector for stock charts
9345                 if (Highcharts.RangeSelector && options.rangeSelector.enabled) {
9346                         chart.rangeSelector = new Highcharts.RangeSelector(chart);
9347                 }
9348
9349                 resetMargins();
9350                 setChartSize();
9351
9352                 // Set the common inversion and transformation for inverted series after initSeries
9353                 setInverted();
9354
9355                 // get axes
9356                 getAxes();
9357
9358                 // Initialize the series
9359                 each(options.series || [], function (serieOptions) {
9360                         initSeries(serieOptions);
9361                 });
9362
9363                 // Run an event where series and axes can be added
9364                 //fireEvent(chart, 'beforeRender');
9365
9366                 // Initialize scroller for stock charts
9367                 if (Highcharts.Scroller && (options.navigator.enabled || options.scrollbar.enabled)) {
9368                         chart.scroller = new Highcharts.Scroller(chart);
9369                 }
9370
9371                 chart.render = render;
9372
9373                 // depends on inverted and on margins being set
9374                 chart.tracker = tracker = new MouseTracker(options.tooltip);
9375
9376
9377                 render();
9378
9379                 // run callbacks
9380                 if (callback) {
9381                         callback.apply(chart, [chart]);
9382                 }
9383                 each(chart.callbacks, function (fn) {
9384                         fn.apply(chart, [chart]);
9385                 });
9386
9387                 fireEvent(chart, 'load');
9388
9389         }
9390
9391         // Run chart
9392
9393         // Set up auto resize
9394         if (optionsChart.reflow !== false) {
9395                 addEvent(chart, 'load', initReflow);
9396         }
9397
9398         // Chart event handlers
9399         if (chartEvents) {
9400                 for (eventType in chartEvents) {
9401                         addEvent(chart, eventType, chartEvents[eventType]);
9402                 }
9403         }
9404
9405
9406         chart.options = options;
9407         chart.series = series;
9408
9409
9410         chart.xAxis = [];
9411         chart.yAxis = [];
9412
9413
9414
9415
9416         // Expose methods and variables
9417         chart.addSeries = addSeries;
9418         chart.animation = pick(optionsChart.animation, true);
9419         chart.Axis = Axis;
9420         chart.destroy = destroy;
9421         chart.get = get;
9422         chart.getSelectedPoints = getSelectedPoints;
9423         chart.getSelectedSeries = getSelectedSeries;
9424         chart.hideLoading = hideLoading;
9425         chart.initSeries = initSeries;
9426         chart.isInsidePlot = isInsidePlot;
9427         chart.redraw = redraw;
9428         chart.setSize = resize;
9429         chart.setTitle = setTitle;
9430         chart.showLoading = showLoading;
9431         chart.pointCount = 0;
9432         chart.counters = new ChartCounters();
9433         /*
9434         if ($) $(function () {
9435                 $container = $('#container');
9436                 var origChartWidth,
9437                         origChartHeight;
9438                 if ($container) {
9439                         $('<button>+</button>')
9440                                 .insertBefore($container)
9441                                 .click(function () {
9442                                         if (origChartWidth === UNDEFINED) {
9443                                                 origChartWidth = chartWidth;
9444                                                 origChartHeight = chartHeight;
9445                                         }
9446                                         chart.resize(chartWidth *= 1.1, chartHeight *= 1.1);
9447                                 });
9448                         $('<button>-</button>')
9449                                 .insertBefore($container)
9450                                 .click(function () {
9451                                         if (origChartWidth === UNDEFINED) {
9452                                                 origChartWidth = chartWidth;
9453                                                 origChartHeight = chartHeight;
9454                                         }
9455                                         chart.resize(chartWidth *= 0.9, chartHeight *= 0.9);
9456                                 });
9457                         $('<button>1:1</button>')
9458                                 .insertBefore($container)
9459                                 .click(function () {
9460                                         if (origChartWidth === UNDEFINED) {
9461                                                 origChartWidth = chartWidth;
9462                                                 origChartHeight = chartHeight;
9463                                         }
9464                                         chart.resize(origChartWidth, origChartHeight);
9465                                 });
9466                 }
9467         })
9468         */
9469
9470
9471
9472
9473         firstRender();
9474
9475
9476 } // end Chart
9477
9478 // Hook for exporting module
9479 Chart.prototype.callbacks = [];
9480 /**
9481  * The Point object and prototype. Inheritable and used as base for PiePoint
9482  */
9483 var Point = function () {};
9484 Point.prototype = {
9485
9486         /**
9487          * Initialize the point
9488          * @param {Object} series The series object containing this point
9489          * @param {Object} options The data in either number, array or object format
9490          */
9491         init: function (series, options, x) {
9492                 var point = this,
9493                         counters = series.chart.counters,
9494                         defaultColors;
9495                 point.series = series;
9496                 point.applyOptions(options, x);
9497                 point.pointAttr = {};
9498
9499                 if (series.options.colorByPoint) {
9500                         defaultColors = series.chart.options.colors;
9501                         if (!point.options) {
9502                                 point.options = {};
9503                         }
9504                         point.color = point.options.color = point.color || defaultColors[counters.color++];
9505
9506                         // loop back to zero
9507                         counters.wrapColor(defaultColors.length);
9508                 }
9509
9510                 series.chart.pointCount++;
9511                 return point;
9512         },
9513         /**
9514          * Apply the options containing the x and y data and possible some extra properties.
9515          * This is called on point init or from point.update.
9516          *
9517          * @param {Object} options
9518          */
9519         applyOptions: function (options, x) {
9520                 var point = this,
9521                         series = point.series,
9522                         optionsType = typeof options;
9523
9524                 point.config = options;
9525
9526                 // onedimensional array input
9527                 if (optionsType === 'number' || options === null) {
9528                         point.y = options;
9529                 } else if (typeof options[0] === 'number') { // two-dimentional array
9530                         point.x = options[0];
9531                         point.y = options[1];
9532                 } else if (optionsType === 'object' && typeof options.length !== 'number') { // object input
9533                         // copy options directly to point
9534                         extend(point, options);
9535                         point.options = options;
9536                 } else if (typeof options[0] === 'string') { // categorized data with name in first position
9537                         point.name = options[0];
9538                         point.y = options[1];
9539                 }
9540
9541                 /*
9542                  * If no x is set by now, get auto incremented value. All points must have an
9543                  * x value, however the y value can be null to create a gap in the series
9544                  */
9545
9546                 // todo: skip this? It is only used in applyOptions, in translate it should not be used
9547                 if (point.x === UNDEFINED) {
9548                         point.x = x === UNDEFINED ? series.autoIncrement() : x;
9549                 }
9550
9551         },
9552
9553         /**
9554          * Destroy a point to clear memory. Its reference still stays in series.data.
9555          */
9556         destroy: function () {
9557                 var point = this,
9558                         series = point.series,
9559                         hoverPoints = series.chart.hoverPoints,
9560                         prop;
9561
9562                 series.chart.pointCount--;
9563
9564                 if (hoverPoints) {
9565                         point.setState();
9566                         erase(hoverPoints, point);
9567                 }
9568                 if (point === series.chart.hoverPoint) {
9569                         point.onMouseOut();
9570                 }
9571                 series.chart.hoverPoints = null;
9572
9573                 // remove all events
9574                 if (point.graphic || point.dataLabel) { // removeEvent and destroyElements are performance expensive
9575                         removeEvent(point);
9576                         point.destroyElements();
9577                 }
9578
9579                 if (point.legendItem) { // pies have legend items
9580                         point.series.chart.legend.destroyItem(point);
9581                 }
9582
9583                 for (prop in point) {
9584                         point[prop] = null;
9585                 }
9586
9587
9588         },
9589
9590         /**
9591          * Destroy SVG elements associated with the point
9592          */
9593         destroyElements: function () {
9594                 var point = this,
9595                         props = ['graphic', 'tracker', 'dataLabel', 'group', 'connector', 'shadowGroup'],
9596                         prop,
9597                         i = 6;
9598                 while (i--) {
9599                         prop = props[i];
9600                         if (point[prop]) {
9601                                 point[prop] = point[prop].destroy();
9602                         }
9603                 }
9604         },
9605
9606         /**
9607          * Return the configuration hash needed for the data label and tooltip formatters
9608          */
9609         getLabelConfig: function () {
9610                 var point = this;
9611                 return {
9612                         x: point.category,
9613                         y: point.y,
9614                         key: point.name || point.category,
9615                         series: point.series,
9616                         point: point,
9617                         percentage: point.percentage,
9618                         total: point.total || point.stackTotal
9619                 };
9620         },
9621
9622         /**
9623          * Toggle the selection status of a point
9624          * @param {Boolean} selected Whether to select or unselect the point.
9625          * @param {Boolean} accumulate Whether to add to the previous selection. By default,
9626          *     this happens if the control key (Cmd on Mac) was pressed during clicking.
9627          */
9628         select: function (selected, accumulate) {
9629                 var point = this,
9630                         series = point.series,
9631                         chart = series.chart;
9632
9633                 selected = pick(selected, !point.selected);
9634
9635                 // fire the event with the defalut handler
9636                 point.firePointEvent(selected ? 'select' : 'unselect', { accumulate: accumulate }, function () {
9637                         point.selected = selected;
9638                         point.setState(selected && SELECT_STATE);
9639
9640                         // unselect all other points unless Ctrl or Cmd + click
9641                         if (!accumulate) {
9642                                 each(chart.getSelectedPoints(), function (loopPoint) {
9643                                         if (loopPoint.selected && loopPoint !== point) {
9644                                                 loopPoint.selected = false;
9645                                                 loopPoint.setState(NORMAL_STATE);
9646                                                 loopPoint.firePointEvent('unselect');
9647                                         }
9648                                 });
9649                         }
9650                 });
9651         },
9652
9653         onMouseOver: function () {
9654                 var point = this,
9655                         series = point.series,
9656                         chart = series.chart,
9657                         tooltip = chart.tooltip,
9658                         hoverPoint = chart.hoverPoint;
9659
9660                 // set normal state to previous series
9661                 if (hoverPoint && hoverPoint !== point) {
9662                         hoverPoint.onMouseOut();
9663                 }
9664
9665                 // trigger the event
9666                 point.firePointEvent('mouseOver');
9667
9668                 // update the tooltip
9669                 if (tooltip && (!tooltip.shared || series.noSharedTooltip)) {
9670                         tooltip.refresh(point);
9671                 }
9672
9673                 // hover this
9674                 point.setState(HOVER_STATE);
9675                 chart.hoverPoint = point;
9676         },
9677
9678         onMouseOut: function () {
9679                 var point = this;
9680                 point.firePointEvent('mouseOut');
9681
9682                 point.setState();
9683                 point.series.chart.hoverPoint = null;
9684         },
9685
9686         /**
9687          * Extendable method for formatting each point's tooltip line
9688          *
9689          * @return {String} A string to be concatenated in to the common tooltip text
9690          */
9691         tooltipFormatter: function (pointFormat) {
9692                 var point = this,
9693                         series = point.series,
9694                         seriesTooltipOptions = series.tooltipOptions,
9695                         split = String(point.y).split('.'),
9696                         originalDecimals = split[1] ? split[1].length : 0,
9697                         match = pointFormat.match(/\{(series|point)\.[a-zA-Z]+\}/g),
9698                         splitter = /[\.}]/,
9699                         obj,
9700                         key,
9701                         replacement,
9702                         i;
9703
9704                 // loop over the variables defined on the form {series.name}, {point.y} etc
9705                 for (i in match) {
9706                         key = match[i];
9707                         
9708                         if (isString(key) && key !== pointFormat) { // IE matches more than just the variables 
9709                                 obj = key.indexOf('point') === 1 ? point : series;
9710                                 
9711                                 if (key === '{point.y}') { // add some preformatting 
9712                                         replacement = (seriesTooltipOptions.valuePrefix || seriesTooltipOptions.yPrefix || '') + 
9713                                                 numberFormat(point.y, pick(seriesTooltipOptions.valueDecimals, seriesTooltipOptions.yDecimals, originalDecimals)) +
9714                                                 (seriesTooltipOptions.valueSuffix || seriesTooltipOptions.ySuffix || '');
9715                                 
9716                                 } else { // automatic replacement
9717                                         replacement = obj[match[i].split(splitter)[1]];
9718                                 }
9719                                 
9720                                 pointFormat = pointFormat.replace(match[i], replacement);
9721                         }
9722                 }
9723                 
9724                 return pointFormat;
9725         },
9726
9727         /**
9728          * Update the point with new options (typically x/y data) and optionally redraw the series.
9729          *
9730          * @param {Object} options Point options as defined in the series.data array
9731          * @param {Boolean} redraw Whether to redraw the chart or wait for an explicit call
9732          * @param {Boolean|Object} animation Whether to apply animation, and optionally animation
9733          *    configuration
9734          *
9735          */
9736         update: function (options, redraw, animation) {
9737                 var point = this,
9738                         series = point.series,
9739                         graphic = point.graphic,
9740                         i,
9741                         data = series.data,
9742                         dataLength = data.length,
9743                         chart = series.chart;
9744
9745                 redraw = pick(redraw, true);
9746
9747                 // fire the event with a default handler of doing the update
9748                 point.firePointEvent('update', { options: options }, function () {
9749
9750                         point.applyOptions(options);
9751
9752                         // update visuals
9753                         if (isObject(options)) {
9754                                 series.getAttribs();
9755                                 if (graphic) {
9756                                         graphic.attr(point.pointAttr[series.state]);
9757                                 }
9758                         }
9759
9760                         // record changes in the parallel arrays
9761                         for (i = 0; i < dataLength; i++) {
9762                                 if (data[i] === point) {
9763                                         series.xData[i] = point.x;
9764                                         series.yData[i] = point.y;
9765                                         series.options.data[i] = options;
9766                                         break;
9767                                 }
9768                         }
9769
9770                         // redraw
9771                         series.isDirty = true;
9772                         series.isDirtyData = true;
9773                         if (redraw) {
9774                                 chart.redraw(animation);
9775                         }
9776                 });
9777         },
9778
9779         /**
9780          * Remove a point and optionally redraw the series and if necessary the axes
9781          * @param {Boolean} redraw Whether to redraw the chart or wait for an explicit call
9782          * @param {Boolean|Object} animation Whether to apply animation, and optionally animation
9783          *    configuration
9784          */
9785         remove: function (redraw, animation) {
9786                 var point = this,
9787                         series = point.series,
9788                         chart = series.chart,
9789                         i,
9790                         data = series.data,
9791                         dataLength = data.length;
9792
9793                 setAnimation(animation, chart);
9794                 redraw = pick(redraw, true);
9795
9796                 // fire the event with a default handler of removing the point
9797                 point.firePointEvent('remove', null, function () {
9798
9799                         //erase(series.data, point);
9800
9801                         for (i = 0; i < dataLength; i++) {
9802                                 if (data[i] === point) {
9803
9804                                         // splice all the parallel arrays
9805                                         data.splice(i, 1);
9806                                         series.options.data.splice(i, 1);
9807                                         series.xData.splice(i, 1);
9808                                         series.yData.splice(i, 1);
9809                                         break;
9810                                 }
9811                         }
9812
9813                         point.destroy();
9814
9815
9816                         // redraw
9817                         series.isDirty = true;
9818                         series.isDirtyData = true;
9819                         if (redraw) {
9820                                 chart.redraw();
9821                         }
9822                 });
9823
9824
9825         },
9826
9827         /**
9828          * Fire an event on the Point object. Must not be renamed to fireEvent, as this
9829          * causes a name clash in MooTools
9830          * @param {String} eventType
9831          * @param {Object} eventArgs Additional event arguments
9832          * @param {Function} defaultFunction Default event handler
9833          */
9834         firePointEvent: function (eventType, eventArgs, defaultFunction) {
9835                 var point = this,
9836                         series = this.series,
9837                         seriesOptions = series.options;
9838
9839                 // load event handlers on demand to save time on mouseover/out
9840                 if (seriesOptions.point.events[eventType] || (point.options && point.options.events && point.options.events[eventType])) {
9841                         this.importEvents();
9842                 }
9843
9844                 // add default handler if in selection mode
9845                 if (eventType === 'click' && seriesOptions.allowPointSelect) {
9846                         defaultFunction = function (event) {
9847                                 // Control key is for Windows, meta (= Cmd key) for Mac, Shift for Opera
9848                                 point.select(null, event.ctrlKey || event.metaKey || event.shiftKey);
9849                         };
9850                 }
9851
9852                 fireEvent(this, eventType, eventArgs, defaultFunction);
9853         },
9854         /**
9855          * Import events from the series' and point's options. Only do it on
9856          * demand, to save processing time on hovering.
9857          */
9858         importEvents: function () {
9859                 if (!this.hasImportedEvents) {
9860                         var point = this,
9861                                 options = merge(point.series.options.point, point.options),
9862                                 events = options.events,
9863                                 eventType;
9864
9865                         point.events = events;
9866
9867                         for (eventType in events) {
9868                                 addEvent(point, eventType, events[eventType]);
9869                         }
9870                         this.hasImportedEvents = true;
9871
9872                 }
9873         },
9874
9875         /**
9876          * Set the point's state
9877          * @param {String} state
9878          */
9879         setState: function (state) {
9880                 var point = this,
9881                         plotX = point.plotX,
9882                         plotY = point.plotY,
9883                         series = point.series,
9884                         stateOptions = series.options.states,
9885                         markerOptions = defaultPlotOptions[series.type].marker && series.options.marker,
9886                         normalDisabled = markerOptions && !markerOptions.enabled,
9887                         markerStateOptions = markerOptions && markerOptions.states[state],
9888                         stateDisabled = markerStateOptions && markerStateOptions.enabled === false,
9889                         stateMarkerGraphic = series.stateMarkerGraphic,
9890                         chart = series.chart,
9891                         radius,
9892                         pointAttr = point.pointAttr;
9893
9894                 state = state || NORMAL_STATE; // empty string
9895
9896                 if (
9897                                 // already has this state
9898                                 state === point.state ||
9899                                 // selected points don't respond to hover
9900                                 (point.selected && state !== SELECT_STATE) ||
9901                                 // series' state options is disabled
9902                                 (stateOptions[state] && stateOptions[state].enabled === false) ||
9903                                 // point marker's state options is disabled
9904                                 (state && (stateDisabled || (normalDisabled && !markerStateOptions.enabled)))
9905
9906                         ) {
9907                         return;
9908                 }
9909
9910                 // apply hover styles to the existing point
9911                 if (point.graphic) {
9912                         radius = point.graphic.symbolName && pointAttr[state].r;
9913                         point.graphic.attr(merge(
9914                                 pointAttr[state],
9915                                 radius ? { // new symbol attributes (#507, #612)
9916                                         x: plotX - radius,
9917                                         y: plotY - radius,
9918                                         width: 2 * radius,
9919                                         height: 2 * radius
9920                                 } : {}
9921                         ));
9922                 } else {
9923                         // if a graphic is not applied to each point in the normal state, create a shared
9924                         // graphic for the hover state
9925                         if (state) {
9926                                 if (!stateMarkerGraphic) {
9927                                         radius = markerOptions.radius;
9928                                         series.stateMarkerGraphic = stateMarkerGraphic = chart.renderer.symbol(
9929                                                 series.symbol,
9930                                                 -radius,
9931                                                 -radius,
9932                                                 2 * radius,
9933                                                 2 * radius
9934                                         )
9935                                         .attr(pointAttr[state])
9936                                         .add(series.group);
9937                                 }
9938
9939                                 stateMarkerGraphic.translate(
9940                                         plotX,
9941                                         plotY
9942                                 );
9943                         }
9944
9945                         if (stateMarkerGraphic) {
9946                                 stateMarkerGraphic[state ? 'show' : 'hide']();
9947                         }
9948                 }
9949
9950                 point.state = state;
9951         }
9952 };
9953
9954 /**
9955  * @classDescription The base function which all other series types inherit from. The data in the series is stored
9956  * in various arrays.
9957  *
9958  * - First, series.options.data contains all the original config options for
9959  * each point whether added by options or methods like series.addPoint.
9960  * - Next, series.data contains those values converted to points, but in case the series data length
9961  * exceeds the cropThreshold, or if the data is grouped, series.data doesn't contain all the points. It
9962  * only contains the points that have been created on demand.
9963  * - Then there's series.points that contains all currently visible point objects. In case of cropping,
9964  * the cropped-away points are not part of this array. The series.points array starts at series.cropStart
9965  * compared to series.data and series.options.data. If however the series data is grouped, these can't
9966  * be correlated one to one.
9967  * - series.xData and series.processedXData contain clean x values, equivalent to series.data and series.points.
9968  * - series.yData and series.processedYData contain clean x values, equivalent to series.data and series.points.
9969  *
9970  * @param {Object} chart
9971  * @param {Object} options
9972  */
9973 var Series = function () {};
9974
9975 Series.prototype = {
9976
9977         isCartesian: true,
9978         type: 'line',
9979         pointClass: Point,
9980         pointAttrToOptions: { // mapping between SVG attributes and the corresponding options
9981                 stroke: 'lineColor',
9982                 'stroke-width': 'lineWidth',
9983                 fill: 'fillColor',
9984                 r: 'radius'
9985         },
9986         init: function (chart, options) {
9987                 var series = this,
9988                         eventType,
9989                         events,
9990                         //pointEvent,
9991                         index = chart.series.length;
9992
9993                 series.chart = chart;
9994                 series.options = options = series.setOptions(options); // merge with plotOptions
9995                 
9996                 // bind the axes
9997                 series.bindAxes();
9998
9999                 // set some variables
10000                 extend(series, {
10001                         index: index,
10002                         name: options.name || 'Series ' + (index + 1),
10003                         state: NORMAL_STATE,
10004                         pointAttr: {},
10005                         visible: options.visible !== false, // true by default
10006                         selected: options.selected === true // false by default
10007                 });
10008                 
10009                 // register event listeners
10010                 events = options.events;
10011                 for (eventType in events) {
10012                         addEvent(series, eventType, events[eventType]);
10013                 }
10014                 if (
10015                         (events && events.click) ||
10016                         (options.point && options.point.events && options.point.events.click) ||
10017                         options.allowPointSelect
10018                 ) {
10019                         chart.runTrackerClick = true;
10020                 }
10021
10022                 series.getColor();
10023                 series.getSymbol();
10024
10025                 // set the data
10026                 series.setData(options.data, false);
10027
10028         },
10029         
10030         
10031         
10032         /**
10033          * Set the xAxis and yAxis properties of cartesian series, and register the series
10034          * in the axis.series array
10035          */
10036         bindAxes: function () {
10037                 var series = this,
10038                         seriesOptions = series.options,
10039                         chart = series.chart,
10040                         axisOptions;
10041                         
10042                 if (series.isCartesian) {
10043                         
10044                         each(['xAxis', 'yAxis'], function (AXIS) { // repeat for xAxis and yAxis
10045                                 
10046                                 each(chart[AXIS], function (axis) { // loop through the chart's axis objects
10047                                         
10048                                         axisOptions = axis.options;
10049                                         
10050                                         // apply if the series xAxis or yAxis option mathches the number of the 
10051                                         // axis, or if undefined, use the first axis
10052                                         if ((seriesOptions[AXIS] === axisOptions.index) ||
10053                                                         (seriesOptions[AXIS] === UNDEFINED && axisOptions.index === 0)) {
10054                                                 
10055                                                 // register this series in the axis.series lookup
10056                                                 axis.series.push(series);
10057                                                 
10058                                                 // set this series.xAxis or series.yAxis reference
10059                                                 series[AXIS] = axis;
10060                                                 
10061                                                 // mark dirty for redraw
10062                                                 axis.isDirty = true;
10063                                         }
10064                                 });
10065                                 
10066                         });
10067                 }
10068         },
10069
10070
10071         /**
10072          * Return an auto incremented x value based on the pointStart and pointInterval options.
10073          * This is only used if an x value is not given for the point that calls autoIncrement.
10074          */
10075         autoIncrement: function () {
10076                 var series = this,
10077                         options = series.options,
10078                         xIncrement = series.xIncrement;
10079
10080                 xIncrement = pick(xIncrement, options.pointStart, 0);
10081
10082                 series.pointInterval = pick(series.pointInterval, options.pointInterval, 1);
10083
10084                 series.xIncrement = xIncrement + series.pointInterval;
10085                 return xIncrement;
10086         },
10087
10088         /**
10089          * Divide the series data into segments divided by null values.
10090          */
10091         getSegments: function () {
10092                 var series = this,
10093                         lastNull = -1,
10094                         segments = [],
10095                         i,
10096                         points = series.points,
10097                         pointsLength = points.length;
10098
10099                 if (pointsLength) { // no action required for []
10100                         
10101                         // if connect nulls, just remove null points
10102                         if (series.options.connectNulls) {
10103                                 i = pointsLength;
10104                                 while (i--) {
10105                                         if (points[i].y === null) {
10106                                                 points.splice(i, 1);
10107                                         }
10108                                 }
10109                                 segments = [points];
10110                                 
10111                         // else, split on null points
10112                         } else {
10113                                 each(points, function (point, i) {
10114                                         if (point.y === null) {
10115                                                 if (i > lastNull + 1) {
10116                                                         segments.push(points.slice(lastNull + 1, i));
10117                                                 }
10118                                                 lastNull = i;
10119                                         } else if (i === pointsLength - 1) { // last value
10120                                                 segments.push(points.slice(lastNull + 1, i + 1));
10121                                         }
10122                                 });
10123                         }
10124                 }
10125                 
10126                 // register it
10127                 series.segments = segments;
10128         },
10129         /**
10130          * Set the series options by merging from the options tree
10131          * @param {Object} itemOptions
10132          */
10133         setOptions: function (itemOptions) {
10134                 var series = this,
10135                         chart = series.chart,
10136                         chartOptions = chart.options,
10137                         plotOptions = chartOptions.plotOptions,
10138                         data = itemOptions.data,
10139                         options;
10140
10141                 itemOptions.data = null; // remove from merge to prevent looping over the data set
10142
10143                 options = merge(
10144                         plotOptions[this.type],
10145                         plotOptions.series,
10146                         itemOptions
10147                 );
10148                 
10149                 // Re-insert the data array to the options and the original config (#717)
10150                 options.data = itemOptions.data = data;
10151                 
10152                 // the tooltip options are merged between global and series specific options
10153                 series.tooltipOptions = merge(chartOptions.tooltip, options.tooltip);
10154
10155                 return options;
10156
10157         },
10158         /**
10159          * Get the series' color
10160          */
10161         getColor: function () {
10162                 var defaultColors = this.chart.options.colors,
10163                         counters = this.chart.counters;
10164                 this.color = this.options.color || defaultColors[counters.color++] || '#0000ff';
10165                 counters.wrapColor(defaultColors.length);
10166         },
10167         /**
10168          * Get the series' symbol
10169          */
10170         getSymbol: function () {
10171                 var series = this,
10172                         seriesMarkerOption = series.options.marker,
10173                         chart = series.chart,
10174                         defaultSymbols = chart.options.symbols,
10175                         counters = chart.counters;
10176                 series.symbol = seriesMarkerOption.symbol || defaultSymbols[counters.symbol++];
10177                 
10178                 // don't substract radius in image symbols (#604)
10179                 if (/^url/.test(series.symbol)) {
10180                         seriesMarkerOption.radius = 0;
10181                 }
10182                 counters.wrapSymbol(defaultSymbols.length);
10183         },
10184
10185         /**
10186          * Add a point dynamically after chart load time
10187          * @param {Object} options Point options as given in series.data
10188          * @param {Boolean} redraw Whether to redraw the chart or wait for an explicit call
10189          * @param {Boolean} shift If shift is true, a point is shifted off the start
10190          *    of the series as one is appended to the end.
10191          * @param {Boolean|Object} animation Whether to apply animation, and optionally animation
10192          *    configuration
10193          */
10194         addPoint: function (options, redraw, shift, animation) {
10195                 var series = this,
10196                         data = series.data,
10197                         graph = series.graph,
10198                         area = series.area,
10199                         chart = series.chart,
10200                         xData = series.xData,
10201                         yData = series.yData,
10202                         currentShift = (graph && graph.shift) || 0,
10203                         dataOptions = series.options.data,
10204                         point;
10205                         //point = (new series.pointClass()).init(series, options);
10206
10207                 setAnimation(animation, chart);
10208
10209                 if (graph && shift) { // make graph animate sideways
10210                         graph.shift = currentShift + 1;
10211                 }
10212                 if (area) {
10213                         area.shift = currentShift + 1;
10214                         area.isArea = true;
10215                 }
10216                 redraw = pick(redraw, true);
10217
10218
10219                 // Get options and push the point to xData, yData and series.options. In series.generatePoints
10220                 // the Point instance will be created on demand and pushed to the series.data array.
10221                 point = { series: series };
10222                 series.pointClass.prototype.applyOptions.apply(point, [options]);
10223                 xData.push(point.x);
10224                 yData.push(series.valueCount === 4 ? [point.open, point.high, point.low, point.close] : point.y);
10225                 dataOptions.push(options);
10226
10227
10228                 // Shift the first point off the parallel arrays
10229                 // todo: consider series.removePoint(i) method
10230                 if (shift) {
10231                         if (data[0]) {
10232                                 data[0].remove(false);
10233                         } else {
10234                                 data.shift();
10235                                 xData.shift();
10236                                 yData.shift();
10237                                 dataOptions.shift();
10238                         }
10239                 }
10240                 series.getAttribs();
10241
10242                 // redraw
10243                 series.isDirty = true;
10244                 series.isDirtyData = true;
10245                 if (redraw) {
10246                         chart.redraw();
10247                 }
10248         },
10249
10250         /**
10251          * Replace the series data with a new set of data
10252          * @param {Object} data
10253          * @param {Object} redraw
10254          */
10255         setData: function (data, redraw) {
10256                 var series = this,
10257                         oldData = series.points,
10258                         options = series.options,
10259                         initialColor = series.initialColor,
10260                         chart = series.chart,
10261                         firstPoint = null,
10262                         i;
10263
10264                 // reset properties
10265                 series.xIncrement = null;
10266                 series.pointRange = (series.xAxis && series.xAxis.categories && 1) || options.pointRange;
10267                 
10268                 if (defined(initialColor)) { // reset colors for pie
10269                         chart.counters.color = initialColor;
10270                 }
10271                 
10272                 // parallel arrays
10273                 var xData = [],
10274                         yData = [],
10275                         dataLength = data ? data.length : [],
10276                         turboThreshold = options.turboThreshold || 1000,
10277                         pt,
10278                         ohlc = series.valueCount === 4;
10279
10280                 // In turbo mode, only one- or twodimensional arrays of numbers are allowed. The
10281                 // first value is tested, and we assume that all the rest are defined the same
10282                 // way. Although the 'for' loops are similar, they are repeated inside each
10283                 // if-else conditional for max performance.
10284                 if (dataLength > turboThreshold) {
10285                         
10286                         // find the first non-null point
10287                         i = 0;
10288                         while (firstPoint === null && i < dataLength) {
10289                                 firstPoint = data[i];
10290                                 i++;
10291                         }
10292                 
10293                 
10294                         if (isNumber(firstPoint)) { // assume all points are numbers
10295                                 var x = pick(options.pointStart, 0),
10296                                         pointInterval = pick(options.pointInterval, 1);
10297
10298                                 for (i = 0; i < dataLength; i++) {
10299                                         xData[i] = x;
10300                                         yData[i] = data[i];
10301                                         x += pointInterval;
10302                                 }
10303                                 series.xIncrement = x;
10304                         } else if (isArray(firstPoint)) { // assume all points are arrays
10305                                 if (ohlc) { // [x, o, h, l, c]
10306                                         for (i = 0; i < dataLength; i++) {
10307                                                 pt = data[i];
10308                                                 xData[i] = pt[0];
10309                                                 yData[i] = pt.slice(1, 5);
10310                                         }
10311                                 } else { // [x, y]
10312                                         for (i = 0; i < dataLength; i++) {
10313                                                 pt = data[i];
10314                                                 xData[i] = pt[0];
10315                                                 yData[i] = pt[1];
10316                                         }
10317                                 }
10318                         } /* else {
10319                                 error(12); // Highcharts expects configs to be numbers or arrays in turbo mode
10320                         }*/
10321                 } else {
10322                         for (i = 0; i < dataLength; i++) {
10323                                 pt = { series: series };
10324                                 series.pointClass.prototype.applyOptions.apply(pt, [data[i]]);
10325                                 xData[i] = pt.x;
10326                                 yData[i] = ohlc ? [pt.open, pt.high, pt.low, pt.close] : pt.y;
10327                         }
10328                 }
10329
10330                 series.data = [];
10331                 series.options.data = data;
10332                 series.xData = xData;
10333                 series.yData = yData;
10334
10335                 // destroy old points
10336                 i = (oldData && oldData.length) || 0;
10337                 while (i--) {
10338                         if (oldData[i] && oldData[i].destroy) {
10339                                 oldData[i].destroy();
10340                         }
10341                 }
10342
10343                 // redraw
10344                 series.isDirty = series.isDirtyData = chart.isDirtyBox = true;
10345                 if (pick(redraw, true)) {
10346                         chart.redraw(false);
10347                 }
10348         },
10349
10350         /**
10351          * Remove a series and optionally redraw the chart
10352          *
10353          * @param {Boolean} redraw Whether to redraw the chart or wait for an explicit call
10354          * @param {Boolean|Object} animation Whether to apply animation, and optionally animation
10355          *    configuration
10356          */
10357
10358         remove: function (redraw, animation) {
10359                 var series = this,
10360                         chart = series.chart;
10361                 redraw = pick(redraw, true);
10362
10363                 if (!series.isRemoving) {  /* prevent triggering native event in jQuery
10364                                 (calling the remove function from the remove event) */
10365                         series.isRemoving = true;
10366
10367                         // fire the event with a default handler of removing the point
10368                         fireEvent(series, 'remove', null, function () {
10369
10370
10371                                 // destroy elements
10372                                 series.destroy();
10373
10374
10375                                 // redraw
10376                                 chart.isDirtyLegend = chart.isDirtyBox = true;
10377                                 if (redraw) {
10378                                         chart.redraw(animation);
10379                                 }
10380                         });
10381
10382                 }
10383                 series.isRemoving = false;
10384         },
10385
10386         /**
10387          * Process the data by cropping away unused data points if the series is longer
10388          * than the crop threshold. This saves computing time for lage series.
10389          */
10390         processData: function (force) {
10391                 var series = this,
10392                         processedXData = series.xData, // copied during slice operation below
10393                         processedYData = series.yData,
10394                         dataLength = processedXData.length,
10395                         cropStart = 0,
10396                         cropEnd = dataLength,
10397                         cropped,
10398                         distance,
10399                         closestPointRange,
10400                         xAxis = series.xAxis,
10401                         i, // loop variable
10402                         options = series.options,
10403                         cropThreshold = options.cropThreshold;
10404
10405                 // If the series data or axes haven't changed, don't go through this. Return false to pass
10406                 // the message on to override methods like in data grouping. 
10407                 if (series.isCartesian && !series.isDirty && !xAxis.isDirty && !series.yAxis.isDirty && !force) {
10408                         return false;
10409                 }
10410
10411                 // optionally filter out points outside the plot area
10412                 if (!cropThreshold || dataLength > cropThreshold || series.forceCrop) {
10413                         var extremes = xAxis.getExtremes(),
10414                                 min = extremes.min,
10415                                 max = extremes.max;
10416
10417                         // it's outside current extremes
10418                         if (processedXData[dataLength - 1] < min || processedXData[0] > max) {
10419                                 processedXData = [];
10420                                 processedYData = [];
10421                         
10422                         // only crop if it's actually spilling out
10423                         } else if (processedXData[0] < min || processedXData[dataLength - 1] > max) {
10424
10425                                 // iterate up to find slice start
10426                                 for (i = 0; i < dataLength; i++) {
10427                                         if (processedXData[i] >= min) {
10428                                                 cropStart = mathMax(0, i - 1);
10429                                                 break;
10430                                         }
10431                                 }
10432                                 // proceed to find slice end
10433                                 for (; i < dataLength; i++) {
10434                                         if (processedXData[i] > max) {
10435                                                 cropEnd = i + 1;
10436                                                 break;
10437                                         }
10438                                         
10439                                 }
10440                                 processedXData = processedXData.slice(cropStart, cropEnd);
10441                                 processedYData = processedYData.slice(cropStart, cropEnd);
10442                                 cropped = true;
10443                         }
10444                 }
10445                 
10446                 
10447                 // Find the closest distance between processed points
10448                 for (i = processedXData.length - 1; i > 0; i--) {
10449                         distance = processedXData[i] - processedXData[i - 1];
10450                         if (closestPointRange === UNDEFINED || distance < closestPointRange) {
10451                                 closestPointRange = distance;
10452                         }
10453                 }
10454                 
10455                 // Record the properties
10456                 series.cropped = cropped; // undefined or true
10457                 series.cropStart = cropStart;
10458                 series.processedXData = processedXData;
10459                 series.processedYData = processedYData;
10460                 
10461                 if (options.pointRange === null) { // null means auto, as for columns, candlesticks and OHLC
10462                         series.pointRange = closestPointRange || 1;
10463                 }
10464                 series.closestPointRange = closestPointRange;
10465                 
10466         },
10467
10468         /**
10469          * Generate the data point after the data has been processed by cropping away
10470          * unused points and optionally grouped in Highcharts Stock.
10471          */
10472         generatePoints: function () {
10473                 var series = this,
10474                         options = series.options,
10475                         dataOptions = options.data,
10476                         data = series.data,
10477                         dataLength,
10478                         processedXData = series.processedXData,
10479                         processedYData = series.processedYData,
10480                         pointClass = series.pointClass,
10481                         processedDataLength = processedXData.length,
10482                         cropStart = series.cropStart || 0,
10483                         cursor,
10484                         hasGroupedData = series.hasGroupedData,
10485                         point,
10486                         points = [],
10487                         i;
10488
10489                 if (!data && !hasGroupedData) {
10490                         var arr = [];
10491                         arr.length = dataOptions.length;
10492                         data = series.data = arr;
10493                 }
10494
10495                 for (i = 0; i < processedDataLength; i++) {
10496                         cursor = cropStart + i;
10497                         if (!hasGroupedData) {
10498                                 if (data[cursor]) {
10499                                         point = data[cursor];
10500                                 } else {
10501                                         data[cursor] = point = (new pointClass()).init(series, dataOptions[cursor], processedXData[i]);
10502                                 }
10503                                 points[i] = point;
10504                         } else {
10505                                 // splat the y data in case of ohlc data array
10506                                 points[i] = (new pointClass()).init(series, [processedXData[i]].concat(splat(processedYData[i])));
10507                         }
10508                 }
10509
10510                 // Hide cropped-away points - this only runs when the number of points is above cropThreshold, or when
10511                 // swithching view from non-grouped data to grouped data (#637) 
10512                 if (data && (processedDataLength !== (dataLength = data.length) || hasGroupedData)) {
10513                         for (i = 0; i < dataLength; i++) {
10514                                 if (i === cropStart && !hasGroupedData) { // when has grouped data, clear all points
10515                                         i += processedDataLength;
10516                                 }
10517                                 if (data[i]) {
10518                                         data[i].destroyElements();
10519                                 }
10520                         }
10521                 }
10522
10523                 series.data = data;
10524                 series.points = points;
10525         },
10526
10527         /**
10528          * Translate data points from raw data values to chart specific positioning data
10529          * needed later in drawPoints, drawGraph and drawTracker.
10530          */
10531         translate: function () {
10532                 if (!this.processedXData) { // hidden series
10533                         this.processData();
10534                 }
10535                 this.generatePoints();
10536                 var series = this,
10537                         chart = series.chart,
10538                         options = series.options,
10539                         stacking = options.stacking,
10540                         xAxis = series.xAxis,
10541                         categories = xAxis.categories,
10542                         yAxis = series.yAxis,
10543                         points = series.points,
10544                         dataLength = points.length,
10545                         hasModifyValue = !!series.modifyValue,
10546                         isLastSeries = series.index === yAxis.series.length - 1,
10547                         i;
10548                 
10549                 for (i = 0; i < dataLength; i++) {
10550                         var point = points[i],
10551                                 xValue = point.x,
10552                                 yValue = point.y,
10553                                 yBottom = point.low,
10554                                 stack = yAxis.stacks[(yValue < options.threshold ? '-' : '') + series.stackKey],
10555                                 pointStack,
10556                                 pointStackTotal;
10557                                 
10558                         // get the plotX translation
10559                         point.plotX = mathRound(xAxis.translate(xValue) * 10) / 10; // Math.round fixes #591
10560
10561                         // calculate the bottom y value for stacked series
10562                         if (stacking && series.visible && stack && stack[xValue]) {
10563                                 pointStack = stack[xValue];
10564                                 pointStackTotal = pointStack.total;
10565                                 pointStack.cum = yBottom = pointStack.cum - yValue; // start from top
10566                                 yValue = yBottom + yValue;
10567                                 
10568                                 if (isLastSeries) {
10569                                         yBottom = options.threshold;
10570                                 }
10571                                 
10572                                 if (stacking === 'percent') {
10573                                         yBottom = pointStackTotal ? yBottom * 100 / pointStackTotal : 0;
10574                                         yValue = pointStackTotal ? yValue * 100 / pointStackTotal : 0;
10575                                 }
10576
10577                                 point.percentage = pointStackTotal ? point.y * 100 / pointStackTotal : 0;
10578                                 point.stackTotal = pointStackTotal;
10579                         }
10580
10581                         if (defined(yBottom)) {
10582                                 point.yBottom = yAxis.translate(yBottom, 0, 1, 0, 1);
10583                         }
10584                         
10585                         // general hook, used for Highstock compare mode
10586                         if (hasModifyValue) {
10587                                 yValue = series.modifyValue(yValue, point);
10588                         }
10589
10590                         // set the y value
10591                         if (yValue !== null) {
10592                                 point.plotY = mathRound(yAxis.translate(yValue, 0, 1, 0, 1) * 10) / 10; // Math.round fixes #591
10593                         }
10594
10595                         // set client related positions for mouse tracking
10596                         point.clientX = chart.inverted ?
10597                                 chart.plotHeight - point.plotX :
10598                                 point.plotX; // for mouse tracking
10599
10600                         // some API data
10601                         point.category = categories && categories[point.x] !== UNDEFINED ?
10602                                 categories[point.x] : point.x;
10603
10604
10605                 }
10606
10607                 // now that we have the cropped data, build the segments
10608                 series.getSegments();
10609         },
10610         /**
10611          * Memoize tooltip texts and positions
10612          */
10613         setTooltipPoints: function (renew) {
10614                 var series = this,
10615                         chart = series.chart,
10616                         inverted = chart.inverted,
10617                         points = [],
10618                         pointsLength,
10619                         plotSize = mathRound((inverted ? chart.plotTop : chart.plotLeft) + chart.plotSizeX),
10620                         low,
10621                         high,
10622                         xAxis = series.xAxis,
10623                         point,
10624                         i,
10625                         tooltipPoints = []; // a lookup array for each pixel in the x dimension
10626
10627                 // don't waste resources if tracker is disabled
10628                 if (series.options.enableMouseTracking === false) {
10629                         return;
10630                 }
10631
10632                 // renew
10633                 if (renew) {
10634                         series.tooltipPoints = null;
10635                 }
10636
10637                 // concat segments to overcome null values
10638                 each(series.segments || series.points, function (segment) {
10639                         points = points.concat(segment);
10640                 });
10641
10642                 // loop the concatenated points and apply each point to all the closest
10643                 // pixel positions
10644                 if (xAxis && xAxis.reversed) {
10645                         points = points.reverse();//reverseArray(points);
10646                 }
10647
10648                 //each(points, function (point, i) {
10649                 pointsLength = points.length;
10650                 for (i = 0; i < pointsLength; i++) {
10651                         point = points[i];
10652                         low = points[i - 1] ? points[i - 1]._high + 1 : 0;
10653                         high = point._high = points[i + 1] ?
10654                                 (mathFloor((point.plotX + (points[i + 1] ? points[i + 1].plotX : plotSize)) / 2)) :
10655                                 plotSize;
10656
10657                         while (low <= high) {
10658                                 tooltipPoints[inverted ? plotSize - low++ : low++] = point;
10659                         }
10660                 }
10661                 series.tooltipPoints = tooltipPoints;
10662         },
10663
10664         /**
10665          * Format the header of the tooltip
10666          */
10667         tooltipHeaderFormatter: function (key) {
10668                 var series = this,
10669                         tooltipOptions = series.tooltipOptions,
10670                         xDateFormat = tooltipOptions.xDateFormat || '%A, %b %e, %Y',
10671                         xAxis = series.xAxis,
10672                         isDateTime = xAxis && xAxis.options.type === 'datetime';
10673                 
10674                 return tooltipOptions.headerFormat
10675                         .replace('{point.key}', isDateTime ? dateFormat(xDateFormat, key) :  key)
10676                         .replace('{series.name}', series.name)
10677                         .replace('{series.color}', series.color);
10678         },
10679
10680         /**
10681          * Series mouse over handler
10682          */
10683         onMouseOver: function () {
10684                 var series = this,
10685                         chart = series.chart,
10686                         hoverSeries = chart.hoverSeries;
10687
10688                 if (!hasTouch && chart.mouseIsDown) {
10689                         return;
10690                 }
10691
10692                 // set normal state to previous series
10693                 if (hoverSeries && hoverSeries !== series) {
10694                         hoverSeries.onMouseOut();
10695                 }
10696
10697                 // trigger the event, but to save processing time,
10698                 // only if defined
10699                 if (series.options.events.mouseOver) {
10700                         fireEvent(series, 'mouseOver');
10701                 }
10702
10703                 // hover this
10704                 series.setState(HOVER_STATE);
10705                 chart.hoverSeries = series;
10706         },
10707
10708         /**
10709          * Series mouse out handler
10710          */
10711         onMouseOut: function () {
10712                 // trigger the event only if listeners exist
10713                 var series = this,
10714                         options = series.options,
10715                         chart = series.chart,
10716                         tooltip = chart.tooltip,
10717                         hoverPoint = chart.hoverPoint;
10718
10719                 // trigger mouse out on the point, which must be in this series
10720                 if (hoverPoint) {
10721                         hoverPoint.onMouseOut();
10722                 }
10723
10724                 // fire the mouse out event
10725                 if (series && options.events.mouseOut) {
10726                         fireEvent(series, 'mouseOut');
10727                 }
10728
10729
10730                 // hide the tooltip
10731                 if (tooltip && !options.stickyTracking && !tooltip.shared) {
10732                         tooltip.hide();
10733                 }
10734
10735                 // set normal state
10736                 series.setState();
10737                 chart.hoverSeries = null;
10738         },
10739
10740         /**
10741          * Animate in the series
10742          */
10743         animate: function (init) {
10744                 var series = this,
10745                         chart = series.chart,
10746                         clipRect = series.clipRect,
10747                         animation = series.options.animation;
10748
10749                 if (animation && !isObject(animation)) {
10750                         animation = {};
10751                 }
10752
10753                 if (init) { // initialize the animation
10754                         if (!clipRect.isAnimating) { // apply it only for one of the series
10755                                 clipRect.attr('width', 0);
10756                                 clipRect.isAnimating = true;
10757                         }
10758
10759                 } else { // run the animation
10760                         clipRect.animate({
10761                                 width: chart.plotSizeX
10762                         }, animation);
10763
10764                         // delete this function to allow it only once
10765                         this.animate = null;
10766                 }
10767         },
10768
10769
10770         /**
10771          * Draw the markers
10772          */
10773         drawPoints: function () {
10774                 var series = this,
10775                         pointAttr,
10776                         points = series.points,
10777                         chart = series.chart,
10778                         plotX,
10779                         plotY,
10780                         i,
10781                         point,
10782                         radius,
10783                         symbol,
10784                         isImage,
10785                         graphic;
10786
10787                 if (series.options.marker.enabled) {
10788                         i = points.length;
10789                         while (i--) {
10790                                 point = points[i];
10791                                 plotX = point.plotX;
10792                                 plotY = point.plotY;
10793                                 graphic = point.graphic;
10794
10795                                 // only draw the point if y is defined
10796                                 if (plotY !== UNDEFINED && !isNaN(plotY)) {
10797
10798                                         // shortcuts
10799                                         pointAttr = point.pointAttr[point.selected ? SELECT_STATE : NORMAL_STATE];
10800                                         radius = pointAttr.r;
10801                                         symbol = pick(point.marker && point.marker.symbol, series.symbol);
10802                                         isImage = symbol.indexOf('url') === 0;
10803
10804                                         if (graphic) { // update
10805                                                 graphic.animate(extend({
10806                                                         x: plotX - radius,
10807                                                         y: plotY - radius
10808                                                 }, graphic.symbolName ? { // don't apply to image symbols #507
10809                                                         width: 2 * radius,
10810                                                         height: 2 * radius
10811                                                 } : {}));
10812                                         } else if (radius > 0 || isImage) {
10813                                                 point.graphic = chart.renderer.symbol(
10814                                                         symbol,
10815                                                         plotX - radius,
10816                                                         plotY - radius,
10817                                                         2 * radius,
10818                                                         2 * radius
10819                                                 )
10820                                                 .attr(pointAttr)
10821                                                 .add(series.group);
10822                                         }
10823                                 }
10824                         }
10825                 }
10826
10827         },
10828
10829         /**
10830          * Convert state properties from API naming conventions to SVG attributes
10831          *
10832          * @param {Object} options API options object
10833          * @param {Object} base1 SVG attribute object to inherit from
10834          * @param {Object} base2 Second level SVG attribute object to inherit from
10835          */
10836         convertAttribs: function (options, base1, base2, base3) {
10837                 var conversion = this.pointAttrToOptions,
10838                         attr,
10839                         option,
10840                         obj = {};
10841
10842                 options = options || {};
10843                 base1 = base1 || {};
10844                 base2 = base2 || {};
10845                 base3 = base3 || {};
10846
10847                 for (attr in conversion) {
10848                         option = conversion[attr];
10849                         obj[attr] = pick(options[option], base1[attr], base2[attr], base3[attr]);
10850                 }
10851                 return obj;
10852         },
10853
10854         /**
10855          * Get the state attributes. Each series type has its own set of attributes
10856          * that are allowed to change on a point's state change. Series wide attributes are stored for
10857          * all series, and additionally point specific attributes are stored for all
10858          * points with individual marker options. If such options are not defined for the point,
10859          * a reference to the series wide attributes is stored in point.pointAttr.
10860          */
10861         getAttribs: function () {
10862                 var series = this,
10863                         normalOptions = defaultPlotOptions[series.type].marker ? series.options.marker : series.options,
10864                         stateOptions = normalOptions.states,
10865                         stateOptionsHover = stateOptions[HOVER_STATE],
10866                         pointStateOptionsHover,
10867                         seriesColor = series.color,
10868                         normalDefaults = {
10869                                 stroke: seriesColor,
10870                                 fill: seriesColor
10871                         },
10872                         points = series.points,
10873                         i,
10874                         point,
10875                         seriesPointAttr = [],
10876                         pointAttr,
10877                         pointAttrToOptions = series.pointAttrToOptions,
10878                         hasPointSpecificOptions,
10879                         key;
10880
10881                 // series type specific modifications
10882                 if (series.options.marker) { // line, spline, area, areaspline, scatter
10883
10884                         // if no hover radius is given, default to normal radius + 2
10885                         stateOptionsHover.radius = stateOptionsHover.radius || normalOptions.radius + 2;
10886                         stateOptionsHover.lineWidth = stateOptionsHover.lineWidth || normalOptions.lineWidth + 1;
10887
10888                 } else { // column, bar, pie
10889
10890                         // if no hover color is given, brighten the normal color
10891                         stateOptionsHover.color = stateOptionsHover.color ||
10892                                 Color(stateOptionsHover.color || seriesColor)
10893                                         .brighten(stateOptionsHover.brightness).get();
10894                 }
10895
10896                 // general point attributes for the series normal state
10897                 seriesPointAttr[NORMAL_STATE] = series.convertAttribs(normalOptions, normalDefaults);
10898
10899                 // HOVER_STATE and SELECT_STATE states inherit from normal state except the default radius
10900                 each([HOVER_STATE, SELECT_STATE], function (state) {
10901                         seriesPointAttr[state] =
10902                                         series.convertAttribs(stateOptions[state], seriesPointAttr[NORMAL_STATE]);
10903                 });
10904
10905                 // set it
10906                 series.pointAttr = seriesPointAttr;
10907
10908
10909                 // Generate the point-specific attribute collections if specific point
10910                 // options are given. If not, create a referance to the series wide point
10911                 // attributes
10912                 i = points.length;
10913                 while (i--) {
10914                         point = points[i];
10915                         normalOptions = (point.options && point.options.marker) || point.options;
10916                         if (normalOptions && normalOptions.enabled === false) {
10917                                 normalOptions.radius = 0;
10918                         }
10919                         hasPointSpecificOptions = false;
10920
10921                         // check if the point has specific visual options
10922                         if (point.options) {
10923                                 for (key in pointAttrToOptions) {
10924                                         if (defined(normalOptions[pointAttrToOptions[key]])) {
10925                                                 hasPointSpecificOptions = true;
10926                                         }
10927                                 }
10928                         }
10929
10930
10931
10932                         // a specific marker config object is defined for the individual point:
10933                         // create it's own attribute collection
10934                         if (hasPointSpecificOptions) {
10935
10936                                 pointAttr = [];
10937                                 stateOptions = normalOptions.states || {}; // reassign for individual point
10938                                 pointStateOptionsHover = stateOptions[HOVER_STATE] = stateOptions[HOVER_STATE] || {};
10939
10940                                 // if no hover color is given, brighten the normal color
10941                                 if (!series.options.marker) { // column, bar, point
10942                                         pointStateOptionsHover.color =
10943                                                 Color(pointStateOptionsHover.color || point.options.color)
10944                                                         .brighten(pointStateOptionsHover.brightness ||
10945                                                                 stateOptionsHover.brightness).get();
10946
10947                                 }
10948
10949                                 // normal point state inherits series wide normal state
10950                                 pointAttr[NORMAL_STATE] = series.convertAttribs(normalOptions, seriesPointAttr[NORMAL_STATE]);
10951
10952                                 // inherit from point normal and series hover
10953                                 pointAttr[HOVER_STATE] = series.convertAttribs(
10954                                         stateOptions[HOVER_STATE],
10955                                         seriesPointAttr[HOVER_STATE],
10956                                         pointAttr[NORMAL_STATE]
10957                                 );
10958                                 // inherit from point normal and series hover
10959                                 pointAttr[SELECT_STATE] = series.convertAttribs(
10960                                         stateOptions[SELECT_STATE],
10961                                         seriesPointAttr[SELECT_STATE],
10962                                         pointAttr[NORMAL_STATE]
10963                                 );
10964
10965
10966
10967                         // no marker config object is created: copy a reference to the series-wide
10968                         // attribute collection
10969                         } else {
10970                                 pointAttr = seriesPointAttr;
10971                         }
10972
10973                         point.pointAttr = pointAttr;
10974
10975                 }
10976
10977         },
10978
10979
10980         /**
10981          * Clear DOM objects and free up memory
10982          */
10983         destroy: function () {
10984                 var series = this,
10985                         chart = series.chart,
10986                         seriesClipRect = series.clipRect,
10987                         issue134 = /AppleWebKit\/533/.test(userAgent),
10988                         destroy,
10989                         i,
10990                         data = series.data || [],
10991                         point,
10992                         prop,
10993                         axis;
10994
10995                 // add event hook
10996                 fireEvent(series, 'destroy');
10997
10998                 // remove all events
10999                 removeEvent(series);
11000                 
11001                 // erase from axes
11002                 each(['xAxis', 'yAxis'], function (AXIS) {
11003                         axis = series[AXIS];
11004                         if (axis) {
11005                                 erase(axis.series, series);
11006                                 axis.isDirty = true;
11007                         }
11008                 });
11009
11010                 // remove legend items
11011                 if (series.legendItem) {
11012                         series.chart.legend.destroyItem(series);
11013                 }
11014
11015                 // destroy all points with their elements
11016                 i = data.length;
11017                 while (i--) {
11018                         point = data[i];
11019                         if (point && point.destroy) {
11020                                 point.destroy();
11021                         }
11022                 }
11023                 series.points = null;
11024
11025                 // If this series clipRect is not the global one (which is removed on chart.destroy) we
11026                 // destroy it here.
11027                 if (seriesClipRect && seriesClipRect !== chart.clipRect) {
11028                         series.clipRect = seriesClipRect.destroy();
11029                 }
11030
11031                 // destroy all SVGElements associated to the series
11032                 each(['area', 'graph', 'dataLabelsGroup', 'group', 'tracker'], function (prop) {
11033                         if (series[prop]) {
11034
11035                                 // issue 134 workaround
11036                                 destroy = issue134 && prop === 'group' ?
11037                                         'hide' :
11038                                         'destroy';
11039
11040                                 series[prop][destroy]();
11041                         }
11042                 });
11043
11044                 // remove from hoverSeries
11045                 if (chart.hoverSeries === series) {
11046                         chart.hoverSeries = null;
11047                 }
11048                 erase(chart.series, series);
11049
11050                 // clear all members
11051                 for (prop in series) {
11052                         delete series[prop];
11053                 }
11054         },
11055
11056         /**
11057          * Draw the data labels
11058          */
11059         drawDataLabels: function () {
11060                 if (this.options.dataLabels.enabled) {
11061                         var series = this,
11062                                 x,
11063                                 y,
11064                                 points = series.points,
11065                                 seriesOptions = series.options,
11066                                 options = seriesOptions.dataLabels,
11067                                 pointOptions,
11068                                 generalOptions,
11069                                 str,
11070                                 dataLabelsGroup = series.dataLabelsGroup,
11071                                 chart = series.chart,
11072                                 xAxis = series.xAxis,
11073                                 groupLeft = xAxis ? xAxis.left : chart.plotLeft,
11074                                 yAxis = series.yAxis,
11075                                 groupTop = yAxis ? yAxis.top : chart.plotTop,
11076                                 renderer = chart.renderer,
11077                                 inverted = chart.inverted,
11078                                 seriesType = series.type,
11079                                 stacking = seriesOptions.stacking,
11080                                 isBarLike = seriesType === 'column' || seriesType === 'bar',
11081                                 vAlignIsNull = options.verticalAlign === null,
11082                                 yIsNull = options.y === null,
11083                                 dataLabel;
11084
11085                         if (isBarLike) {
11086                                 if (stacking) {
11087                                         // In stacked series the default label placement is inside the bars
11088                                         if (vAlignIsNull) {
11089                                                 options = merge(options, {verticalAlign: 'middle'});
11090                                         }
11091
11092                                         // If no y delta is specified, try to create a good default
11093                                         if (yIsNull) {
11094                                                 options = merge(options, {y: {top: 14, middle: 4, bottom: -6}[options.verticalAlign]});
11095                                         }
11096                                 } else {
11097                                         // In non stacked series the default label placement is on top of the bars
11098                                         if (vAlignIsNull) {
11099                                                 options = merge(options, {verticalAlign: 'top'});
11100                                         }
11101                                 }
11102                         }
11103
11104
11105                         // create a separate group for the data labels to avoid rotation
11106                         if (!dataLabelsGroup) {
11107                                 dataLabelsGroup = series.dataLabelsGroup =
11108                                         renderer.g('data-labels')
11109                                                 .attr({
11110                                                         visibility: series.visible ? VISIBLE : HIDDEN,
11111                                                         zIndex: 6
11112                                                 })
11113                                                 .translate(groupLeft, groupTop)
11114                                                 .add();
11115                         } else {
11116                                 dataLabelsGroup.translate(groupLeft, groupTop);
11117                         }
11118
11119                         // make the labels for each point
11120                         generalOptions = options;
11121                         each(points, function (point) {
11122                                 
11123                                 dataLabel = point.dataLabel;
11124                                 
11125                                 // Merge in individual options from point // docs
11126                                 options = generalOptions; // reset changes from previous points
11127                                 pointOptions = point.options;
11128                                 if (pointOptions && pointOptions.dataLabels) {
11129                                         options = merge(options, pointOptions.dataLabels);
11130                                 }
11131                                 
11132                                 // If the point is outside the plot area, destroy it. #678
11133                                 if (dataLabel && series.isCartesian && !chart.isInsidePlot(point.plotX, point.plotY)) {
11134                                         point.dataLabel = dataLabel.destroy();
11135                                 
11136                                 // Individual labels are disabled if the are explicitly disabled 
11137                                 // in the point options, or if they fall outside the plot area.
11138                                 } else if (options.enabled) {
11139                                 
11140                                         // Get the string
11141                                         str = options.formatter.call(point.getLabelConfig(), options);
11142                                         
11143                                         var barX = point.barX,
11144                                                 plotX = (barX && barX + point.barW / 2) || point.plotX || -999,
11145                                                 plotY = pick(point.plotY, -999),
11146                                                 align = options.align,
11147                                                 individualYDelta = yIsNull ? (point.y >= 0 ? -6 : 12) : options.y;
11148         
11149                                         // Postprocess the positions
11150                                         x = (inverted ? chart.plotWidth - plotY : plotX) + options.x;
11151                                         y = (inverted ? chart.plotHeight - plotX : plotY) + individualYDelta;
11152                                         
11153                                         // in columns, align the string to the column
11154                                         if (seriesType === 'column') {
11155                                                 x += { left: -1, right: 1 }[align] * point.barW / 2 || 0;
11156                                         }
11157         
11158                                         if (!stacking && inverted && point.y < 0) {
11159                                                 align = 'right';
11160                                                 x -= 10;
11161                                         }
11162                                         
11163                                         // Determine the color
11164                                         options.style.color = pick(options.color, options.style.color, series.color, 'black');
11165         
11166                                         
11167                                         // update existing label
11168                                         if (dataLabel) {
11169                                                 // vertically centered
11170                                                 if (inverted && !options.y) {
11171                                                         y = y + pInt(dataLabel.styles.lineHeight) * 0.9 - dataLabel.getBBox().height / 2;
11172                                                 }
11173                                                 dataLabel
11174                                                         .attr({
11175                                                                 text: str
11176                                                         }).animate({
11177                                                                 x: x,
11178                                                                 y: y
11179                                                         });
11180                                         // create new label
11181                                         } else if (defined(str)) {
11182                                                 dataLabel = point.dataLabel = renderer.text(
11183                                                         str,
11184                                                         x,
11185                                                         y
11186                                                 )
11187                                                 .attr({
11188                                                         align: align,
11189                                                         rotation: options.rotation,
11190                                                         zIndex: 1
11191                                                 })
11192                                                 .css(options.style)
11193                                                 .add(dataLabelsGroup);
11194                                                 // vertically centered
11195                                                 if (inverted && !options.y) {
11196                                                         dataLabel.attr({
11197                                                                 y: y + pInt(dataLabel.styles.lineHeight) * 0.9 - dataLabel.getBBox().height / 2
11198                                                         });
11199                                                 }
11200                                         }
11201         
11202                                         if (isBarLike && seriesOptions.stacking && dataLabel) {
11203                                                 var barY = point.barY,
11204                                                         barW = point.barW,
11205                                                         barH = point.barH;
11206         
11207                                                 dataLabel.align(options, null,
11208                                                         {
11209                                                                 x: inverted ? chart.plotWidth - barY - barH : barX,
11210                                                                 y: inverted ? chart.plotHeight - barX - barW : barY,
11211                                                                 width: inverted ? barH : barW,
11212                                                                 height: inverted ? barW : barH
11213                                                         });
11214                                         }
11215                                         
11216                                         
11217                                 }
11218                         });
11219                 }
11220         },
11221
11222         /**
11223          * Draw the actual graph
11224          */
11225         drawGraph: function () {
11226                 var series = this,
11227                         options = series.options,
11228                         chart = series.chart,
11229                         graph = series.graph,
11230                         graphPath = [],
11231                         fillColor,
11232                         area = series.area,
11233                         group = series.group,
11234                         color = options.lineColor || series.color,
11235                         lineWidth = options.lineWidth,
11236                         dashStyle =  options.dashStyle,
11237                         segmentPath,
11238                         renderer = chart.renderer,
11239                         translatedThreshold = series.yAxis.getThreshold(options.threshold),
11240                         useArea = /^area/.test(series.type),
11241                         singlePoints = [], // used in drawTracker
11242                         areaPath = [],
11243                         attribs;
11244
11245
11246                 // divide into segments and build graph and area paths
11247                 each(series.segments, function (segment) {
11248                         segmentPath = [];
11249
11250                         // build the segment line
11251                         each(segment, function (point, i) {
11252
11253                                 if (series.getPointSpline) { // generate the spline as defined in the SplineSeries object
11254                                         segmentPath.push.apply(segmentPath, series.getPointSpline(segment, point, i));
11255
11256                                 } else {
11257
11258                                         // moveTo or lineTo
11259                                         segmentPath.push(i ? L : M);
11260
11261                                         // step line?
11262                                         if (i && options.step) {
11263                                                 var lastPoint = segment[i - 1];
11264                                                 segmentPath.push(
11265                                                         point.plotX,
11266                                                         lastPoint.plotY
11267                                                 );
11268                                         }
11269
11270                                         // normal line to next point
11271                                         segmentPath.push(
11272                                                 point.plotX,
11273                                                 point.plotY
11274                                         );
11275                                 }
11276                         });
11277
11278                         // add the segment to the graph, or a single point for tracking
11279                         if (segment.length > 1) {
11280                                 graphPath = graphPath.concat(segmentPath);
11281                         } else {
11282                                 singlePoints.push(segment[0]);
11283                         }
11284
11285                         // build the area
11286                         if (useArea) {
11287                                 var areaSegmentPath = [],
11288                                         i,
11289                                         segLength = segmentPath.length;
11290                                 for (i = 0; i < segLength; i++) {
11291                                         areaSegmentPath.push(segmentPath[i]);
11292                                 }
11293                                 if (segLength === 3) { // for animation from 1 to two points
11294                                         areaSegmentPath.push(L, segmentPath[1], segmentPath[2]);
11295                                 }
11296                                 if (options.stacking && series.type !== 'areaspline') {
11297                                         
11298                                         // Follow stack back. Todo: implement areaspline. A general solution could be to 
11299                                         // reverse the entire graphPath of the previous series, though may be hard with
11300                                         // splines and with series with different extremes
11301                                         for (i = segment.length - 1; i >= 0; i--) {
11302                                         
11303                                                 // step line?
11304                                                 if (i < segment.length - 1 && options.step) {
11305                                                         areaSegmentPath.push(segment[i + 1].plotX, segment[i].yBottom);
11306                                                 }
11307                                                 
11308                                                 areaSegmentPath.push(segment[i].plotX, segment[i].yBottom);
11309                                         }
11310
11311                                 } else { // follow zero line back
11312                                         areaSegmentPath.push(
11313                                                 L,
11314                                                 segment[segment.length - 1].plotX,
11315                                                 translatedThreshold,
11316                                                 L,
11317                                                 segment[0].plotX,
11318                                                 translatedThreshold
11319                                         );
11320                                 }
11321                                 areaPath = areaPath.concat(areaSegmentPath);
11322                         }
11323                 });
11324
11325                 // used in drawTracker:
11326                 series.graphPath = graphPath;
11327                 series.singlePoints = singlePoints;
11328
11329                 // draw the area if area series or areaspline
11330                 if (useArea) {
11331                         fillColor = pick(
11332                                 options.fillColor,
11333                                 Color(series.color).setOpacity(options.fillOpacity || 0.75).get()
11334                         );
11335                         if (area) {
11336                                 area.animate({ d: areaPath });
11337
11338                         } else {
11339                                 // draw the area
11340                                 series.area = series.chart.renderer.path(areaPath)
11341                                         .attr({
11342                                                 fill: fillColor
11343                                         }).add(group);
11344                         }
11345                 }
11346
11347                 // draw the graph
11348                 if (graph) {
11349                         stop(graph); // cancel running animations, #459
11350                         graph.animate({ d: graphPath });
11351
11352                 } else {
11353                         if (lineWidth) {
11354                                 attribs = {
11355                                         'stroke': color,
11356                                         'stroke-width': lineWidth
11357                                 };
11358                                 if (dashStyle) {
11359                                         attribs.dashstyle = dashStyle;
11360                                 }
11361
11362                                 series.graph = renderer.path(graphPath)
11363                                         .attr(attribs).add(group).shadow(options.shadow);
11364                         }
11365                 }
11366         },
11367
11368
11369         /**
11370          * Render the graph and markers
11371          */
11372         render: function () {
11373                 var series = this,
11374                         chart = series.chart,
11375                         group,
11376                         setInvert,
11377                         options = series.options,
11378                         doClip = options.clip !== false,
11379                         animation = options.animation,
11380                         doAnimation = animation && series.animate,
11381                         duration = doAnimation ? (animation && animation.duration) || 500 : 0,
11382                         clipRect = series.clipRect,
11383                         renderer = chart.renderer;
11384
11385
11386                 // Add plot area clipping rectangle. If this is before chart.hasRendered,
11387                 // create one shared clipRect.
11388
11389                 // Todo: since creating the clip property, the clipRect is created but
11390                 // never used when clip is false. A better way would be that the animation
11391                 // would run, then the clipRect destroyed.
11392                 if (!clipRect) {
11393                         clipRect = series.clipRect = !chart.hasRendered && chart.clipRect ?
11394                                 chart.clipRect :
11395                                 renderer.clipRect(0, 0, chart.plotSizeX, chart.plotSizeY + 1);
11396                         if (!chart.clipRect) {
11397                                 chart.clipRect = clipRect;
11398                         }
11399                 }
11400                 
11401
11402                 // the group
11403                 if (!series.group) {
11404                         group = series.group = renderer.g('series');
11405
11406                         if (chart.inverted) {
11407                                 setInvert = function () {
11408                                         group.attr({
11409                                                 width: chart.plotWidth,
11410                                                 height: chart.plotHeight
11411                                         }).invert();
11412                                 };
11413
11414                                 setInvert(); // do it now
11415                                 addEvent(chart, 'resize', setInvert); // do it on resize
11416                                 addEvent(series, 'destroy', function () {
11417                                         removeEvent(chart, 'resize', setInvert);
11418                                 });
11419                         }
11420
11421                         if (doClip) {
11422                                 group.clip(clipRect);
11423                         }
11424                         group.attr({
11425                                         visibility: series.visible ? VISIBLE : HIDDEN,
11426                                         zIndex: options.zIndex
11427                                 })
11428                                 .translate(series.xAxis.left, series.yAxis.top)
11429                                 .add(chart.seriesGroup);
11430                 }
11431
11432                 series.drawDataLabels();
11433
11434                 // initiate the animation
11435                 if (doAnimation) {
11436                         series.animate(true);
11437                 }
11438
11439                 // cache attributes for shapes
11440                 series.getAttribs();
11441
11442                 // draw the graph if any
11443                 if (series.drawGraph) {
11444                         series.drawGraph();
11445                 }
11446
11447                 // draw the points
11448                 series.drawPoints();
11449
11450                 // draw the mouse tracking area
11451                 if (series.options.enableMouseTracking !== false) {
11452                         series.drawTracker();
11453                 }
11454
11455                 // run the animation
11456                 if (doAnimation) {
11457                         series.animate();
11458                 }
11459
11460                 // finish the individual clipRect
11461                 setTimeout(function () {
11462                         clipRect.isAnimating = false;
11463                         group = series.group; // can be destroyed during the timeout
11464                         if (group && clipRect !== chart.clipRect && clipRect.renderer) {
11465                                 if (doClip) {
11466                                         group.clip((series.clipRect = chart.clipRect));
11467                                 }
11468                                 clipRect.destroy();
11469                         }
11470                 }, duration);
11471
11472                 series.isDirty = series.isDirtyData = false; // means data is in accordance with what you see
11473                 // (See #322) series.isDirty = series.isDirtyData = false; // means data is in accordance with what you see
11474
11475         },
11476
11477         /**
11478          * Redraw the series after an update in the axes.
11479          */
11480         redraw: function () {
11481                 var series = this,
11482                         chart = series.chart,
11483                         wasDirtyData = series.isDirtyData, // cache it here as it is set to false in render, but used after
11484                         group = series.group;
11485
11486                 // reposition on resize
11487                 if (group) {
11488                         if (chart.inverted) {
11489                                 group.attr({
11490                                         width: chart.plotWidth,
11491                                         height: chart.plotHeight
11492                                 });
11493                         }
11494
11495                         group.animate({
11496                                 translateX: series.xAxis.left,
11497                                 translateY: series.yAxis.top
11498                         });
11499                 }
11500
11501                 series.translate();
11502                 series.setTooltipPoints(true);
11503
11504                 series.render();
11505                 if (wasDirtyData) {
11506                         fireEvent(series, 'updatedData');
11507                 }
11508         },
11509
11510         /**
11511          * Set the state of the graph
11512          */
11513         setState: function (state) {
11514                 var series = this,
11515                         options = series.options,
11516                         graph = series.graph,
11517                         stateOptions = options.states,
11518                         lineWidth = options.lineWidth;
11519
11520                 state = state || NORMAL_STATE;
11521
11522                 if (series.state !== state) {
11523                         series.state = state;
11524
11525                         if (stateOptions[state] && stateOptions[state].enabled === false) {
11526                                 return;
11527                         }
11528
11529                         if (state) {
11530                                 lineWidth = stateOptions[state].lineWidth || lineWidth + 1;
11531                         }
11532
11533                         if (graph && !graph.dashstyle) { // hover is turned off for dashed lines in VML
11534                                 graph.attr({ // use attr because animate will cause any other animation on the graph to stop
11535                                         'stroke-width': lineWidth
11536                                 }, state ? 0 : 500);
11537                         }
11538                 }
11539         },
11540
11541         /**
11542          * Set the visibility of the graph
11543          *
11544          * @param vis {Boolean} True to show the series, false to hide. If UNDEFINED,
11545          *        the visibility is toggled.
11546          */
11547         setVisible: function (vis, redraw) {
11548                 var series = this,
11549                         chart = series.chart,
11550                         legendItem = series.legendItem,
11551                         seriesGroup = series.group,
11552                         seriesTracker = series.tracker,
11553                         dataLabelsGroup = series.dataLabelsGroup,
11554                         showOrHide,
11555                         i,
11556                         points = series.points,
11557                         point,
11558                         ignoreHiddenSeries = chart.options.chart.ignoreHiddenSeries,
11559                         oldVisibility = series.visible;
11560
11561                 // if called without an argument, toggle visibility
11562                 series.visible = vis = vis === UNDEFINED ? !oldVisibility : vis;
11563                 showOrHide = vis ? 'show' : 'hide';
11564
11565                 // show or hide series
11566                 if (seriesGroup) { // pies don't have one
11567                         seriesGroup[showOrHide]();
11568                 }
11569
11570                 // show or hide trackers
11571                 if (seriesTracker) {
11572                         seriesTracker[showOrHide]();
11573                 } else if (points) {
11574                         i = points.length;
11575                         while (i--) {
11576                                 point = points[i];
11577                                 if (point.tracker) {
11578                                         point.tracker[showOrHide]();
11579                                 }
11580                         }
11581                 }
11582
11583
11584                 if (dataLabelsGroup) {
11585                         dataLabelsGroup[showOrHide]();
11586                 }
11587
11588                 if (legendItem) {
11589                         chart.legend.colorizeItem(series, vis);
11590                 }
11591
11592
11593                 // rescale or adapt to resized chart
11594                 series.isDirty = true;
11595                 // in a stack, all other series are affected
11596                 if (series.options.stacking) {
11597                         each(chart.series, function (otherSeries) {
11598                                 if (otherSeries.options.stacking && otherSeries.visible) {
11599                                         otherSeries.isDirty = true;
11600                                 }
11601                         });
11602                 }
11603
11604                 if (ignoreHiddenSeries) {
11605                         chart.isDirtyBox = true;
11606                 }
11607                 if (redraw !== false) {
11608                         chart.redraw();
11609                 }
11610
11611                 fireEvent(series, showOrHide);
11612         },
11613
11614         /**
11615          * Show the graph
11616          */
11617         show: function () {
11618                 this.setVisible(true);
11619         },
11620
11621         /**
11622          * Hide the graph
11623          */
11624         hide: function () {
11625                 this.setVisible(false);
11626         },
11627
11628
11629         /**
11630          * Set the selected state of the graph
11631          *
11632          * @param selected {Boolean} True to select the series, false to unselect. If
11633          *        UNDEFINED, the selection state is toggled.
11634          */
11635         select: function (selected) {
11636                 var series = this;
11637                 // if called without an argument, toggle
11638                 series.selected = selected = (selected === UNDEFINED) ? !series.selected : selected;
11639
11640                 if (series.checkbox) {
11641                         series.checkbox.checked = selected;
11642                 }
11643
11644                 fireEvent(series, selected ? 'select' : 'unselect');
11645         },
11646
11647
11648         /**
11649          * Draw the tracker object that sits above all data labels and markers to
11650          * track mouse events on the graph or points. For the line type charts
11651          * the tracker uses the same graphPath, but with a greater stroke width
11652          * for better control.
11653          */
11654         drawTracker: function () {
11655                 var series = this,
11656                         options = series.options,
11657                         trackerPath = [].concat(series.graphPath),
11658                         trackerPathLength = trackerPath.length,
11659                         chart = series.chart,
11660                         renderer = chart.renderer,
11661                         snap = chart.options.tooltip.snap,
11662                         tracker = series.tracker,
11663                         cursor = options.cursor,
11664                         css = cursor && { cursor: cursor },
11665                         singlePoints = series.singlePoints,
11666                         group,
11667                         singlePoint,
11668                         i;
11669
11670                 // Extend end points. A better way would be to use round linecaps,
11671                 // but those are not clickable in VML.
11672                 if (trackerPathLength) {
11673                         i = trackerPathLength + 1;
11674                         while (i--) {
11675                                 if (trackerPath[i] === M) { // extend left side
11676                                         trackerPath.splice(i + 1, 0, trackerPath[i + 1] - snap, trackerPath[i + 2], L);
11677                                 }
11678                                 if ((i && trackerPath[i] === M) || i === trackerPathLength) { // extend right side
11679                                         trackerPath.splice(i, 0, L, trackerPath[i - 2] + snap, trackerPath[i - 1]);
11680                                 }
11681                         }
11682                 }
11683
11684                 // handle single points
11685                 for (i = 0; i < singlePoints.length; i++) {
11686                         singlePoint = singlePoints[i];
11687                         trackerPath.push(M, singlePoint.plotX - snap, singlePoint.plotY,
11688                                 L, singlePoint.plotX + snap, singlePoint.plotY);
11689                 }
11690                 
11691                 
11692
11693                 // draw the tracker
11694                 if (tracker) {
11695                         tracker.attr({ d: trackerPath });
11696
11697                 } else { // create
11698                         group = renderer.g()
11699                                 .clip(chart.clipRect)
11700                                 .add(chart.trackerGroup);
11701                                 
11702                         series.tracker = renderer.path(trackerPath)
11703                                 .attr({
11704                                         isTracker: true,
11705                                         stroke: TRACKER_FILL,
11706                                         fill: NONE,
11707                                         'stroke-linejoin': 'bevel',
11708                                         'stroke-width' : options.lineWidth + 2 * snap,
11709                                         visibility: series.visible ? VISIBLE : HIDDEN,
11710                                         zIndex: options.zIndex || 1
11711                                 })
11712                                 .on(hasTouch ? 'touchstart' : 'mouseover', function () {
11713                                         if (chart.hoverSeries !== series) {
11714                                                 series.onMouseOver();
11715                                         }
11716                                 })
11717                                 .on('mouseout', function () {
11718                                         if (!options.stickyTracking) {
11719                                                 series.onMouseOut();
11720                                         }
11721                                 })
11722                                 .css(css)
11723                                 .add(group);
11724                 }
11725
11726         }
11727
11728 }; // end Series prototype
11729
11730
11731 /**
11732  * LineSeries object
11733  */
11734 var LineSeries = extendClass(Series);
11735 seriesTypes.line = LineSeries;
11736
11737 /**
11738  * AreaSeries object
11739  */
11740 var AreaSeries = extendClass(Series, {
11741         type: 'area',
11742         useThreshold: true
11743 });
11744 seriesTypes.area = AreaSeries;
11745
11746
11747
11748
11749 /**
11750  * SplineSeries object
11751  */
11752 var SplineSeries = extendClass(Series, {
11753         type: 'spline',
11754
11755         /**
11756          * Draw the actual graph
11757          */
11758         getPointSpline: function (segment, point, i) {
11759                 var smoothing = 1.5, // 1 means control points midway between points, 2 means 1/3 from the point, 3 is 1/4 etc
11760                         denom = smoothing + 1,
11761                         plotX = point.plotX,
11762                         plotY = point.plotY,
11763                         lastPoint = segment[i - 1],
11764                         nextPoint = segment[i + 1],
11765                         leftContX,
11766                         leftContY,
11767                         rightContX,
11768                         rightContY,
11769                         ret;
11770
11771                 // find control points
11772                 if (i && i < segment.length - 1) {
11773                         var lastX = lastPoint.plotX,
11774                                 lastY = lastPoint.plotY,
11775                                 nextX = nextPoint.plotX,
11776                                 nextY = nextPoint.plotY,
11777                                 correction;
11778
11779                         leftContX = (smoothing * plotX + lastX) / denom;
11780                         leftContY = (smoothing * plotY + lastY) / denom;
11781                         rightContX = (smoothing * plotX + nextX) / denom;
11782                         rightContY = (smoothing * plotY + nextY) / denom;
11783
11784                         // have the two control points make a straight line through main point
11785                         correction = ((rightContY - leftContY) * (rightContX - plotX)) /
11786                                 (rightContX - leftContX) + plotY - rightContY;
11787
11788                         leftContY += correction;
11789                         rightContY += correction;
11790
11791                         // to prevent false extremes, check that control points are between
11792                         // neighbouring points' y values
11793                         if (leftContY > lastY && leftContY > plotY) {
11794                                 leftContY = mathMax(lastY, plotY);
11795                                 rightContY = 2 * plotY - leftContY; // mirror of left control point
11796                         } else if (leftContY < lastY && leftContY < plotY) {
11797                                 leftContY = mathMin(lastY, plotY);
11798                                 rightContY = 2 * plotY - leftContY;
11799                         }
11800                         if (rightContY > nextY && rightContY > plotY) {
11801                                 rightContY = mathMax(nextY, plotY);
11802                                 leftContY = 2 * plotY - rightContY;
11803                         } else if (rightContY < nextY && rightContY < plotY) {
11804                                 rightContY = mathMin(nextY, plotY);
11805                                 leftContY = 2 * plotY - rightContY;
11806                         }
11807
11808                         // record for drawing in next point
11809                         point.rightContX = rightContX;
11810                         point.rightContY = rightContY;
11811
11812                 }
11813
11814                 // moveTo or lineTo
11815                 if (!i) {
11816                         ret = [M, plotX, plotY];
11817                 } else { // curve from last point to this
11818                         ret = [
11819                                 'C',
11820                                 lastPoint.rightContX || lastPoint.plotX,
11821                                 lastPoint.rightContY || lastPoint.plotY,
11822                                 leftContX || plotX,
11823                                 leftContY || plotY,
11824                                 plotX,
11825                                 plotY
11826                         ];
11827                         lastPoint.rightContX = lastPoint.rightContY = null; // reset for updating series later
11828                 }
11829                 return ret;
11830         }
11831 });
11832 seriesTypes.spline = SplineSeries;
11833
11834
11835
11836 /**
11837  * AreaSplineSeries object
11838  */
11839 var AreaSplineSeries = extendClass(SplineSeries, {
11840         type: 'areaspline',
11841         useThreshold: true
11842 });
11843 seriesTypes.areaspline = AreaSplineSeries;
11844
11845 /**
11846  * ColumnSeries object
11847  */
11848 var ColumnSeries = extendClass(Series, {
11849         type: 'column',
11850         useThreshold: true,
11851         tooltipOutsidePlot: true,
11852         pointAttrToOptions: { // mapping between SVG attributes and the corresponding options
11853                 stroke: 'borderColor',
11854                 'stroke-width': 'borderWidth',
11855                 fill: 'color',
11856                 r: 'borderRadius'
11857         },
11858         init: function () {
11859                 Series.prototype.init.apply(this, arguments);
11860
11861                 var series = this,
11862                         chart = series.chart;
11863
11864                 // if the series is added dynamically, force redraw of other
11865                 // series affected by a new column
11866                 if (chart.hasRendered) {
11867                         each(chart.series, function (otherSeries) {
11868                                 if (otherSeries.type === series.type) {
11869                                         otherSeries.isDirty = true;
11870                                 }
11871                         });
11872                 }
11873         },
11874
11875         /**
11876          * Translate each point to the plot area coordinate system and find shape positions
11877          */
11878         translate: function () {
11879                 var series = this,
11880                         chart = series.chart,
11881                         options = series.options,
11882                         stacking = options.stacking,
11883                         borderWidth = options.borderWidth,
11884                         columnCount = 0,
11885                         xAxis = series.xAxis,
11886                         reversedXAxis = xAxis.reversed,
11887                         stackGroups = {},
11888                         stackKey,
11889                         columnIndex;
11890
11891                 Series.prototype.translate.apply(series);
11892
11893                 // Get the total number of column type series.
11894                 // This is called on every series. Consider moving this logic to a
11895                 // chart.orderStacks() function and call it on init, addSeries and removeSeries
11896                 each(chart.series, function (otherSeries) {
11897                         if (otherSeries.type === series.type && otherSeries.visible &&
11898                                         series.options.group === otherSeries.options.group) { // used in Stock charts navigator series
11899                                 if (otherSeries.options.stacking) {
11900                                         stackKey = otherSeries.stackKey;
11901                                         if (stackGroups[stackKey] === UNDEFINED) {
11902                                                 stackGroups[stackKey] = columnCount++;
11903                                         }
11904                                         columnIndex = stackGroups[stackKey];
11905                                 } else {
11906                                         columnIndex = columnCount++;
11907                                 }
11908                                 otherSeries.columnIndex = columnIndex;
11909                         }
11910                 });
11911
11912                 // calculate the width and position of each column based on
11913                 // the number of column series in the plot, the groupPadding
11914                 // and the pointPadding options
11915                 var points = series.points,
11916                         categoryWidth = mathAbs(xAxis.translationSlope) * (xAxis.ordinalSlope || xAxis.closestPointRange || 1),
11917                         groupPadding = categoryWidth * options.groupPadding,
11918                         groupWidth = categoryWidth - 2 * groupPadding,
11919                         pointOffsetWidth = groupWidth / columnCount,
11920                         optionPointWidth = options.pointWidth,
11921                         pointPadding = defined(optionPointWidth) ? (pointOffsetWidth - optionPointWidth) / 2 :
11922                                 pointOffsetWidth * options.pointPadding,
11923                         pointWidth = mathCeil(mathMax(pick(optionPointWidth, pointOffsetWidth - 2 * pointPadding), 1)),
11924                         colIndex = (reversedXAxis ? columnCount -
11925                                 series.columnIndex : series.columnIndex) || 0,
11926                         pointXOffset = pointPadding + (groupPadding + colIndex *
11927                                 pointOffsetWidth - (categoryWidth / 2)) *
11928                                 (reversedXAxis ? -1 : 1),
11929                         threshold = options.threshold,
11930                         translatedThreshold = series.yAxis.getThreshold(threshold),
11931                         minPointLength = pick(options.minPointLength, 5);
11932
11933                 // record the new values
11934                 each(points, function (point) {
11935                         var plotY = point.plotY,
11936                                 yBottom = point.yBottom || translatedThreshold,
11937                                 barX = point.plotX + pointXOffset,
11938                                 barY = mathCeil(mathMin(plotY, yBottom)),
11939                                 barH = mathCeil(mathMax(plotY, yBottom) - barY),
11940                                 stack = series.yAxis.stacks[(point.y < 0 ? '-' : '') + series.stackKey],
11941                                 shapeArgs;
11942
11943                         // Record the offset'ed position and width of the bar to be able to align the stacking total correctly
11944                         if (stacking && series.visible && stack && stack[point.x]) {
11945                                 stack[point.x].setOffset(pointXOffset, pointWidth);
11946                         }
11947
11948                         // handle options.minPointLength
11949                         if (mathAbs(barH) < minPointLength) {
11950                                 if (minPointLength) {
11951                                         barH = minPointLength;
11952                                         barY =
11953                                                 mathAbs(barY - translatedThreshold) > minPointLength ? // stacked
11954                                                         yBottom - minPointLength : // keep position
11955                                                         translatedThreshold - (plotY <= translatedThreshold ? minPointLength : 0);
11956                                 }
11957                         }
11958
11959                         extend(point, {
11960                                 barX: barX,
11961                                 barY: barY,
11962                                 barW: pointWidth,
11963                                 barH: barH
11964                         });
11965
11966                         // create shape type and shape args that are reused in drawPoints and drawTracker
11967                         point.shapeType = 'rect';
11968                         shapeArgs = extend(chart.renderer.Element.prototype.crisp.apply({}, [
11969                                 borderWidth,
11970                                 barX,
11971                                 barY,
11972                                 pointWidth,
11973                                 barH
11974                         ]), {
11975                                 r: options.borderRadius
11976                         });
11977                         if (borderWidth % 2) { // correct for shorting in crisp method, visible in stacked columns with 1px border
11978                                 shapeArgs.y -= 1;
11979                                 shapeArgs.height += 1;
11980                         }
11981                         point.shapeArgs = shapeArgs;
11982
11983                         // make small columns responsive to mouse
11984                         point.trackerArgs = mathAbs(barH) < 3 && merge(point.shapeArgs, {
11985                                 height: 6,
11986                                 y: barY - 3
11987                         });
11988                 });
11989
11990         },
11991
11992         getSymbol: function () {
11993         },
11994
11995         /**
11996          * Columns have no graph
11997          */
11998         drawGraph: function () {},
11999
12000         /**
12001          * Draw the columns. For bars, the series.group is rotated, so the same coordinates
12002          * apply for columns and bars. This method is inherited by scatter series.
12003          *
12004          */
12005         drawPoints: function () {
12006                 var series = this,
12007                         options = series.options,
12008                         renderer = series.chart.renderer,
12009                         graphic,
12010                         shapeArgs;
12011
12012
12013                 // draw the columns
12014                 each(series.points, function (point) {
12015                         var plotY = point.plotY;
12016                         if (plotY !== UNDEFINED && !isNaN(plotY) && point.y !== null) {
12017                                 graphic = point.graphic;
12018                                 shapeArgs = point.shapeArgs;
12019                                 if (graphic) { // update
12020                                         stop(graphic);
12021                                         graphic.animate(shapeArgs);
12022
12023                                 } else {
12024                                         point.graphic = graphic = renderer[point.shapeType](shapeArgs)
12025                                                 .attr(point.pointAttr[point.selected ? SELECT_STATE : NORMAL_STATE])
12026                                                 .add(series.group)
12027                                                 .shadow(options.shadow);
12028                                 }
12029
12030                         }
12031                 });
12032         },
12033         /**
12034          * Draw the individual tracker elements.
12035          * This method is inherited by scatter and pie charts too.
12036          */
12037         drawTracker: function () {
12038                 var series = this,
12039                         chart = series.chart,
12040                         renderer = chart.renderer,
12041                         shapeArgs,
12042                         tracker,
12043                         trackerLabel = +new Date(),
12044                         options = series.options,
12045                         cursor = options.cursor,
12046                         css = cursor && { cursor: cursor },
12047                         group,
12048                         rel;
12049                         
12050                 // Add a series specific group to allow clipping the trackers
12051                 if (series.isCartesian) {
12052                         group = renderer.g()
12053                                 .clip(chart.clipRect)
12054                                 .add(chart.trackerGroup);       
12055                 }
12056
12057                 each(series.points, function (point) {
12058                         tracker = point.tracker;
12059                         shapeArgs = point.trackerArgs || point.shapeArgs;
12060                         delete shapeArgs.strokeWidth;
12061                         if (point.y !== null) {
12062                                 if (tracker) {// update
12063                                         tracker.attr(shapeArgs);
12064
12065                                 } else {
12066                                         point.tracker =
12067                                                 renderer[point.shapeType](shapeArgs)
12068                                                 .attr({
12069                                                         isTracker: trackerLabel,
12070                                                         fill: TRACKER_FILL,
12071                                                         visibility: series.visible ? VISIBLE : HIDDEN,
12072                                                         zIndex: options.zIndex || 1
12073                                                 })
12074                                                 .on(hasTouch ? 'touchstart' : 'mouseover', function (event) {
12075                                                         rel = event.relatedTarget || event.fromElement;
12076                                                         if (chart.hoverSeries !== series && attr(rel, 'isTracker') !== trackerLabel) {
12077                                                                 series.onMouseOver();
12078                                                         }
12079                                                         point.onMouseOver();
12080
12081                                                 })
12082                                                 .on('mouseout', function (event) {
12083                                                         if (!options.stickyTracking) {
12084                                                                 rel = event.relatedTarget || event.toElement;
12085                                                                 if (attr(rel, 'isTracker') !== trackerLabel) {
12086                                                                         series.onMouseOut();
12087                                                                 }
12088                                                         }
12089                                                 })
12090                                                 .css(css)
12091                                                 .add(point.group || group); // pies have point group - see issue #118
12092                                 }
12093                         }
12094                 });
12095         },
12096
12097
12098         /**
12099          * Animate the column heights one by one from zero
12100          * @param {Boolean} init Whether to initialize the animation or run it
12101          */
12102         animate: function (init) {
12103                 var series = this,
12104                         points = series.points;
12105
12106                 if (!init) { // run the animation
12107                         /*
12108                          * Note: Ideally the animation should be initialized by calling
12109                          * series.group.hide(), and then calling series.group.show()
12110                          * after the animation was started. But this rendered the shadows
12111                          * invisible in IE8 standards mode. If the columns flicker on large
12112                          * datasets, this is the cause.
12113                          */
12114
12115                         each(points, function (point) {
12116                                 var graphic = point.graphic,
12117                                         shapeArgs = point.shapeArgs;
12118
12119                                 if (graphic) {
12120                                         // start values
12121                                         graphic.attr({
12122                                                 height: 0,
12123                                                 y: series.yAxis.translate(0, 0, 1)
12124                                         });
12125
12126                                         // animate
12127                                         graphic.animate({
12128                                                 height: shapeArgs.height,
12129                                                 y: shapeArgs.y
12130                                         }, series.options.animation);
12131                                 }
12132                         });
12133
12134
12135                         // delete this function to allow it only once
12136                         series.animate = null;
12137                 }
12138
12139         },
12140         /**
12141          * Remove this series from the chart
12142          */
12143         remove: function () {
12144                 var series = this,
12145                         chart = series.chart;
12146
12147                 // column and bar series affects other series of the same type
12148                 // as they are either stacked or grouped
12149                 if (chart.hasRendered) {
12150                         each(chart.series, function (otherSeries) {
12151                                 if (otherSeries.type === series.type) {
12152                                         otherSeries.isDirty = true;
12153                                 }
12154                         });
12155                 }
12156
12157                 Series.prototype.remove.apply(series, arguments);
12158         }
12159 });
12160 seriesTypes.column = ColumnSeries;
12161
12162 var BarSeries = extendClass(ColumnSeries, {
12163         type: 'bar',
12164         init: function () {
12165                 this.inverted = true;
12166                 ColumnSeries.prototype.init.apply(this, arguments);
12167         }
12168 });
12169 seriesTypes.bar = BarSeries;
12170
12171 /**
12172  * The scatter series class
12173  */
12174 var ScatterSeries = extendClass(Series, {
12175         type: 'scatter',
12176
12177         /**
12178          * Extend the base Series' translate method by adding shape type and
12179          * arguments for the point trackers
12180          */
12181         translate: function () {
12182                 var series = this;
12183
12184                 Series.prototype.translate.apply(series);
12185
12186                 each(series.points, function (point) {
12187                         point.shapeType = 'circle';
12188                         point.shapeArgs = {
12189                                 x: point.plotX,
12190                                 y: point.plotY,
12191                                 r: series.chart.options.tooltip.snap
12192                         };
12193                 });
12194         },
12195
12196         /**
12197          * Add tracking event listener to the series group, so the point graphics
12198          * themselves act as trackers
12199          */
12200         drawTracker: function () {
12201                 var series = this,
12202                         cursor = series.options.cursor,
12203                         css = cursor && { cursor: cursor },
12204                         points = series.points,
12205                         i = points.length,
12206                         graphic;
12207
12208                 // Set an expando property for the point index, used below
12209                 while (i--) {
12210                         graphic = points[i].graphic;
12211                         if (graphic) { // doesn't exist for null points
12212                                 graphic.element._index = i; 
12213                         }
12214                 }
12215                 
12216                 // Add the event listeners, we need to do this only once
12217                 if (!series._hasTracking) {
12218                         series.group
12219                                 .on(hasTouch ? 'touchstart' : 'mouseover', function (e) {
12220                                         series.onMouseOver();
12221                                         points[e.target._index].onMouseOver();
12222                                 })
12223                                 .on('mouseout', function () {
12224                                         if (!series.options.stickyTracking) {
12225                                                 series.onMouseOut();
12226                                         }
12227                                 })
12228                                 .css(css);
12229                 } else {
12230                         series._hasTracking = true;
12231                 }
12232
12233         }
12234 });
12235 seriesTypes.scatter = ScatterSeries;
12236
12237 /**
12238  * Extended point object for pies
12239  */
12240 var PiePoint = extendClass(Point, {
12241         /**
12242          * Initiate the pie slice
12243          */
12244         init: function () {
12245
12246                 Point.prototype.init.apply(this, arguments);
12247
12248                 var point = this,
12249                         toggleSlice;
12250
12251                 //visible: options.visible !== false,
12252                 extend(point, {
12253                         visible: point.visible !== false,
12254                         name: pick(point.name, 'Slice')
12255                 });
12256
12257                 // add event listener for select
12258                 toggleSlice = function () {
12259                         point.slice();
12260                 };
12261                 addEvent(point, 'select', toggleSlice);
12262                 addEvent(point, 'unselect', toggleSlice);
12263
12264                 return point;
12265         },
12266
12267         /**
12268          * Toggle the visibility of the pie slice
12269          * @param {Boolean} vis Whether to show the slice or not. If undefined, the
12270          *    visibility is toggled
12271          */
12272         setVisible: function (vis) {
12273                 var point = this,
12274                         chart = point.series.chart,
12275                         tracker = point.tracker,
12276                         dataLabel = point.dataLabel,
12277                         connector = point.connector,
12278                         shadowGroup = point.shadowGroup,
12279                         method;
12280
12281                 // if called without an argument, toggle visibility
12282                 point.visible = vis = vis === UNDEFINED ? !point.visible : vis;
12283
12284                 method = vis ? 'show' : 'hide';
12285
12286                 point.group[method]();
12287                 if (tracker) {
12288                         tracker[method]();
12289                 }
12290                 if (dataLabel) {
12291                         dataLabel[method]();
12292                 }
12293                 if (connector) {
12294                         connector[method]();
12295                 }
12296                 if (shadowGroup) {
12297                         shadowGroup[method]();
12298                 }
12299                 if (point.legendItem) {
12300                         chart.legend.colorizeItem(point, vis);
12301                 }
12302         },
12303
12304         /**
12305          * Set or toggle whether the slice is cut out from the pie
12306          * @param {Boolean} sliced When undefined, the slice state is toggled
12307          * @param {Boolean} redraw Whether to redraw the chart. True by default.
12308          */
12309         slice: function (sliced, redraw, animation) {
12310                 var point = this,
12311                         series = point.series,
12312                         chart = series.chart,
12313                         slicedTranslation = point.slicedTranslation,
12314                         translation;
12315
12316                 setAnimation(animation, chart);
12317
12318                 // redraw is true by default
12319                 redraw = pick(redraw, true);
12320
12321                 // if called without an argument, toggle
12322                 sliced = point.sliced = defined(sliced) ? sliced : !point.sliced;
12323
12324                 translation = {
12325                         translateX: (sliced ? slicedTranslation[0] : chart.plotLeft),
12326                         translateY: (sliced ? slicedTranslation[1] : chart.plotTop)
12327                 };
12328                 point.group.animate(translation);
12329                 if (point.shadowGroup) {
12330                         point.shadowGroup.animate(translation);
12331                 }
12332
12333         }
12334 });
12335
12336 /**
12337  * The Pie series class
12338  */
12339 var PieSeries = extendClass(Series, {
12340         type: 'pie',
12341         isCartesian: false,
12342         pointClass: PiePoint,
12343         pointAttrToOptions: { // mapping between SVG attributes and the corresponding options
12344                 stroke: 'borderColor',
12345                 'stroke-width': 'borderWidth',
12346                 fill: 'color'
12347         },
12348
12349         /**
12350          * Pies have one color each point
12351          */
12352         getColor: function () {
12353                 // record first color for use in setData
12354                 this.initialColor = this.chart.counters.color;
12355         },
12356
12357         /**
12358          * Animate the column heights one by one from zero
12359          */
12360         animate: function () {
12361                 var series = this,
12362                         points = series.points;
12363
12364                 each(points, function (point) {
12365                         var graphic = point.graphic,
12366                                 args = point.shapeArgs,
12367                                 up = -mathPI / 2;
12368
12369                         if (graphic) {
12370                                 // start values
12371                                 graphic.attr({
12372                                         r: 0,
12373                                         start: up,
12374                                         end: up
12375                                 });
12376
12377                                 // animate
12378                                 graphic.animate({
12379                                         r: args.r,
12380                                         start: args.start,
12381                                         end: args.end
12382                                 }, series.options.animation);
12383                         }
12384                 });
12385
12386                 // delete this function to allow it only once
12387                 series.animate = null;
12388
12389         },
12390
12391         /**
12392          * Extend the basic setData method by running processData and generatePoints immediately,
12393          * in order to access the points from the legend.
12394          */
12395         setData: function () {
12396                 Series.prototype.setData.apply(this, arguments);
12397                 this.processData();
12398                 this.generatePoints();
12399         },
12400         /**
12401          * Do translation for pie slices
12402          */
12403         translate: function () {
12404                 this.generatePoints();
12405                 
12406                 var total = 0,
12407                         series = this,
12408                         cumulative = -0.25, // start at top
12409                         precision = 1000, // issue #172
12410                         options = series.options,
12411                         slicedOffset = options.slicedOffset,
12412                         connectorOffset = slicedOffset + options.borderWidth,
12413                         positions = options.center.concat([options.size, options.innerSize || 0]),
12414                         chart = series.chart,
12415                         plotWidth = chart.plotWidth,
12416                         plotHeight = chart.plotHeight,
12417                         start,
12418                         end,
12419                         angle,
12420                         points = series.points,
12421                         circ = 2 * mathPI,
12422                         fraction,
12423                         smallestSize = mathMin(plotWidth, plotHeight),
12424                         isPercent,
12425                         radiusX, // the x component of the radius vector for a given point
12426                         radiusY,
12427                         labelDistance = options.dataLabels.distance;
12428
12429                 // get positions - either an integer or a percentage string must be given
12430                 positions = map(positions, function (length, i) {
12431
12432                         isPercent = /%$/.test(length);
12433                         return isPercent ?
12434                                 // i == 0: centerX, relative to width
12435                                 // i == 1: centerY, relative to height
12436                                 // i == 2: size, relative to smallestSize
12437                                 // i == 4: innerSize, relative to smallestSize
12438                                 [plotWidth, plotHeight, smallestSize, smallestSize][i] *
12439                                         pInt(length) / 100 :
12440                                 length;
12441                 });
12442
12443                 // utility for getting the x value from a given y, used for anticollision logic in data labels
12444                 series.getX = function (y, left) {
12445
12446                         angle = math.asin((y - positions[1]) / (positions[2] / 2 + labelDistance));
12447
12448                         return positions[0] +
12449                                 (left ? -1 : 1) *
12450                                 (mathCos(angle) * (positions[2] / 2 + labelDistance));
12451                 };
12452
12453                 // set center for later use
12454                 series.center = positions;
12455
12456                 // get the total sum
12457                 each(points, function (point) {
12458                         total += point.y;
12459                 });
12460
12461                 each(points, function (point) {
12462                         // set start and end angle
12463                         fraction = total ? point.y / total : 0;
12464                         start = mathRound(cumulative * circ * precision) / precision;
12465                         cumulative += fraction;
12466                         end = mathRound(cumulative * circ * precision) / precision;
12467
12468                         // set the shape
12469                         point.shapeType = 'arc';
12470                         point.shapeArgs = {
12471                                 x: positions[0],
12472                                 y: positions[1],
12473                                 r: positions[2] / 2,
12474                                 innerR: positions[3] / 2,
12475                                 start: start,
12476                                 end: end
12477                         };
12478
12479                         // center for the sliced out slice
12480                         angle = (end + start) / 2;
12481                         point.slicedTranslation = map([
12482                                 mathCos(angle) * slicedOffset + chart.plotLeft,
12483                                 mathSin(angle) * slicedOffset + chart.plotTop
12484                         ], mathRound);
12485
12486                         // set the anchor point for tooltips
12487                         radiusX = mathCos(angle) * positions[2] / 2;
12488                         radiusY = mathSin(angle) * positions[2] / 2;
12489                         point.tooltipPos = [
12490                                 positions[0] + radiusX * 0.7,
12491                                 positions[1] + radiusY * 0.7
12492                         ];
12493
12494                         // set the anchor point for data labels
12495                         point.labelPos = [
12496                                 positions[0] + radiusX + mathCos(angle) * labelDistance, // first break of connector
12497                                 positions[1] + radiusY + mathSin(angle) * labelDistance, // a/a
12498                                 positions[0] + radiusX + mathCos(angle) * connectorOffset, // second break, right outside pie
12499                                 positions[1] + radiusY + mathSin(angle) * connectorOffset, // a/a
12500                                 positions[0] + radiusX, // landing point for connector
12501                                 positions[1] + radiusY, // a/a
12502                                 labelDistance < 0 ? // alignment
12503                                         'center' :
12504                                         angle < circ / 4 ? 'left' : 'right', // alignment
12505                                 angle // center angle
12506                         ];
12507
12508                         // API properties
12509                         point.percentage = fraction * 100;
12510                         point.total = total;
12511
12512                 });
12513
12514
12515                 this.setTooltipPoints();
12516         },
12517
12518         /**
12519          * Render the slices
12520          */
12521         render: function () {
12522                 var series = this;
12523
12524                 // cache attributes for shapes
12525                 series.getAttribs();
12526
12527                 this.drawPoints();
12528
12529                 // draw the mouse tracking area
12530                 if (series.options.enableMouseTracking !== false) {
12531                         series.drawTracker();
12532                 }
12533
12534                 this.drawDataLabels();
12535
12536                 if (series.options.animation && series.animate) {
12537                         series.animate();
12538                 }
12539
12540                 // (See #322) series.isDirty = series.isDirtyData = false; // means data is in accordance with what you see
12541                 series.isDirty = false; // means data is in accordance with what you see
12542         },
12543
12544         /**
12545          * Draw the data points
12546          */
12547         drawPoints: function () {
12548                 var series = this,
12549                         chart = series.chart,
12550                         renderer = chart.renderer,
12551                         groupTranslation,
12552                         //center,
12553                         graphic,
12554                         group,
12555                         shadow = series.options.shadow,
12556                         shadowGroup,
12557                         shapeArgs;
12558
12559                 // draw the slices
12560                 each(series.points, function (point) {
12561                         graphic = point.graphic;
12562                         shapeArgs = point.shapeArgs;
12563                         group = point.group;
12564                         shadowGroup = point.shadowGroup;
12565
12566                         // put the shadow behind all points
12567                         if (shadow && !shadowGroup) {
12568                                 shadowGroup = point.shadowGroup = renderer.g('shadow')
12569                                         .attr({ zIndex: 4 })
12570                                         .add();
12571                         }
12572
12573                         // create the group the first time
12574                         if (!group) {
12575                                 group = point.group = renderer.g('point')
12576                                         .attr({ zIndex: 5 })
12577                                         .add();
12578                         }
12579
12580                         // if the point is sliced, use special translation, else use plot area traslation
12581                         groupTranslation = point.sliced ? point.slicedTranslation : [chart.plotLeft, chart.plotTop];
12582                         group.translate(groupTranslation[0], groupTranslation[1]);
12583                         if (shadowGroup) {
12584                                 shadowGroup.translate(groupTranslation[0], groupTranslation[1]);
12585                         }
12586
12587                         // draw the slice
12588                         if (graphic) {
12589                                 graphic.animate(shapeArgs);
12590                         } else {
12591                                 point.graphic =
12592                                         renderer.arc(shapeArgs)
12593                                         .attr(extend(
12594                                                 point.pointAttr[NORMAL_STATE],
12595                                                 { 'stroke-linejoin': 'round' }
12596                                         ))
12597                                         .add(point.group)
12598                                         .shadow(shadow, shadowGroup);
12599                         }
12600
12601                         // detect point specific visibility
12602                         if (point.visible === false) {
12603                                 point.setVisible(false);
12604                         }
12605
12606                 });
12607
12608         },
12609
12610         /**
12611          * Override the base drawDataLabels method by pie specific functionality
12612          */
12613         drawDataLabels: function () {
12614                 var series = this,
12615                         data = series.data,
12616                         point,
12617                         chart = series.chart,
12618                         options = series.options.dataLabels,
12619                         connectorPadding = pick(options.connectorPadding, 10),
12620                         connectorWidth = pick(options.connectorWidth, 1),
12621                         connector,
12622                         connectorPath,
12623                         softConnector = pick(options.softConnector, true),
12624                         distanceOption = options.distance,
12625                         seriesCenter = series.center,
12626                         radius = seriesCenter[2] / 2,
12627                         centerY = seriesCenter[1],
12628                         outside = distanceOption > 0,
12629                         dataLabel,
12630                         labelPos,
12631                         labelHeight,
12632                         halves = [// divide the points into right and left halves for anti collision
12633                                 [], // right
12634                                 []  // left
12635                         ],
12636                         x,
12637                         y,
12638                         visibility,
12639                         rankArr,
12640                         sort,
12641                         i = 2,
12642                         j;
12643
12644                 // get out if not enabled
12645                 if (!options.enabled) {
12646                         return;
12647                 }
12648
12649                 // run parent method
12650                 Series.prototype.drawDataLabels.apply(series);
12651
12652                 // arrange points for detection collision
12653                 each(data, function (point) {
12654                         if (point.dataLabel) { // it may have been cancelled in the base method (#407)
12655                                 halves[
12656                                         point.labelPos[7] < mathPI / 2 ? 0 : 1
12657                                 ].push(point);
12658                         }
12659                 });
12660                 halves[1].reverse();
12661
12662                 // define the sorting algorithm
12663                 sort = function (a, b) {
12664                         return b.y - a.y;
12665                 };
12666
12667                 // assume equal label heights
12668                 labelHeight = halves[0][0] && halves[0][0].dataLabel && pInt(halves[0][0].dataLabel.styles.lineHeight);
12669
12670                 /* Loop over the points in each quartile, starting from the top and bottom
12671                  * of the pie to detect overlapping labels.
12672                  */
12673                 while (i--) {
12674
12675                         var slots = [],
12676                                 slotsLength,
12677                                 usedSlots = [],
12678                                 points = halves[i],
12679                                 pos,
12680                                 length = points.length,
12681                                 slotIndex;
12682
12683
12684                         // build the slots
12685                         for (pos = centerY - radius - distanceOption; pos <= centerY + radius + distanceOption; pos += labelHeight) {
12686                                 slots.push(pos);
12687                                 // visualize the slot
12688                                 /*
12689                                 var slotX = series.getX(pos, i) + chart.plotLeft - (i ? 100 : 0),
12690                                         slotY = pos + chart.plotTop;
12691                                 if (!isNaN(slotX)) {
12692                                         chart.renderer.rect(slotX, slotY - 7, 100, labelHeight)
12693                                                 .attr({
12694                                                         'stroke-width': 1,
12695                                                         stroke: 'silver'
12696                                                 })
12697                                                 .add();
12698                                         chart.renderer.text('Slot '+ (slots.length - 1), slotX, slotY + 4)
12699                                                 .attr({
12700                                                         fill: 'silver'
12701                                                 }).add();
12702                                 }
12703                                 // */
12704                         }
12705                         slotsLength = slots.length;
12706
12707                         // if there are more values than available slots, remove lowest values
12708                         if (length > slotsLength) {
12709                                 // create an array for sorting and ranking the points within each quarter
12710                                 rankArr = [].concat(points);
12711                                 rankArr.sort(sort);
12712                                 j = length;
12713                                 while (j--) {
12714                                         rankArr[j].rank = j;
12715                                 }
12716                                 j = length;
12717                                 while (j--) {
12718                                         if (points[j].rank >= slotsLength) {
12719                                                 points.splice(j, 1);
12720                                         }
12721                                 }
12722                                 length = points.length;
12723                         }
12724
12725                         // The label goes to the nearest open slot, but not closer to the edge than
12726                         // the label's index.
12727                         for (j = 0; j < length; j++) {
12728
12729                                 point = points[j];
12730                                 labelPos = point.labelPos;
12731
12732                                 var closest = 9999,
12733                                         distance,
12734                                         slotI;
12735
12736                                 // find the closest slot index
12737                                 for (slotI = 0; slotI < slotsLength; slotI++) {
12738                                         distance = mathAbs(slots[slotI] - labelPos[1]);
12739                                         if (distance < closest) {
12740                                                 closest = distance;
12741                                                 slotIndex = slotI;
12742                                         }
12743                                 }
12744
12745                                 // if that slot index is closer to the edges of the slots, move it
12746                                 // to the closest appropriate slot
12747                                 if (slotIndex < j && slots[j] !== null) { // cluster at the top
12748                                         slotIndex = j;
12749                                 } else if (slotsLength  < length - j + slotIndex && slots[j] !== null) { // cluster at the bottom
12750                                         slotIndex = slotsLength - length + j;
12751                                         while (slots[slotIndex] === null) { // make sure it is not taken
12752                                                 slotIndex++;
12753                                         }
12754                                 } else {
12755                                         // Slot is taken, find next free slot below. In the next run, the next slice will find the
12756                                         // slot above these, because it is the closest one
12757                                         while (slots[slotIndex] === null) { // make sure it is not taken
12758                                                 slotIndex++;
12759                                         }
12760                                 }
12761
12762                                 usedSlots.push({ i: slotIndex, y: slots[slotIndex] });
12763                                 slots[slotIndex] = null; // mark as taken
12764                         }
12765                         // sort them in order to fill in from the top
12766                         usedSlots.sort(sort);
12767
12768
12769                         // now the used slots are sorted, fill them up sequentially
12770                         for (j = 0; j < length; j++) {
12771
12772                                 point = points[j];
12773                                 labelPos = point.labelPos;
12774                                 dataLabel = point.dataLabel;
12775                                 var slot = usedSlots.pop(),
12776                                         naturalY = labelPos[1];
12777
12778                                 visibility = point.visible === false ? HIDDEN : VISIBLE;
12779                                 slotIndex = slot.i;
12780
12781                                 // if the slot next to currrent slot is free, the y value is allowed
12782                                 // to fall back to the natural position
12783                                 y = slot.y;
12784                                 if ((naturalY > y && slots[slotIndex + 1] !== null) ||
12785                                                 (naturalY < y &&  slots[slotIndex - 1] !== null)) {
12786                                         y = naturalY;
12787                                 }
12788
12789                                 // get the x - use the natural x position for first and last slot, to prevent the top
12790                                 // and botton slice connectors from touching each other on either side
12791                                 x = series.getX(slotIndex === 0 || slotIndex === slots.length - 1 ? naturalY : y, i);
12792
12793                                 // move or place the data label
12794                                 dataLabel
12795                                         .attr({
12796                                                 visibility: visibility,
12797                                                 align: labelPos[6]
12798                                         })[dataLabel.moved ? 'animate' : 'attr']({
12799                                                 x: x + options.x +
12800                                                         ({ left: connectorPadding, right: -connectorPadding }[labelPos[6]] || 0),
12801                                                 y: y + options.y
12802                                         });
12803                                 dataLabel.moved = true;
12804
12805                                 // draw the connector
12806                                 if (outside && connectorWidth) {
12807                                         connector = point.connector;
12808
12809                                         connectorPath = softConnector ? [
12810                                                 M,
12811                                                 x + (labelPos[6] === 'left' ? 5 : -5), y, // end of the string at the label
12812                                                 'C',
12813                                                 x, y, // first break, next to the label
12814                                                 2 * labelPos[2] - labelPos[4], 2 * labelPos[3] - labelPos[5],
12815                                                 labelPos[2], labelPos[3], // second break
12816                                                 L,
12817                                                 labelPos[4], labelPos[5] // base
12818                                         ] : [
12819                                                 M,
12820                                                 x + (labelPos[6] === 'left' ? 5 : -5), y, // end of the string at the label
12821                                                 L,
12822                                                 labelPos[2], labelPos[3], // second break
12823                                                 L,
12824                                                 labelPos[4], labelPos[5] // base
12825                                         ];
12826
12827                                         if (connector) {
12828                                                 connector.animate({ d: connectorPath });
12829                                                 connector.attr('visibility', visibility);
12830
12831                                         } else {
12832                                                 point.connector = connector = series.chart.renderer.path(connectorPath).attr({
12833                                                         'stroke-width': connectorWidth,
12834                                                         stroke: options.connectorColor || point.color || '#606060',
12835                                                         visibility: visibility,
12836                                                         zIndex: 3
12837                                                 })
12838                                                 .translate(chart.plotLeft, chart.plotTop)
12839                                                 .add();
12840                                         }
12841                                 }
12842                         }
12843                 }
12844         },
12845
12846         /**
12847          * Draw point specific tracker objects. Inherit directly from column series.
12848          */
12849         drawTracker: ColumnSeries.prototype.drawTracker,
12850
12851         /**
12852          * Pies don't have point marker symbols
12853          */
12854         getSymbol: function () {}
12855
12856 });
12857 seriesTypes.pie = PieSeries;
12858
12859 /* ****************************************************************************
12860  * Start data grouping module                                                                                            *
12861  ******************************************************************************/
12862 /*jslint white:true */
12863 var DATA_GROUPING = 'dataGrouping',
12864         seriesProto = Series.prototype,
12865         baseProcessData = seriesProto.processData,
12866         baseGeneratePoints = seriesProto.generatePoints,
12867         baseDestroy = seriesProto.destroy,
12868         baseTooltipHeaderFormatter = seriesProto.tooltipHeaderFormatter,
12869         NUMBER = 'number',
12870         
12871         commonOptions = {
12872                         approximation: 'average', // average, open, high, low, close, sum
12873                         //forced: undefined,
12874                         groupPixelWidth: 2,
12875                         // the first one is the point or start value, the second is the start value if we're dealing with range,
12876                         // the third one is the end value if dealing with a range
12877                         dateTimeLabelFormats: hash( 
12878                                 MILLISECOND, ['%A, %b %e, %H:%M:%S.%L', '%A, %b %e, %H:%M:%S.%L', '-%H:%M:%S.%L'],
12879                                 SECOND, ['%A, %b %e, %H:%M:%S', '%A, %b %e, %H:%M:%S', '-%H:%M:%S'],
12880                                 MINUTE, ['%A, %b %e, %H:%M', '%A, %b %e, %H:%M', '-%H:%M'],
12881                                 HOUR, ['%A, %b %e, %H:%M', '%A, %b %e, %H:%M', '-%H:%M'],
12882                                 DAY, ['%A, %b %e, %Y', '%A, %b %e', '-%A, %b %e, %Y'],
12883                                 WEEK, ['Week from %A, %b %e, %Y', '%A, %b %e', '-%A, %b %e, %Y'],
12884                                 MONTH, ['%B %Y', '%B', '-%B %Y'],
12885                                 YEAR, ['%Y', '%Y', '-%Y']
12886                         )
12887                         // smoothed = false, // enable this for navigator series only
12888                 },
12889                 
12890                 // units are defined in a separate array to allow complete overriding in case of a user option
12891                 defaultDataGroupingUnits = [[
12892                                 MILLISECOND, // unit name
12893                                 [1, 2, 5, 10, 20, 25, 50, 100, 200, 500] // allowed multiples
12894                         ], [
12895                                 SECOND,
12896                                 [1, 2, 5, 10, 15, 30]
12897                         ], [
12898                                 MINUTE,
12899                                 [1, 2, 5, 10, 15, 30]
12900                         ], [
12901                                 HOUR,
12902                                 [1, 2, 3, 4, 6, 8, 12]
12903                         ], [
12904                                 DAY,
12905                                 [1]
12906                         ], [
12907                                 WEEK,
12908                                 [1]
12909                         ], [
12910                                 MONTH,
12911                                 [1, 3, 6]
12912                         ], [
12913                                 YEAR,
12914                                 null
12915                         ]
12916                 ],
12917         
12918         /**
12919          * Define the available approximation types. The data grouping approximations takes an array
12920          * or numbers as the first parameter. In case of ohlc, four arrays are sent in as four parameters.
12921          * Each array consists only of numbers. In case null values belong to the group, the property
12922          * .hasNulls will be set to true on the array.
12923          */
12924         approximations = {
12925                 sum: function (arr) {
12926                         var len = arr.length, 
12927                                 ret;
12928                                 
12929                         // 1. it consists of nulls exclusively
12930                         if (!len && arr.hasNulls) {
12931                                 ret = null;
12932                         // 2. it has a length and real values
12933                         } else if (len) {
12934                                 ret = 0;
12935                                 while (len--) {
12936                                         ret += arr[len];
12937                                 }
12938                         }
12939                         // 3. it has zero length, so just return undefined 
12940                         // => doNothing()
12941                         
12942                         return ret;
12943                 },
12944                 average: function (arr) {
12945                         var len = arr.length,
12946                                 ret = approximations.sum(arr);
12947                                 
12948                         // If we have a number, return it divided by the length. If not, return
12949                         // null or undefined based on what the sum method finds.
12950                         if (typeof ret === NUMBER && len) {
12951                                 ret = ret / len;
12952                         }
12953                         
12954                         return ret;
12955                 },
12956                 open: function (arr) {
12957                         return arr.length ? arr[0] : (arr.hasNulls ? null : UNDEFINED);
12958                 },
12959                 high: function (arr) {
12960                         return arr.length ? arrayMax(arr) : (arr.hasNulls ? null : UNDEFINED);
12961                 },
12962                 low: function (arr) {
12963                         return arr.length ? arrayMin(arr) : (arr.hasNulls ? null : UNDEFINED);
12964                 },
12965                 close: function (arr) {
12966                         return arr.length ? arr[arr.length - 1] : (arr.hasNulls ? null : UNDEFINED);
12967                 },
12968                 // ohlc is a special case where a multidimensional array is input and an array is output
12969                 ohlc: function (open, high, low, close) {
12970                         open = approximations.open(open);
12971                         high = approximations.high(high);
12972                         low = approximations.low(low);
12973                         close = approximations.close(close);
12974                         
12975                         if (typeof open === NUMBER || typeof high === NUMBER || typeof low === NUMBER || typeof close === NUMBER) {
12976                                 return [open, high, low, close];
12977                         } 
12978                         // else, return is undefined
12979                 }
12980         };
12981
12982 /*jslint white:false */
12983
12984 /**
12985  * Takes parallel arrays of x and y data and groups the data into intervals defined by groupPositions, a collection
12986  * of starting x values for each group.
12987  */
12988 seriesProto.groupData = function (xData, yData, groupPositions, approximation) {
12989         var series = this,
12990                 data = series.data,
12991                 dataOptions = series.options.data,
12992                 groupedXData = [],
12993                 groupedYData = [],
12994                 dataLength = xData.length,
12995                 pointX,
12996                 pointY,
12997                 groupedY,
12998                 handleYData = !!yData, // when grouping the fake extended axis for panning, we don't need to consider y
12999                 values1 = [],
13000                 values2 = [],
13001                 values3 = [],
13002                 values4 = [],
13003                 approximationFn = typeof approximation === 'function' ? approximation : approximations[approximation],
13004                 i;
13005         
13006                 for (i = 0; i <= dataLength; i++) {
13007
13008                         // when a new group is entered, summarize and initiate the previous group
13009                         while ((groupPositions[1] !== UNDEFINED && xData[i] >= groupPositions[1]) ||
13010                                         i === dataLength) { // get the last group
13011
13012                                 // get group x and y 
13013                                 pointX = groupPositions.shift();                                
13014                                 groupedY = approximationFn(values1, values2, values3, values4);
13015                                 
13016                                 // push the grouped data                
13017                                 if (groupedY !== UNDEFINED) {
13018                                         groupedXData.push(pointX);
13019                                         groupedYData.push(groupedY);
13020                                 }
13021                                 
13022                                 // reset the aggregate arrays
13023                                 values1 = [];
13024                                 values2 = [];
13025                                 values3 = [];
13026                                 values4 = [];
13027                                 
13028                                 // don't loop beyond the last group
13029                                 if (i === dataLength) {
13030                                         break;
13031                                 }
13032                         }
13033                         
13034                         // break out
13035                         if (i === dataLength) {
13036                                 break;
13037                         }
13038                         
13039                         // for each raw data point, push it to an array that contains all values for this specific group
13040                         pointY = handleYData ? yData[i] : null;
13041                         if (approximation === 'ohlc') {
13042                                 
13043                                 var index = series.cropStart + i,
13044                                         point = (data && data[index]) || series.pointClass.prototype.applyOptions.apply({}, [dataOptions[index]]),
13045                                         open = point.open,
13046                                         high = point.high,
13047                                         low = point.low,
13048                                         close = point.close;
13049                                 
13050                                 
13051                                 if (typeof open === NUMBER) {
13052                                         values1.push(open);
13053                                 } else if (open === null) {
13054                                         values1.hasNulls = true;
13055                                 }
13056                                 
13057                                 if (typeof high === NUMBER) {
13058                                         values2.push(high);
13059                                 } else if (high === null) {
13060                                         values2.hasNulls = true;
13061                                 }
13062                                 
13063                                 if (typeof low === NUMBER) {
13064                                         values3.push(low);
13065                                 } else if (low === null) {
13066                                         values3.hasNulls = true;
13067                                 }
13068                                 
13069                                 if (typeof close === NUMBER) {
13070                                         values4.push(close);
13071                                 } else if (close === null) {
13072                                         values4.hasNulls = true;
13073                                 }
13074                         } else {
13075                                 if (typeof pointY === NUMBER) {
13076                                         values1.push(pointY);
13077                                 } else if (pointY === null) {
13078                                         values1.hasNulls = true;
13079                                 }
13080                         }
13081
13082                 }
13083         return [groupedXData, groupedYData];
13084 };
13085
13086 /**
13087  * Extend the basic processData method, that crops the data to the current zoom
13088  * range, with data grouping logic.
13089  */
13090 seriesProto.processData = function () {
13091         var series = this,
13092                 options = series.options,
13093                 dataGroupingOptions = options[DATA_GROUPING],
13094                 groupingEnabled = dataGroupingOptions && dataGroupingOptions.enabled,
13095                 groupedData = series.groupedData,
13096                 hasGroupedData;
13097
13098         // run base method
13099         series.forceCrop = groupingEnabled; // #334
13100         
13101         // skip if processData returns false or if grouping is disabled (in that order)
13102         if (baseProcessData.apply(series, arguments) === false || !groupingEnabled) {
13103                 return;
13104                 
13105         } else {
13106                 // clear previous groups, #622, #740
13107                 each(groupedData || [], function (point, i) {
13108                         if (point) {
13109                                 groupedData[i] = point.destroy ? point.destroy() : null;
13110                         }
13111                 });
13112         }
13113         
13114         var i,
13115                 chart = series.chart,
13116                 processedXData = series.processedXData,
13117                 processedYData = series.processedYData,
13118                 plotSizeX = chart.plotSizeX,
13119                 xAxis = series.xAxis,
13120                 groupPixelWidth = pick(xAxis.groupPixelWidth, dataGroupingOptions.groupPixelWidth),
13121                 dataLength = processedXData.length,
13122                 chartSeries = chart.series,
13123                 nonGroupedPointRange = series.pointRange;
13124
13125         // attempt to solve #334: if multiple series are compared on the same x axis, give them the same
13126         // group pixel width
13127         if (!xAxis.groupPixelWidth) {
13128                 i = chartSeries.length;
13129                 while (i--) {
13130                         if (chartSeries[i].xAxis === xAxis && chartSeries[i].options[DATA_GROUPING]) {
13131                                 groupPixelWidth = mathMax(groupPixelWidth, chartSeries[i].options[DATA_GROUPING].groupPixelWidth);
13132                         }
13133                 }
13134                 xAxis.groupPixelWidth = groupPixelWidth;
13135                 
13136         }
13137
13138         // Execute grouping if the amount of points is greater than the limit defined in groupPixelWidth
13139         if (dataLength > (plotSizeX / groupPixelWidth) || dataGroupingOptions.forced) {
13140                 hasGroupedData = true;
13141
13142                 series.points = null; // force recreation of point instances in series.translate
13143
13144                 var extremes = xAxis.getExtremes(),
13145                         xMin = extremes.min,
13146                         xMax = extremes.max,
13147                         groupIntervalFactor = (xAxis.getGroupIntervalFactor && xAxis.getGroupIntervalFactor(xMin, xMax, processedXData)) || 1,
13148                         interval = (groupPixelWidth * (xMax - xMin) / plotSizeX) * groupIntervalFactor,                 
13149                         groupPositions = (xAxis.getNonLinearTimeTicks || getTimeTicks)(
13150                                 normalizeTimeTickInterval(interval, dataGroupingOptions.units || defaultDataGroupingUnits),
13151                                 xMin, 
13152                                 xMax, 
13153                                 null, 
13154                                 processedXData, 
13155                                 series.closestPointRange
13156                         ),
13157                         groupedXandY = seriesProto.groupData.apply(series, [processedXData, processedYData, groupPositions, dataGroupingOptions.approximation]),
13158                         groupedXData = groupedXandY[0],
13159                         groupedYData = groupedXandY[1];
13160                         
13161                 // prevent the smoothed data to spill out left and right, and make
13162                 // sure data is not shifted to the left
13163                 if (dataGroupingOptions.smoothed) {
13164                         i = groupedXData.length - 1;
13165                         groupedXData[i] = xMax;
13166                         while (i-- && i > 0) {
13167                                 groupedXData[i] += interval / 2;
13168                         }
13169                         groupedXData[0] = xMin;
13170                 }
13171
13172                 // record what data grouping values were used
13173                 series.currentDataGrouping = groupPositions.info;
13174                 if (options.pointRange === null) { // null means auto, as for columns, candlesticks and OHLC
13175                         series.pointRange = groupPositions.info.totalRange;
13176                 }
13177                 series.closestPointRange = groupPositions.info.totalRange;
13178                 
13179                 // set series props
13180                 series.processedXData = groupedXData;
13181                 series.processedYData = groupedYData;
13182         } else {
13183                 series.currentDataGrouping = null;
13184                 series.pointRange = nonGroupedPointRange;
13185         }
13186
13187         series.hasGroupedData = hasGroupedData;
13188 };
13189
13190 seriesProto.generatePoints = function () {
13191         var series = this;
13192
13193         baseGeneratePoints.apply(series);
13194
13195         // record grouped data in order to let it be destroyed the next time processData runs
13196         series.groupedData = series.hasGroupedData ? series.points : null;
13197 };
13198
13199 /**
13200  * Make the tooltip's header reflect the grouped range
13201  */
13202 seriesProto.tooltipHeaderFormatter = function (key) {
13203         var series = this,
13204                 options = series.options,
13205                 tooltipOptions = series.tooltipOptions,
13206                 dataGroupingOptions = options.dataGrouping,
13207                 xDateFormat = tooltipOptions.xDateFormat,
13208                 xDateFormatEnd,
13209                 xAxis = series.xAxis,
13210                 currentDataGrouping,
13211                 dateTimeLabelFormats,
13212                 labelFormats,
13213                 formattedKey,
13214                 n,
13215                 ret;
13216         
13217         // apply only to grouped series
13218         if (xAxis && xAxis.options.type === 'datetime' && dataGroupingOptions) {
13219                 
13220                 // set variables
13221                 currentDataGrouping = series.currentDataGrouping;               
13222                 dateTimeLabelFormats = dataGroupingOptions.dateTimeLabelFormats;
13223                 
13224                 // if we have grouped data, use the grouping information to get the right format
13225                 if (currentDataGrouping) {
13226                         labelFormats = dateTimeLabelFormats[currentDataGrouping.unitName];
13227                         if (currentDataGrouping.count === 1) {
13228                                 xDateFormat = labelFormats[0];
13229                         } else {
13230                                 xDateFormat = labelFormats[1];
13231                                 xDateFormatEnd = labelFormats[2];
13232                         } 
13233                 // if not grouped, and we don't have set the xDateFormat option, get the best fit,
13234                 // so if the least distance between points is one minute, show it, but if the 
13235                 // least distance is one day, skip hours and minutes etc.
13236                 } else if (!xDateFormat) {
13237                         for (n in timeUnits) {
13238                                 if (timeUnits[n] >= xAxis.closestPointRange) {
13239                                         xDateFormat = dateTimeLabelFormats[n][0];
13240                                         break;
13241                                 }       
13242                         }               
13243                 }
13244                 
13245                 // now format the key
13246                 formattedKey = dateFormat(xDateFormat, key);
13247                 if (xDateFormatEnd) {
13248                         formattedKey += dateFormat(xDateFormatEnd, key + currentDataGrouping.totalRange - 1);
13249                 }
13250                 
13251                 // return the replaced format
13252                 ret = tooltipOptions.headerFormat.replace('{point.key}', formattedKey);
13253         
13254         // else, fall back to the regular formatter
13255         } else {
13256                 ret = baseTooltipHeaderFormatter.apply(series, [key]);
13257         }
13258         
13259         return ret;
13260 };
13261
13262 /**
13263  * Extend the series destroyer
13264  */
13265 seriesProto.destroy = function () {
13266         var series = this,
13267                 groupedData = series.groupedData || [],
13268                 i = groupedData.length;
13269
13270         while (i--) {
13271                 if (groupedData[i]) {
13272                         groupedData[i].destroy();
13273                 }
13274         }
13275         baseDestroy.apply(series);
13276 };
13277
13278
13279 // Extend the plot options
13280
13281 // line types
13282 defaultPlotOptions.line[DATA_GROUPING] =
13283         defaultPlotOptions.spline[DATA_GROUPING] =
13284         defaultPlotOptions.area[DATA_GROUPING] =
13285         defaultPlotOptions.areaspline[DATA_GROUPING] = commonOptions;
13286
13287 // bar-like types (OHLC and candleticks inherit this as the classes are not yet built)
13288 defaultPlotOptions.column[DATA_GROUPING] = merge(commonOptions, {
13289                 approximation: 'sum',
13290                 groupPixelWidth: 10
13291 });
13292 /* ****************************************************************************
13293  * End data grouping module                                                                                                *
13294  ******************************************************************************//* ****************************************************************************
13295  * Start OHLC series code                                                                                                        *
13296  *****************************************************************************/
13297
13298 // 1 - Set default options
13299 defaultPlotOptions.ohlc = merge(defaultPlotOptions.column, {
13300         lineWidth: 1,
13301         dataGrouping: {
13302                 approximation: 'ohlc',
13303                 enabled: true,
13304                 groupPixelWidth: 5 // allows to be packed tighter than candlesticks
13305         },
13306         states: {
13307                 hover: {
13308                         lineWidth: 3
13309                 }
13310         }
13311 });
13312
13313 // 2- Create the OHLCPoint object
13314 var OHLCPoint = extendClass(Point, {
13315         /**
13316          * Apply the options containing the x and OHLC data and possible some extra properties.
13317          * This is called on point init or from point.update. Extends base Point by adding
13318          * multiple y-like values.
13319          *
13320          * @param {Object} options
13321          */
13322         applyOptions: function (options) {
13323                 var point = this,
13324                         series = point.series,
13325                         i = 0;
13326
13327
13328                 // object input for example:
13329                 // { x: Date(2010, 0, 1), open: 7.88, high: 7.99, low: 7.02, close: 7.65 }
13330                 if (typeof options === 'object' && typeof options.length !== 'number') {
13331
13332                         // copy options directly to point
13333                         extend(point, options);
13334
13335                         point.options = options;
13336                 } else if (options.length) { // array
13337                         // with leading x value
13338                         if (options.length === 5) {
13339                                 if (typeof options[0] === 'string') {
13340                                         point.name = options[0];
13341                                 } else if (typeof options[0] === 'number') {
13342                                         point.x = options[0];
13343                                 }
13344                                 i++;
13345                         }
13346                         point.open = options[i++];
13347                         point.high = options[i++];
13348                         point.low = options[i++];
13349                         point.close = options[i++];
13350                 }
13351
13352                 /*
13353                  * If no x is set by now, get auto incremented value. All points must have an
13354                  * x value, however the y value can be null to create a gap in the series
13355                  */
13356                 point.y = point.high;
13357                 if (point.x === UNDEFINED && series) {
13358                         point.x = series.autoIncrement();
13359                 }
13360                 return point;
13361         },
13362
13363         /**
13364          * A specific OHLC tooltip formatter
13365          */
13366         tooltipFormatter: function () {
13367                 var point = this,
13368                         series = point.series;
13369
13370                 return ['<span style="color:' + series.color + ';font-weight:bold">', (point.name || series.name), '</span><br/>',
13371                         'Open: ', point.open, '<br/>',
13372                         'High: ', point.high, '<br/>',
13373                         'Low: ', point.low, '<br/>',
13374                         'Close: ', point.close, '<br/>'].join('');
13375
13376         }
13377
13378 });
13379
13380 // 3 - Create the OHLCSeries object
13381 var OHLCSeries = extendClass(seriesTypes.column, {
13382         type: 'ohlc',
13383         valueCount: 4, // four values per point
13384         pointClass: OHLCPoint,
13385         useThreshold: false,
13386
13387         pointAttrToOptions: { // mapping between SVG attributes and the corresponding options
13388                 stroke: 'color',
13389                 'stroke-width': 'lineWidth'
13390         },
13391
13392
13393         /**
13394          * Translate data points from raw values x and y to plotX and plotY
13395          */
13396         translate: function () {
13397                 var series = this,
13398                         yAxis = series.yAxis;
13399
13400                 seriesTypes.column.prototype.translate.apply(series);
13401
13402                 // do the translation
13403                 each(series.points, function (point) {
13404                         // the graphics
13405                         if (point.open !== null) {
13406                                 point.plotOpen = yAxis.translate(point.open, 0, 1, 0, 1);
13407                         }
13408                         if (point.close !== null) {
13409                                 point.plotClose = yAxis.translate(point.close, 0, 1, 0, 1);
13410                         }
13411
13412                 });
13413         },
13414
13415         /**
13416          * Draw the data points
13417          */
13418         drawPoints: function () {
13419                 var series = this,
13420                         points = series.points,
13421                         chart = series.chart,
13422                         pointAttr,
13423                         plotOpen,
13424                         plotClose,
13425                         crispCorr,
13426                         halfWidth,
13427                         path,
13428                         graphic,
13429                         crispX;
13430
13431
13432                 each(points, function (point) {
13433                         if (point.plotY !== UNDEFINED) {
13434
13435                                 graphic = point.graphic;
13436                                 pointAttr = point.pointAttr[point.selected ? 'selected' : ''];
13437
13438                                 // crisp vector coordinates
13439                                 crispCorr = (pointAttr['stroke-width'] % 2) / 2;
13440                                 crispX = mathRound(point.plotX) + crispCorr;
13441                                 halfWidth = mathRound(point.barW / 2);
13442
13443                                 // the vertical stem
13444                                 path = [
13445                                         'M',
13446                                         crispX, mathRound(point.yBottom),
13447                                         'L',
13448                                         crispX, mathRound(point.plotY)
13449                                 ];
13450
13451                                 // open
13452                                 if (point.open !== null) {
13453                                         plotOpen = mathRound(point.plotOpen) + crispCorr;
13454                                         path.push(
13455                                                 'M',
13456                                                 crispX,
13457                                                 plotOpen,
13458                                                 'L',
13459                                                 crispX - halfWidth,
13460                                                 plotOpen
13461                                         );
13462                                 }
13463
13464                                 // close
13465                                 if (point.close !== null) {
13466                                         plotClose = mathRound(point.plotClose) + crispCorr;
13467                                         path.push(
13468                                                 'M',
13469                                                 crispX,
13470                                                 plotClose,
13471                                                 'L',
13472                                                 crispX + halfWidth,
13473                                                 plotClose
13474                                         );
13475                                 }
13476
13477                                 // create and/or update the graphic
13478                                 if (graphic) {
13479                                         graphic.animate({ d: path });
13480                                 } else {
13481                                         point.graphic = chart.renderer.path(path)
13482                                                 .attr(pointAttr)
13483                                                 .add(series.group);
13484                                 }
13485
13486                         }
13487
13488
13489                 });
13490
13491         },
13492
13493         /**
13494          * Disable animation
13495          */
13496         animate: null
13497
13498
13499 });
13500 seriesTypes.ohlc = OHLCSeries;
13501 /* ****************************************************************************
13502  * End OHLC series code                                                                                                    *
13503  *****************************************************************************/
13504 /* ****************************************************************************
13505  * Start Candlestick series code                                                                                          *
13506  *****************************************************************************/
13507
13508 // 1 - set default options
13509 defaultPlotOptions.candlestick = merge(defaultPlotOptions.column, {
13510         dataGrouping: {
13511                 approximation: 'ohlc',
13512                 enabled: true
13513         },
13514         lineColor: 'black',
13515         lineWidth: 1,
13516         upColor: 'white',
13517         states: {
13518                 hover: {
13519                         lineWidth: 2
13520                 }
13521         }
13522 });
13523
13524 // 2 - Create the CandlestickSeries object
13525 var CandlestickSeries = extendClass(OHLCSeries, {
13526         type: 'candlestick',
13527
13528         /**
13529          * One-to-one mapping from options to SVG attributes
13530          */
13531         pointAttrToOptions: { // mapping between SVG attributes and the corresponding options
13532                 fill: 'color',
13533                 stroke: 'lineColor',
13534                 'stroke-width': 'lineWidth'
13535         },
13536
13537         /**
13538          * Postprocess mapping between options and SVG attributes
13539          */
13540         getAttribs: function () {
13541                 OHLCSeries.prototype.getAttribs.apply(this, arguments);
13542                 var series = this,
13543                         options = series.options,
13544                         stateOptions = options.states,
13545                         upColor = options.upColor,
13546                         seriesDownPointAttr = merge(series.pointAttr);
13547
13548                 seriesDownPointAttr[''].fill = upColor;
13549                 seriesDownPointAttr.hover.fill = stateOptions.hover.upColor || upColor;
13550                 seriesDownPointAttr.select.fill = stateOptions.select.upColor || upColor;
13551
13552                 each(series.points, function (point) {
13553                         if (point.open < point.close) {
13554                                 point.pointAttr = seriesDownPointAttr;
13555                         }
13556                 });
13557         },
13558
13559         /**
13560          * Draw the data points
13561          */
13562         drawPoints: function () {
13563                 var series = this,  //state = series.state,
13564                         points = series.points,
13565                         chart = series.chart,
13566                         pointAttr,
13567                         plotOpen,
13568                         plotClose,
13569                         topBox,
13570                         bottomBox,
13571                         crispCorr,
13572                         crispX,
13573                         graphic,
13574                         path,
13575                         halfWidth;
13576
13577
13578                 each(points, function (point) {
13579
13580                         graphic = point.graphic;
13581                         if (point.plotY !== UNDEFINED) {
13582
13583                                 pointAttr = point.pointAttr[point.selected ? 'selected' : ''];
13584
13585                                 // crisp vector coordinates
13586                                 crispCorr = (pointAttr['stroke-width'] % 2) / 2;
13587                                 crispX = mathRound(point.plotX) + crispCorr;
13588                                 plotOpen = mathRound(point.plotOpen) + crispCorr;
13589                                 plotClose = mathRound(point.plotClose) + crispCorr;
13590                                 topBox = math.min(plotOpen, plotClose);
13591                                 bottomBox = math.max(plotOpen, plotClose);
13592                                 halfWidth = mathRound(point.barW / 2);
13593
13594                                 // create the path
13595                                 path = [
13596                                         'M',
13597                                         crispX - halfWidth, bottomBox,
13598                                         'L',
13599                                         crispX - halfWidth, topBox,
13600                                         'L',
13601                                         crispX + halfWidth, topBox,
13602                                         'L',
13603                                         crispX + halfWidth, bottomBox,
13604                                         'L',
13605                                         crispX - halfWidth, bottomBox,
13606                                         'M',
13607                                         crispX, bottomBox,
13608                                         'L',
13609                                         crispX, mathRound(point.yBottom),
13610                                         'M',
13611                                         crispX, topBox,
13612                                         'L',
13613                                         crispX, mathRound(point.plotY),
13614                                         'Z'
13615                                 ];
13616
13617                                 if (graphic) {
13618                                         graphic.animate({ d: path });
13619                                 } else {
13620                                         point.graphic = chart.renderer.path(path)
13621                                                 .attr(pointAttr)
13622                                                 .add(series.group);
13623                                 }
13624
13625                         }
13626                 });
13627
13628         }
13629
13630
13631 });
13632
13633 seriesTypes.candlestick = CandlestickSeries;
13634
13635 /* ****************************************************************************
13636  * End Candlestick series code                                                                                          *
13637  *****************************************************************************/
13638 /* ****************************************************************************
13639  * Start Flags series code                                                                                                      *
13640  *****************************************************************************/
13641
13642 var symbols = SVGRenderer.prototype.symbols;
13643
13644 // 1 - set default options
13645 defaultPlotOptions.flags = merge(defaultPlotOptions.column, {
13646         dataGrouping: null,
13647         fillColor: 'white',
13648         lineWidth: 1,
13649         pointRange: 0, // #673
13650         //radius: 2,
13651         shape: 'flag',
13652         stackDistance: 7,
13653         states: {
13654                 hover: {
13655                         lineColor: 'black',
13656                         fillColor: '#FCFFC5'
13657                 }
13658         },
13659         style: {
13660                 fontSize: '11px',
13661                 fontWeight: 'bold',
13662                 textAlign: 'center'
13663         },
13664         y: -30
13665 });
13666
13667 // 2 - Create the CandlestickSeries object
13668 seriesTypes.flags = extendClass(seriesTypes.column, {
13669         type: 'flags',
13670         noSharedTooltip: true,
13671         useThreshold: false,
13672         /**
13673          * Inherit the initialization from base Series
13674          */
13675         init: Series.prototype.init,
13676
13677         /**
13678          * One-to-one mapping from options to SVG attributes
13679          */
13680         pointAttrToOptions: { // mapping between SVG attributes and the corresponding options
13681                 fill: 'fillColor',
13682                 stroke: 'color',
13683                 'stroke-width': 'lineWidth',
13684                 r: 'radius'
13685         },
13686
13687         /**
13688          * Extend the translate method by placing the point on the related series
13689          */
13690         translate: function () {
13691
13692                 seriesTypes.column.prototype.translate.apply(this);
13693
13694                 var series = this,
13695                         options = series.options,
13696                         chart = series.chart,
13697                         points = series.points,
13698                         cursor = points.length - 1,
13699                         i,
13700                         point,
13701                         lastPoint,
13702                         optionsOnSeries = options.onSeries,
13703                         onSeries = optionsOnSeries && chart.get(optionsOnSeries),
13704                         onData,
13705                         leftPoint,
13706                         rightPoint;
13707
13708
13709                 // relate to a master series
13710                 if (onSeries) {
13711                         onData = onSeries.points;
13712                         i = onData.length;
13713
13714                         // sort the data points
13715                         points.sort(function (a, b) {
13716                                 return (a.x - b.x);
13717                         });
13718
13719                         while (i-- && points[cursor]) {
13720                                 point = points[cursor];
13721                                 leftPoint = onData[i];
13722                                 if (leftPoint.x <= point.x) {
13723                                         point.plotY = leftPoint.plotY;
13724                                         
13725                                         // interpolate between points, #666
13726                                         if (leftPoint.x < point.x) { 
13727                                                 rightPoint = onData[i + 1];
13728                                                 if (rightPoint) {
13729                                                         point.plotY += 
13730                                                                 ((point.x - leftPoint.x) / (rightPoint.x - leftPoint.x)) * // the distance ratio, between 0 and 1 
13731                                                                 (rightPoint.plotY - leftPoint.plotY); // the y distance
13732                                                 }
13733                                         }
13734                                         
13735                                         cursor--;
13736                                         i++; // check again for points in the same x position
13737                                         if (cursor < 0) {
13738                                                 break;
13739                                         }
13740                                 }
13741                         }
13742                 }
13743
13744                 each(points, function (point, i) {
13745                         // place on y axis or custom position
13746                         if (!onSeries) {
13747                                 point.plotY = point.y === UNDEFINED ? chart.plotHeight : point.plotY;
13748                         }
13749                         // if multiple flags appear at the same x, order them into a stack
13750                         lastPoint = points[i - 1];
13751                         if (lastPoint && lastPoint.plotX === point.plotX) {
13752                                 if (lastPoint.stackIndex === UNDEFINED) {
13753                                         lastPoint.stackIndex = 0;
13754                                 }
13755                                 point.stackIndex = lastPoint.stackIndex + 1;
13756                         }
13757
13758                 });
13759
13760
13761         },
13762
13763         /**
13764          * Draw the markers
13765          */
13766         drawPoints: function () {
13767                 var series = this,
13768                         pointAttr,
13769                         points = series.points,
13770                         chart = series.chart,
13771                         renderer = chart.renderer,
13772                         plotX,
13773                         plotY,
13774                         options = series.options,
13775                         optionsY = options.y,
13776                         shape = options.shape,
13777                         box,
13778                         bBox,
13779                         i,
13780                         point,
13781                         graphic,
13782                         connector,
13783                         stackIndex,
13784                         crisp = (options.lineWidth % 2 / 2),
13785                         anchorX,
13786                         anchorY;
13787
13788                 i = points.length;
13789                 while (i--) {
13790                         point = points[i];
13791                         plotX = point.plotX + crisp;
13792                         stackIndex = point.stackIndex;
13793                         plotY = point.plotY + optionsY + crisp - (stackIndex !== UNDEFINED && stackIndex * options.stackDistance);
13794                         // outside to the left, on series but series is clipped
13795                         if (isNaN(plotY)) {
13796                                 plotY = 0;
13797                         }
13798                         anchorX = stackIndex ? UNDEFINED : point.plotX + crisp; // skip connectors for higher level stacked points
13799                         anchorY = stackIndex ? UNDEFINED : point.plotY;
13800
13801                         graphic = point.graphic;
13802                         connector = point.connector;
13803
13804                         // only draw the point if y is defined
13805                         if (plotY !== UNDEFINED) {
13806                                 // shortcuts
13807                                 pointAttr = point.pointAttr[point.selected ? 'select' : ''];
13808                                 if (graphic) { // update
13809                                         graphic.attr({
13810                                                 x: plotX,
13811                                                 y: plotY,
13812                                                 r: pointAttr.r,
13813                                                 anchorX: anchorX,
13814                                                 anchorY: anchorY
13815                                         });
13816                                 } else {
13817                                         graphic = point.graphic = renderer.label(
13818                                                 point.options.title || options.title || 'A',
13819                                                 plotX,
13820                                                 plotY,
13821                                                 shape,
13822                                                 anchorX,
13823                                                 anchorY
13824                                         )
13825                                         .css(merge(options.style, point.style))
13826                                         .attr(pointAttr)
13827                                         .attr({
13828                                                 align: shape === 'flag' ? 'left' : 'center',
13829                                                 width: options.width,
13830                                                 height: options.height
13831                                         })
13832                                         .add(series.group)
13833                                         .shadow(options.shadow);
13834
13835                                 }
13836
13837                                 // get the bounding box
13838                                 box = graphic.box;
13839                                 bBox = box.getBBox();
13840
13841                                 // set the shape arguments for the tracker element
13842                                 point.shapeArgs = extend(
13843                                         bBox,
13844                                         {
13845                                                 x: plotX - (shape === 'flag' ? 0 : box.attr('width') / 2), // flags align left, else align center
13846                                                 y: plotY
13847                                         }
13848                                 );
13849
13850                         }
13851
13852                 }
13853
13854         },
13855
13856         /**
13857          * Extend the column trackers with listeners to expand and contract stacks
13858          */
13859         drawTracker: function () {
13860                 var series = this;
13861
13862                 seriesTypes.column.prototype.drawTracker.apply(series);
13863
13864                 // put each point in front on mouse over, this allows readability of vertically
13865                 // stacked elements as well as tight points on the x axis
13866                 each(series.points, function (point) {
13867                         addEvent(point.tracker.element, 'mouseover', function () {
13868                                 point.graphic.toFront();
13869                         });
13870                 });
13871         },
13872
13873         /**
13874          * Override the regular tooltip formatter by returning the point text given
13875          * in the options
13876          */
13877         tooltipFormatter: function (item) {
13878                 return item.point.text;
13879         },
13880
13881         /**
13882          * Disable animation
13883          */
13884         animate: function () {}
13885
13886 });
13887
13888 // create the flag icon with anchor
13889 symbols.flag = function (x, y, w, h, options) {
13890         var anchorX = (options && options.anchorX) || x,
13891                 anchorY = (options &&  options.anchorY) || y;
13892
13893         return [
13894                 'M', anchorX, anchorY,
13895                 'L', x, y + h,
13896                 x, y,
13897                 x + w, y,
13898                 x + w, y + h,
13899                 x, y + h,
13900                 'M', anchorX, anchorY,
13901                 'Z'
13902         ];
13903 };
13904
13905 // create the circlepin and squarepin icons with anchor
13906 each(['circle', 'square'], function (shape) {
13907         symbols[shape + 'pin'] = function (x, y, w, h, options) {
13908
13909                 var anchorX = options && options.anchorX,
13910                         anchorY = options &&  options.anchorY,
13911                         path = symbols[shape](x, y, w, h);
13912
13913                 if (anchorX && anchorY) {
13914                         path.push('M', anchorX, y + h, 'L', anchorX, anchorY);
13915                 }
13916
13917                 return path;
13918         };
13919 });
13920
13921 // The symbol callbacks are generated on the SVGRenderer object in all browsers. Even
13922 // VML browsers need this in order to generate shapes in export. Now share
13923 // them with the VMLRenderer.
13924 if (Renderer === VMLRenderer) {
13925         each(['flag', 'circlepin', 'squarepin'], function (shape) {
13926                 VMLRenderer.prototype.symbols[shape] = symbols[shape];
13927         });
13928 }
13929
13930 /* ****************************************************************************
13931  * End Flags series code                                                                                                          *
13932  *****************************************************************************/
13933
13934 // constants
13935 var MOUSEDOWN = hasTouch ? 'touchstart' : 'mousedown',
13936         MOUSEMOVE = hasTouch ? 'touchmove' : 'mousemove',
13937         MOUSEUP = hasTouch ? 'touchend' : 'mouseup';
13938
13939
13940
13941
13942 /* ****************************************************************************
13943  * Start Scroller code                                                                                                          *
13944  *****************************************************************************/
13945 /*jslint white:true */
13946 var buttonGradient = hash(
13947                 LINEAR_GRADIENT, { x1: 0, y1: 0, x2: 0, y2: 1 },
13948                 STOPS, [
13949                         [0, '#FFF'],
13950                         [1, '#CCC']
13951                 ]
13952         ),
13953         units = [].concat(defaultDataGroupingUnits); // copy
13954
13955 // add more resolution to units
13956 units[4] = [DAY, [1, 2, 3, 4]]; // allow more days
13957 units[5] = [WEEK, [1, 2, 3]]; // allow more weeks
13958
13959 extend(defaultOptions, {
13960         navigator: {
13961                 //enabled: true,
13962                 handles: {
13963                         backgroundColor: '#FFF',
13964                         borderColor: '#666'
13965                 },
13966                 height: 40,
13967                 margin: 10,
13968                 maskFill: 'rgba(255, 255, 255, 0.75)',
13969                 outlineColor: '#444',
13970                 outlineWidth: 1,
13971                 series: {
13972                         type: 'areaspline',
13973                         color: '#4572A7',
13974                         compare: null,
13975                         fillOpacity: 0.4,
13976                         dataGrouping: {
13977                                 approximation: 'average',
13978                                 groupPixelWidth: 2,
13979                                 smoothed: true,
13980                                 units: units
13981                         },
13982                         dataLabels: {
13983                                 enabled: false
13984                         },
13985                         id: PREFIX + 'navigator-series',
13986                         lineColor: '#4572A7',
13987                         lineWidth: 1,
13988                         marker: {
13989                                 enabled: false
13990                         },
13991                         pointRange: 0,
13992                         shadow: false
13993                 },
13994                 //top: undefined, // docs
13995                 xAxis: {
13996                         tickWidth: 0,
13997                         lineWidth: 0,
13998                         gridLineWidth: 1,
13999                         tickPixelInterval: 200,
14000                         labels: {
14001                                 align: 'left',
14002                                 x: 3,
14003                                 y: -4
14004                         }
14005                 },
14006                 yAxis: {
14007                         gridLineWidth: 0,
14008                         startOnTick: false,
14009                         endOnTick: false,
14010                         minPadding: 0.1,
14011                         maxPadding: 0.1,
14012                         labels: {
14013                                 enabled: false
14014                         },
14015                         title: {
14016                                 text: null
14017                         },
14018                         tickWidth: 0
14019                 }
14020         },
14021         scrollbar: {
14022                 //enabled: true
14023                 height: hasTouch ? 20 : 14,
14024                 barBackgroundColor: buttonGradient,
14025                 barBorderRadius: 2,
14026                 barBorderWidth: 1,
14027                 barBorderColor: '#666',
14028                 buttonArrowColor: '#666',
14029                 buttonBackgroundColor: buttonGradient,
14030                 buttonBorderColor: '#666',
14031                 buttonBorderRadius: 2,
14032                 buttonBorderWidth: 1,
14033                 rifleColor: '#666',
14034                 trackBackgroundColor: hash(
14035                         LINEAR_GRADIENT, { x1: 0, y1: 0, x2: 0, y2: 1 },
14036                         STOPS, [
14037                                 [0, '#EEE'],
14038                                 [1, '#FFF']
14039                         ]
14040                 ),
14041                 trackBorderColor: '#CCC',
14042                 trackBorderWidth: 1
14043                 // trackBorderRadius: 0
14044         }
14045 });
14046 /*jslint white:false */
14047
14048 /**
14049  * The Scroller class
14050  * @param {Object} chart
14051  */
14052 Highcharts.Scroller = function (chart) {
14053
14054         var renderer = chart.renderer,
14055                 chartOptions = chart.options,
14056                 navigatorOptions = chartOptions.navigator,
14057                 navigatorEnabled = navigatorOptions.enabled,
14058                 navigatorLeft,
14059                 navigatorWidth,
14060                 navigatorSeries,
14061                 navigatorData,
14062                 scrollbarOptions = chartOptions.scrollbar,
14063                 scrollbarEnabled = scrollbarOptions.enabled,
14064                 grabbedLeft,
14065                 grabbedRight,
14066                 grabbedCenter,
14067                 otherHandlePos,
14068                 dragOffset,
14069                 hasDragged,
14070                 xAxis,
14071                 yAxis,
14072                 zoomedMin,
14073                 zoomedMax,
14074                 range,
14075
14076                 bodyStyle = document.body.style,
14077                 defaultBodyCursor,
14078
14079                 handlesOptions = navigatorOptions.handles,
14080                 height = navigatorEnabled ? navigatorOptions.height : 0,
14081                 outlineWidth = navigatorOptions.outlineWidth,
14082                 scrollbarHeight = scrollbarEnabled ? scrollbarOptions.height : 0,
14083                 outlineHeight = height + scrollbarHeight,
14084                 barBorderRadius = scrollbarOptions.barBorderRadius,
14085                 top,
14086                 halfOutline = outlineWidth / 2,
14087                 outlineTop,
14088                 scrollerLeft,
14089                 scrollerWidth,
14090                 rendered,
14091                 baseSeriesOption = navigatorOptions.baseSeries,
14092                 baseSeries = chart.series[baseSeriesOption] ||
14093                         (typeof baseSeriesOption === 'string' && chart.get(baseSeriesOption)) ||
14094                         chart.series[0],
14095
14096                 // element wrappers
14097                 leftShade,
14098                 rightShade,
14099                 outline,
14100                 handles = [],
14101                 scrollbarGroup,
14102                 scrollbarTrack,
14103                 scrollbar,
14104                 scrollbarRifles,
14105                 scrollbarButtons = [],
14106                 elementsToDestroy = []; // Array containing the elements to destroy when Scroller is destroyed
14107
14108         chart.resetZoomEnabled = false;
14109
14110         /**
14111          * Return the top of the navigation 
14112          */
14113         function getAxisTop(chartHeight) {
14114                 return navigatorOptions.top || chartHeight - height - scrollbarHeight - chartOptions.chart.spacingBottom;
14115         }
14116
14117         /**
14118          * Draw one of the handles on the side of the zoomed range in the navigator
14119          * @param {Number} x The x center for the handle
14120          * @param {Number} index 0 for left and 1 for right
14121          */
14122         function drawHandle(x, index) {
14123
14124                 var attr = {
14125                                 fill: handlesOptions.backgroundColor,
14126                                 stroke: handlesOptions.borderColor,
14127                                 'stroke-width': 1
14128                         },
14129                         tempElem;
14130
14131                 // create the elements
14132                 if (!rendered) {
14133
14134                         // the group
14135                         handles[index] = renderer.g()
14136                                 .css({ cursor: 'e-resize' })
14137                                 .attr({ zIndex: 4 - index }) // zIndex = 3 for right handle, 4 for left
14138                                 .add();
14139
14140                         // the rectangle
14141                         tempElem = renderer.rect(-4.5, 0, 9, 16, 3, 1)
14142                                 .attr(attr)
14143                                 .add(handles[index]);
14144                         elementsToDestroy.push(tempElem);
14145
14146                         // the rifles
14147                         tempElem = renderer.path([
14148                                         'M',
14149                                         -1.5, 4,
14150                                         'L',
14151                                         -1.5, 12,
14152                                         'M',
14153                                         0.5, 4,
14154                                         'L',
14155                                         0.5, 12
14156                                 ]).attr(attr)
14157                                 .add(handles[index]);
14158                         elementsToDestroy.push(tempElem);
14159                 }
14160
14161                 handles[index].translate(scrollerLeft + scrollbarHeight + parseInt(x, 10), top + height / 2 - 8);
14162         }
14163
14164         /**
14165          * Draw the scrollbar buttons with arrows
14166          * @param {Number} index 0 is left, 1 is right
14167          */
14168         function drawScrollbarButton(index) {
14169                 var tempElem;
14170                 if (!rendered) {
14171
14172                         scrollbarButtons[index] = renderer.g().add(scrollbarGroup);
14173
14174                         tempElem = renderer.rect(
14175                                 -0.5,
14176                                 -0.5,
14177                                 scrollbarHeight + 1, // +1 to compensate for crispifying in rect method
14178                                 scrollbarHeight + 1,
14179                                 scrollbarOptions.buttonBorderRadius,
14180                                 scrollbarOptions.buttonBorderWidth
14181                         ).attr({
14182                                 stroke: scrollbarOptions.buttonBorderColor,
14183                                 'stroke-width': scrollbarOptions.buttonBorderWidth,
14184                                 fill: scrollbarOptions.buttonBackgroundColor
14185                         }).add(scrollbarButtons[index]);
14186                         elementsToDestroy.push(tempElem);
14187
14188                         tempElem = renderer.path([
14189                                 'M',
14190                                 scrollbarHeight / 2 + (index ? -1 : 1), scrollbarHeight / 2 - 3,
14191                                 'L',
14192                                 scrollbarHeight / 2 + (index ? -1 : 1), scrollbarHeight / 2 + 3,
14193                                 scrollbarHeight / 2 + (index ? 2 : -2), scrollbarHeight / 2
14194                         ]).attr({
14195                                 fill: scrollbarOptions.buttonArrowColor
14196                         }).add(scrollbarButtons[index]);
14197                         elementsToDestroy.push(tempElem);
14198                 }
14199
14200                 // adjust the right side button to the varying length of the scroll track
14201                 if (index) {
14202                         scrollbarButtons[index].attr({
14203                                 translateX: scrollerWidth - scrollbarHeight
14204                         });
14205                 }
14206         }
14207
14208         /**
14209          * Render the navigator and scroll bar
14210          * @param {Number} min X axis value minimum
14211          * @param {Number} max X axis value maximum
14212          * @param {Number} pxMin Pixel value minimum
14213          * @param {Number} pxMax Pixel value maximum
14214          */
14215         function render(min, max, pxMin, pxMax) {
14216
14217                 // don't render the navigator until we have data (#486)
14218                 if (isNaN(min)) {
14219                         return;
14220                 }
14221
14222                 var strokeWidth,
14223                         scrollbarStrokeWidth = scrollbarOptions.barBorderWidth,
14224                         centerBarX;
14225
14226                 outlineTop = top + halfOutline;
14227                 navigatorLeft = pick(
14228                         xAxis.left,
14229                         chart.plotLeft + scrollbarHeight // in case of scrollbar only, without navigator
14230                 );
14231                 navigatorWidth = pick(xAxis.len, chart.plotWidth - 2 * scrollbarHeight);
14232                 scrollerLeft = navigatorLeft - scrollbarHeight;
14233                 scrollerWidth = navigatorWidth + 2 * scrollbarHeight;
14234
14235                 // Set the scroller x axis extremes to reflect the total. The navigator extremes
14236                 // should always be the extremes of the union of all series in the chart as
14237                 // well as the navigator series.
14238                 if (xAxis.getExtremes) {
14239                         var baseExtremes = chart.xAxis[0].getExtremes(), // the base
14240                                 noBase = baseExtremes.dataMin === null,
14241                                 navExtremes = xAxis.getExtremes(),
14242                                 newMin = mathMin(baseExtremes.dataMin, navExtremes.dataMin),
14243                                 newMax = mathMax(baseExtremes.dataMax, navExtremes.dataMax);
14244
14245                         if (!noBase && (newMin !== navExtremes.min || newMax !== navExtremes.max)) {
14246                                 xAxis.setExtremes(newMin, newMax, true, false);
14247                         }
14248                 }
14249
14250                 // get the pixel position of the handles
14251                 pxMin = pick(pxMin, xAxis.translate(min));
14252                 pxMax = pick(pxMax, xAxis.translate(max));
14253
14254
14255                 // handles are allowed to cross
14256                 zoomedMin = pInt(mathMin(pxMin, pxMax));
14257                 zoomedMax = pInt(mathMax(pxMin, pxMax));
14258                 range = zoomedMax - zoomedMin;
14259
14260                 // on first render, create all elements
14261                 if (!rendered) {
14262
14263                         if (navigatorEnabled) {
14264
14265                                 leftShade = renderer.rect()
14266                                         .attr({
14267                                                 fill: navigatorOptions.maskFill,
14268                                                 zIndex: 3
14269                                         }).add();
14270                                 rightShade = renderer.rect()
14271                                         .attr({
14272                                                 fill: navigatorOptions.maskFill,
14273                                                 zIndex: 3
14274                                         }).add();
14275                                 outline = renderer.path()
14276                                         .attr({
14277                                                 'stroke-width': outlineWidth,
14278                                                 stroke: navigatorOptions.outlineColor,
14279                                                 zIndex: 3
14280                                         })
14281                                         .add();
14282                         }
14283
14284                         if (scrollbarEnabled) {
14285
14286                                 // draw the scrollbar group
14287                                 scrollbarGroup = renderer.g().add();
14288
14289                                 // the scrollbar track
14290                                 strokeWidth = scrollbarOptions.trackBorderWidth;
14291                                 scrollbarTrack = renderer.rect().attr({
14292                                         y: -strokeWidth % 2 / 2,
14293                                         fill: scrollbarOptions.trackBackgroundColor,
14294                                         stroke: scrollbarOptions.trackBorderColor,
14295                                         'stroke-width': strokeWidth,
14296                                         r: scrollbarOptions.trackBorderRadius || 0,
14297                                         height: scrollbarHeight
14298                                 }).add(scrollbarGroup);
14299
14300                                 // the scrollbar itself
14301                                 scrollbar = renderer.rect()
14302                                         .attr({
14303                                                 y: -scrollbarStrokeWidth % 2 / 2,
14304                                                 height: scrollbarHeight,
14305                                                 fill: scrollbarOptions.barBackgroundColor,
14306                                                 stroke: scrollbarOptions.barBorderColor,
14307                                                 'stroke-width': scrollbarStrokeWidth,
14308                                                 r: barBorderRadius
14309                                         })
14310                                         .add(scrollbarGroup);
14311
14312                                 scrollbarRifles = renderer.path()
14313                                         .attr({
14314                                                 stroke: scrollbarOptions.rifleColor,
14315                                                 'stroke-width': 1
14316                                         })
14317                                         .add(scrollbarGroup);
14318                         }
14319                 }
14320
14321                 // place elements
14322                 if (navigatorEnabled) {
14323                         leftShade.attr({
14324                                 x: navigatorLeft,
14325                                 y: top,
14326                                 width: zoomedMin,
14327                                 height: height
14328                         });
14329                         rightShade.attr({
14330                                 x: navigatorLeft + zoomedMax,
14331                                 y: top,
14332                                 width: navigatorWidth - zoomedMax,
14333                                 height: height
14334                         });
14335                         outline.attr({ d: [
14336                                 M,
14337                                 scrollerLeft, outlineTop, // left
14338                                 L,
14339                                 navigatorLeft + zoomedMin + halfOutline, outlineTop, // upper left of zoomed range
14340                                 navigatorLeft + zoomedMin + halfOutline, outlineTop + outlineHeight - scrollbarHeight, // lower left of z.r.
14341                                 M,
14342                                 navigatorLeft + zoomedMax - halfOutline, outlineTop + outlineHeight - scrollbarHeight, // lower right of z.r.
14343                                 L,
14344                                 navigatorLeft + zoomedMax - halfOutline, outlineTop, // upper right of z.r.
14345                                 scrollerLeft + scrollerWidth, outlineTop // right
14346                         ]});
14347                         // draw handles
14348                         drawHandle(zoomedMin + halfOutline, 0);
14349                         drawHandle(zoomedMax + halfOutline, 1);
14350                 }
14351
14352                 // draw the scrollbar
14353                 if (scrollbarEnabled) {
14354
14355                         // draw the buttons
14356                         drawScrollbarButton(0);
14357                         drawScrollbarButton(1);
14358
14359                         scrollbarGroup.translate(scrollerLeft, mathRound(outlineTop + height));
14360
14361                         scrollbarTrack.attr({
14362                                 width: scrollerWidth
14363                         });
14364
14365                         scrollbar.attr({
14366                                 x: mathRound(scrollbarHeight + zoomedMin) + (scrollbarStrokeWidth % 2 / 2),
14367                                 width: range - scrollbarStrokeWidth
14368                         });
14369
14370                         centerBarX = scrollbarHeight + zoomedMin + range / 2 - 0.5;
14371
14372                         scrollbarRifles.attr({ d: [
14373                                         M,
14374                                         centerBarX - 3, scrollbarHeight / 4,
14375                                         L,
14376                                         centerBarX - 3, 2 * scrollbarHeight / 3,
14377                                         M,
14378                                         centerBarX, scrollbarHeight / 4,
14379                                         L,
14380                                         centerBarX, 2 * scrollbarHeight / 3,
14381                                         M,
14382                                         centerBarX + 3, scrollbarHeight / 4,
14383                                         L,
14384                                         centerBarX + 3, 2 * scrollbarHeight / 3
14385                                 ],
14386                                 visibility: range > 12 ? VISIBLE : HIDDEN
14387                         });
14388                 }
14389
14390                 rendered = true;
14391         }
14392
14393         /**
14394          * Event handler for the mouse down event.
14395          */
14396         function mouseDownHandler(e) {
14397                 e = chart.tracker.normalizeMouseEvent(e);
14398                 var chartX = e.chartX,
14399                         chartY = e.chartY,
14400                         handleSensitivity = hasTouch ? 10 : 7,
14401                         left,
14402                         isOnNavigator;
14403
14404                 if (chartY > top && chartY < top + height + scrollbarHeight) { // we're vertically inside the navigator
14405                         isOnNavigator = !scrollbarEnabled || chartY < top + height;
14406
14407                         // grab the left handle
14408                         if (isOnNavigator && math.abs(chartX - zoomedMin - navigatorLeft) < handleSensitivity) {
14409                                 grabbedLeft = true;
14410                                 otherHandlePos = zoomedMax;
14411
14412                         // grab the right handle
14413                         } else if (isOnNavigator && math.abs(chartX - zoomedMax - navigatorLeft) < handleSensitivity) {
14414                                 grabbedRight = true;
14415                                 otherHandlePos = zoomedMin;
14416
14417                         // grab the zoomed range
14418                         } else if (chartX > navigatorLeft + zoomedMin && chartX < navigatorLeft + zoomedMax) {
14419                                 grabbedCenter = chartX;
14420                                 defaultBodyCursor = bodyStyle.cursor;
14421                                 bodyStyle.cursor = 'ew-resize';
14422
14423                                 dragOffset = chartX - zoomedMin;
14424
14425                         // shift the range by clicking on shaded areas, scrollbar track or scrollbar buttons
14426                         } else if (chartX > scrollerLeft && chartX < scrollerLeft + scrollerWidth) {
14427
14428                                 if (isOnNavigator) { // center around the clicked point
14429                                         left = chartX - navigatorLeft - range / 2;
14430                                 } else { // click on scrollbar
14431                                         if (chartX < navigatorLeft) { // click left scrollbar button
14432                                                 left = zoomedMin - mathMin(10, range);
14433                                         } else if (chartX > scrollerLeft + scrollerWidth - scrollbarHeight) {
14434                                                 left = zoomedMin + mathMin(10, range);
14435                                         } else {
14436                                                 // click on scrollbar track, shift the scrollbar by one range
14437                                                 left = chartX < navigatorLeft + zoomedMin ? // on the left
14438                                                         zoomedMin - range :
14439                                                         zoomedMax;
14440                                         }
14441                                 }
14442                                 if (left < 0) {
14443                                         left = 0;
14444                                 } else if (left + range > navigatorWidth) {
14445                                         left = navigatorWidth - range;
14446                                 }
14447                                 if (left !== zoomedMin) { // it has actually moved
14448                                         chart.xAxis[0].setExtremes(
14449                                                 xAxis.translate(left, true),
14450                                                 xAxis.translate(left + range, true),
14451                                                 true,
14452                                                 false
14453                                         );
14454                                 }
14455                         }
14456                 }
14457                 if (e.preventDefault) { // tries to drag object when clicking on the shades
14458                         e.preventDefault();
14459                 }
14460         }
14461
14462         /**
14463          * Event handler for the mouse move event.
14464          */
14465         function mouseMoveHandler(e) {
14466                 e = chart.tracker.normalizeMouseEvent(e);
14467                 var chartX = e.chartX;
14468
14469                 // validation for handle dragging
14470                 if (chartX < navigatorLeft) {
14471                         chartX = navigatorLeft;
14472                 } else if (chartX > scrollerLeft + scrollerWidth - scrollbarHeight) {
14473                         chartX = scrollerLeft + scrollerWidth - scrollbarHeight;
14474                 }
14475
14476                 // drag left handle
14477                 if (grabbedLeft) {
14478                         hasDragged = true;
14479                         render(0, 0, chartX - navigatorLeft, otherHandlePos);
14480
14481                 // drag right handle
14482                 } else if (grabbedRight) {
14483                         hasDragged = true;
14484                         render(0, 0, otherHandlePos, chartX - navigatorLeft);
14485
14486                 // drag scrollbar or open area in navigator
14487                 } else if (grabbedCenter) {
14488                         hasDragged = true;
14489                         if (chartX < dragOffset) { // outside left
14490                                 chartX = dragOffset;
14491                         } else if (chartX > navigatorWidth + dragOffset - range) { // outside right
14492                                 chartX = navigatorWidth + dragOffset - range;
14493                         }
14494
14495                         render(0, 0, chartX - dragOffset, chartX - dragOffset + range);
14496                 }
14497         }
14498
14499         /**
14500          * Event handler for the mouse up event.
14501          */
14502         function mouseUpHandler() {
14503                 if (hasDragged) {
14504                                 chart.xAxis[0].setExtremes(
14505                                         xAxis.translate(zoomedMin, true),
14506                                         xAxis.translate(zoomedMax, true),
14507                                         true,
14508                                         false
14509                                 );
14510                         }
14511                         grabbedLeft = grabbedRight = grabbedCenter = hasDragged = dragOffset = null;
14512                         bodyStyle.cursor = defaultBodyCursor;
14513         }
14514
14515         function updatedDataHandler() {
14516                 var baseXAxis = baseSeries.xAxis,
14517                         baseExtremes = baseXAxis.getExtremes(),
14518                         baseMin = baseExtremes.min,
14519                         baseMax = baseExtremes.max,
14520                         baseDataMin = baseExtremes.dataMin,
14521                         baseDataMax = baseExtremes.dataMax,
14522                         range = baseMax - baseMin,
14523                         stickToMin,
14524                         stickToMax,
14525                         newMax,
14526                         newMin,
14527                         doRedraw,
14528                         navXData = navigatorSeries.xData,
14529                         hasSetExtremes = !!baseXAxis.setExtremes;
14530
14531                 // detect whether to move the range
14532                 stickToMax = baseMax >= navXData[navXData.length - 1];
14533                 stickToMin = baseMin <= baseDataMin;
14534
14535                 // set the navigator series data to the new data of the base series
14536                 if (!navigatorData) {
14537                         navigatorSeries.options.pointStart = baseSeries.xData[0];
14538                         navigatorSeries.setData(baseSeries.options.data, false);
14539                         doRedraw = true;
14540                 }
14541
14542                 // if the zoomed range is already at the min, move it to the right as new data
14543                 // comes in
14544                 if (stickToMin) {
14545                         newMin = baseDataMin;
14546                         newMax = newMin + range;
14547                 }
14548
14549                 // if the zoomed range is already at the max, move it to the right as new data
14550                 // comes in
14551                 if (stickToMax) {
14552                         newMax = baseDataMax;
14553                         if (!stickToMin) { // if stickToMin is true, the new min value is set above
14554                                 newMin = mathMax(newMax - range, navigatorSeries.xData[0]);
14555                         }
14556                 }
14557
14558                 // update the extremes
14559                 if (hasSetExtremes && (stickToMin || stickToMax)) {
14560                         baseXAxis.setExtremes(newMin, newMax, true, false);
14561                 // if it is not at any edge, just move the scroller window to reflect the new series data
14562                 } else {
14563                         if (doRedraw) {
14564                                 chart.redraw(false);
14565                         }
14566
14567                         render(
14568                                 mathMax(baseMin, baseDataMin),
14569                                 mathMin(baseMax, baseDataMax)
14570                         );
14571                 }
14572         }
14573
14574         /**
14575          * Set up the mouse and touch events for the navigator and scrollbar
14576          */
14577         function addEvents() {
14578                 addEvent(chart.container, MOUSEDOWN, mouseDownHandler);
14579                 addEvent(chart.container, MOUSEMOVE, mouseMoveHandler);
14580                 addEvent(document, MOUSEUP, mouseUpHandler);
14581         }
14582
14583         /**
14584          * Removes the event handlers attached previously with addEvents.
14585          */
14586         function removeEvents() {
14587                 removeEvent(chart.container, MOUSEDOWN, mouseDownHandler);
14588                 removeEvent(chart.container, MOUSEMOVE, mouseMoveHandler);
14589                 removeEvent(document, MOUSEUP, mouseUpHandler);
14590                 if (navigatorEnabled) {
14591                         removeEvent(baseSeries, 'updatedData', updatedDataHandler);
14592                 }
14593         }
14594
14595         /**
14596          * Initiate the Scroller object
14597          */
14598         function init() {
14599                 var xAxisIndex = chart.xAxis.length,
14600                         yAxisIndex = chart.yAxis.length,
14601                         baseChartSetSize = chart.setSize;
14602
14603                 // make room below the chart
14604                 chart.extraBottomMargin = outlineHeight + navigatorOptions.margin;
14605                 // get the top offset
14606                 top = getAxisTop(chart.chartHeight);
14607
14608                 if (navigatorEnabled) {
14609                         var baseOptions = baseSeries.options,
14610                                 mergedNavSeriesOptions,
14611                                 baseData = baseOptions.data,
14612                                 navigatorSeriesOptions = navigatorOptions.series;
14613
14614                         // remove it to prevent merging one by one
14615                         navigatorData = navigatorSeriesOptions.data;
14616                         baseOptions.data = navigatorSeriesOptions.data = null;
14617
14618
14619                         // an x axis is required for scrollbar also
14620                         xAxis = new chart.Axis(merge({
14621                                 ordinal: baseSeries.xAxis.options.ordinal // inherit base xAxis' ordinal option
14622                         }, navigatorOptions.xAxis, {
14623                                 isX: true,
14624                                 type: 'datetime',
14625                                 index: xAxisIndex,
14626                                 height: height, // docs + width
14627                                 top: top, // docs + left
14628                                 offset: 0,
14629                                 offsetLeft: scrollbarHeight, // docs
14630                                 offsetRight: -scrollbarHeight, // docs
14631                                 startOnTick: false,
14632                                 endOnTick: false,
14633                                 minPadding: 0,
14634                                 maxPadding: 0,
14635                                 zoomEnabled: false
14636                         }));
14637
14638                         yAxis = new chart.Axis(merge(navigatorOptions.yAxis, {
14639                                 alignTicks: false, // docs
14640                                 height: height,
14641                                 top: top,
14642                                 offset: 0,
14643                                 index: yAxisIndex,
14644                                 zoomEnabled: false
14645                         }));
14646
14647                         // dmerge the series options
14648                         mergedNavSeriesOptions = merge(baseSeries.options, navigatorSeriesOptions, {
14649                                 threshold: null, // docs
14650                                 clip: false, // docs
14651                                 enableMouseTracking: false,
14652                                 group: 'nav', // for columns
14653                                 padXAxis: false,
14654                                 xAxis: xAxisIndex,
14655                                 yAxis: yAxisIndex,
14656                                 name: 'Navigator',
14657                                 showInLegend: false,
14658                                 isInternal: true,
14659                                 visible: true
14660                         });
14661
14662                         // set the data back
14663                         baseOptions.data = baseData;
14664                         navigatorSeriesOptions.data = navigatorData;
14665                         mergedNavSeriesOptions.data = navigatorData || baseData;
14666
14667                         // add the series
14668                         navigatorSeries = chart.initSeries(mergedNavSeriesOptions);
14669
14670                         // respond to updated data in the base series
14671                         // todo: use similiar hook when base series is not yet initialized
14672                         addEvent(baseSeries, 'updatedData', updatedDataHandler);
14673
14674                 // in case of scrollbar only, fake an x axis to get translation
14675                 } else {
14676                         xAxis = {
14677                                 translate: function (value, reverse) {
14678                                         var ext = baseSeries.xAxis.getExtremes(),
14679                                                 scrollTrackWidth = chart.plotWidth - 2 * scrollbarHeight,
14680                                                 dataMin = ext.dataMin,
14681                                                 valueRange = ext.dataMax - dataMin;
14682
14683                                         return reverse ?
14684                                                 // from pixel to value
14685                                                 (value * valueRange / scrollTrackWidth) + dataMin :
14686                                                 // from value to pixel
14687                                                 scrollTrackWidth * (value - dataMin) / valueRange;
14688                                 }
14689                         };
14690                 }
14691                 
14692                 
14693                 // Override the chart.setSize method to adjust the xAxis and yAxis top option as well.
14694                 // This needs to be done prior to chart.resize
14695                 chart.setSize = function (width, height, animation) {
14696                         xAxis.options.top = yAxis.options.top = top = getAxisTop(height);
14697                         baseChartSetSize.call(chart, width, height, animation);
14698                 };
14699
14700                 addEvents();
14701         }
14702
14703         /**
14704          * Destroys allocated elements.
14705          */
14706         function destroy() {
14707                 // Disconnect events added in addEvents
14708                 removeEvents();
14709
14710                 // Destroy local variables
14711                 each([xAxis, yAxis, leftShade, rightShade, outline, scrollbarTrack, scrollbar, scrollbarRifles, scrollbarGroup], function (obj) {
14712                         if (obj && obj.destroy) {
14713                                 obj.destroy();
14714                         }
14715                 });
14716                 xAxis = yAxis = leftShade = rightShade = outline = scrollbarTrack = scrollbar = scrollbarRifles = scrollbarGroup = null;
14717
14718                 // Destroy elements in collection
14719                 each([scrollbarButtons, handles, elementsToDestroy], function (coll) {
14720                         destroyObjectProperties(coll);
14721                 });
14722         }
14723
14724         // Run scroller
14725         init();
14726
14727         // Expose
14728         return {
14729                 render: render,
14730                 destroy: destroy
14731         };
14732
14733 };
14734
14735 /* ****************************************************************************
14736  * End Scroller code                                                                                                              *
14737  *****************************************************************************/
14738
14739 /* ****************************************************************************
14740  * Start Range Selector code                                                                                              *
14741  *****************************************************************************/
14742 extend(defaultOptions, {
14743         rangeSelector: {
14744                 // enabled: true,
14745                 // buttons: {Object}
14746                 // buttonSpacing: 0,
14747                 buttonTheme: {
14748                         width: 28,
14749                         height: 16,
14750                         padding: 1,
14751                         r: 0,
14752                         zIndex: 10 // #484
14753                 //      states: {
14754                 //              hover: {},
14755                 //              select: {}
14756                 // }
14757                 }
14758                 // inputDateFormat: '%b %e, %Y',
14759                 // inputEditDateFormat: '%Y-%m-%d',
14760                 // inputEnabled: true,
14761                 // inputStyle: {}
14762                 // labelStyle: {}
14763                 // selected: undefined
14764                 // todo:
14765                 // - button styles for normal, hover and select state
14766                 // - CSS text styles
14767                 // - styles for the inputs and labels
14768         }
14769 });
14770 defaultOptions.lang = merge(defaultOptions.lang, {
14771         rangeSelectorZoom: 'Zoom',
14772         rangeSelectorFrom: 'From:',
14773         rangeSelectorTo: 'To:'
14774 });
14775
14776 /**
14777  * The object constructor for the range selector
14778  * @param {Object} chart
14779  */
14780 Highcharts.RangeSelector = function (chart) {
14781         var renderer = chart.renderer,
14782                 rendered,
14783                 container = chart.container,
14784                 lang = defaultOptions.lang,
14785                 div,
14786                 leftBox,
14787                 rightBox,
14788                 boxSpanElements = {},
14789                 divAbsolute,
14790                 divRelative,
14791                 selected,
14792                 zoomText,
14793                 buttons = [],
14794                 buttonOptions,
14795                 options,
14796                 defaultButtons = [{
14797                         type: 'month',
14798                         count: 1,
14799                         text: '1m'
14800                 }, {
14801                         type: 'month',
14802                         count: 3,
14803                         text: '3m'
14804                 }, {
14805                         type: 'month',
14806                         count: 6,
14807                         text: '6m'
14808                 }, {
14809                         type: 'ytd',
14810                         text: 'YTD'
14811                 }, {
14812                         type: 'year',
14813                         count: 1,
14814                         text: '1y'
14815                 }, {
14816                         type: 'all',
14817                         text: 'All'
14818                 }];
14819                 chart.resetZoomEnabled = false;
14820
14821         /**
14822          * The method to run when one of the buttons in the range selectors is clicked
14823          * @param {Number} i The index of the button
14824          * @param {Object} rangeOptions
14825          * @param {Boolean} redraw
14826          */
14827         function clickButton(i, rangeOptions, redraw) {
14828
14829                 var baseAxis = chart.xAxis[0],
14830                         extremes = baseAxis && baseAxis.getExtremes(),
14831                         now,
14832                         dataMin = extremes && extremes.dataMin,
14833                         dataMax = extremes && extremes.dataMax,
14834                         newMin,
14835                         newMax = baseAxis && mathMin(extremes.max, dataMax),
14836                         date = new Date(newMax),
14837                         type = rangeOptions.type,
14838                         count = rangeOptions.count,
14839                         baseXAxisOptions,
14840                         range,
14841                         rangeMin,
14842                         year,
14843                         // these time intervals have a fixed number of milliseconds, as opposed
14844                         // to month, ytd and year
14845                         fixedTimes = {
14846                                 millisecond: 1,
14847                                 second: 1000,
14848                                 minute: 60 * 1000,
14849                                 hour: 3600 * 1000,
14850                                 day: 24 * 3600 * 1000,
14851                                 week: 7 * 24 * 3600 * 1000
14852                         };
14853
14854                 if (dataMin === null || dataMax === null || // chart has no data, base series is removed
14855                                 i === selected) { // same button is clicked twice
14856                         return;
14857                 }
14858
14859                 if (fixedTimes[type]) {
14860                         range = fixedTimes[type] * count;
14861                         newMin = mathMax(newMax - range, dataMin);
14862                 } else if (type === 'month') {
14863                         date.setMonth(date.getMonth() - count);
14864                         newMin = mathMax(date.getTime(), dataMin);
14865                         range = 30 * 24 * 3600 * 1000 * count;
14866                 } else if (type === 'ytd') {
14867                         date = new Date(0);
14868                         now = new Date();
14869                         year = now.getFullYear();
14870                         date.setFullYear(year);
14871
14872                         // workaround for IE6 bug, which sets year to next year instead of current
14873                         if (String(year) !== dateFormat('%Y', date)) {
14874                                 date.setFullYear(year - 1);
14875                         }
14876
14877                         newMin = rangeMin = mathMax(dataMin || 0, date.getTime());
14878                         now = now.getTime();
14879                         newMax = mathMin(dataMax || now, now);
14880                 } else if (type === 'year') {
14881                         date.setFullYear(date.getFullYear() - count);
14882                         newMin = mathMax(dataMin, date.getTime());
14883                         range = 365 * 24 * 3600 * 1000 * count;
14884                 } else if (type === 'all' && baseAxis) {
14885                         newMin = dataMin;
14886                         newMax = dataMax;
14887                 }
14888
14889                 // mark the button pressed
14890                 if (buttons[i]) {
14891                         buttons[i].setState(2);
14892                 }
14893
14894                 // update the chart
14895                 if (!baseAxis) { // axis not yet instanciated
14896                         baseXAxisOptions = chart.options.xAxis;
14897                         baseXAxisOptions[0] = merge(
14898                                 baseXAxisOptions[0],
14899                                 {
14900                                         range: range,
14901                                         min: rangeMin
14902                                 }
14903                         );
14904                         selected = i;
14905
14906                 } else { // existing axis object; after render time
14907                         setTimeout(function () { // make sure the visual state is set before the heavy process begins
14908                                 baseAxis.setExtremes(
14909                                         newMin,
14910                                         newMax,
14911                                         pick(redraw, 1),
14912                                         0
14913                                 );
14914                                 selected = i;
14915                         }, 1);
14916                 }
14917
14918         }
14919
14920         /**
14921          * The handler connected to container that handles mousedown.
14922          */
14923         function mouseDownHandler() {
14924                 if (leftBox) {
14925                         leftBox.blur();
14926                 }
14927                 if (rightBox) {
14928                         rightBox.blur();
14929                 }
14930         }
14931
14932         /**
14933          * Initialize the range selector
14934          */
14935         function init() {
14936                 chart.extraTopMargin = 25;
14937                 options = chart.options.rangeSelector;
14938                 buttonOptions = options.buttons || defaultButtons;
14939
14940
14941                 var selectedOption = options.selected;
14942
14943                 addEvent(container, MOUSEDOWN, mouseDownHandler);
14944
14945                 // zoomed range based on a pre-selected button index
14946                 if (selectedOption !== UNDEFINED && buttonOptions[selectedOption]) {
14947                         clickButton(selectedOption, buttonOptions[selectedOption], false);
14948                 }
14949
14950                 // normalize the pressed button whenever a new range is selected
14951                 addEvent(chart, 'load', function () {
14952                         addEvent(chart.xAxis[0], 'afterSetExtremes', function () {
14953                                 if (buttons[selected]) {
14954                                         buttons[selected].setState(0);
14955                                 }
14956                                 selected = null;
14957                         });
14958                 });
14959         }
14960
14961
14962         /**
14963          * Set the internal and displayed value of a HTML input for the dates
14964          * @param {Object} input
14965          * @param {Number} time
14966          */
14967         function setInputValue(input, time) {
14968                 var format = input.hasFocus ? options.inputEditDateFormat || '%Y-%m-%d' : options.inputDateFormat || '%b %e, %Y';
14969                 if (time) {
14970                         input.HCTime = time;
14971                 }
14972                 input.value = dateFormat(format, input.HCTime);
14973         }
14974
14975         /**
14976          * Draw either the 'from' or the 'to' HTML input box of the range selector
14977          * @param {Object} name
14978          */
14979         function drawInput(name) {
14980                 var isMin = name === 'min',
14981                         input;
14982
14983                 // create the text label
14984                 boxSpanElements[name] = createElement('span', {
14985                         innerHTML: lang[isMin ? 'rangeSelectorFrom' : 'rangeSelectorTo']
14986                 }, options.labelStyle, div);
14987
14988                 // create the input element
14989                 input = createElement('input', {
14990                         name: name,
14991                         className: PREFIX + 'range-selector',
14992                         type: 'text'
14993                 }, extend({
14994                         width: '80px',
14995                         height: '16px',
14996                         border: '1px solid silver',
14997                         marginLeft: '5px',
14998                         marginRight: isMin ? '5px' : '0',
14999                         textAlign: 'center'
15000                 }, options.inputStyle), div);
15001
15002
15003                 input.onfocus = input.onblur = function (e) {
15004                         e = e || window.event;
15005                         input.hasFocus = e.type === 'focus';
15006                         setInputValue(input);
15007                 };
15008
15009                 // handle changes in the input boxes
15010                 input.onchange = function () {
15011                         var inputValue = input.value,
15012                                 value = Date.parse(inputValue),
15013                                 extremes = chart.xAxis[0].getExtremes();
15014
15015                         // if the value isn't parsed directly to a value by the browser's Date.parse method,
15016                         // like YYYY-MM-DD in IE, try parsing it a different way
15017                         if (isNaN(value)) {
15018                                 value = inputValue.split('-');
15019                                 value = Date.UTC(pInt(value[0]), pInt(value[1]) - 1, pInt(value[2]));
15020                         }
15021
15022                         if (!isNaN(value) &&
15023                                 ((isMin && (value >= extremes.dataMin && value <= rightBox.HCTime)) ||
15024                                 (!isMin && (value <= extremes.dataMax && value >= leftBox.HCTime)))
15025                         ) {
15026                                 chart.xAxis[0].setExtremes(
15027                                         isMin ? value : extremes.min,
15028                                         isMin ? extremes.max : value
15029                                 );
15030                         }
15031                 };
15032
15033                 return input;
15034         }
15035
15036         /**
15037          * Render the range selector including the buttons and the inputs. The first time render
15038          * is called, the elements are created and positioned. On subsequent calls, they are
15039          * moved and updated.
15040          * @param {Number} min X axis minimum
15041          * @param {Number} max X axis maximum
15042          */
15043         function render(min, max) {
15044                 var chartStyle = chart.options.chart.style,
15045                         buttonTheme = options.buttonTheme,
15046                         inputEnabled = options.inputEnabled !== false,
15047                         states = buttonTheme && buttonTheme.states,
15048                         plotLeft = chart.plotLeft,
15049                         buttonLeft;
15050
15051                 // create the elements
15052                 if (!rendered) {
15053                         zoomText = renderer.text(lang.rangeSelectorZoom, plotLeft, chart.plotTop - 10)
15054                                 .css(options.labelStyle)
15055                                 .add();
15056
15057                         // button starting position
15058                         buttonLeft = plotLeft + zoomText.getBBox().width + 5;
15059
15060                         each(buttonOptions, function (rangeOptions, i) {
15061                                 buttons[i] = renderer.button(
15062                                         rangeOptions.text,
15063                                         buttonLeft,
15064                                         chart.plotTop - 25,
15065                                         function () {
15066                                                 clickButton(i, rangeOptions);
15067                                                 this.isActive = true;
15068                                         },
15069                                         buttonTheme,
15070                                         states && states.hover,
15071                                         states && states.select
15072                                 )
15073                                 .css({
15074                                         textAlign: 'center'
15075                                 })
15076                                 .add();
15077
15078                                 // increase button position for the next button
15079                                 buttonLeft += buttons[i].width + (options.buttonSpacing || 0);
15080
15081                                 if (selected === i) {
15082                                         buttons[i].setState(2);
15083                                 }
15084
15085                         });
15086
15087                         // first create a wrapper outside the container in order to make
15088                         // the inputs work and make export correct
15089                         if (inputEnabled) {
15090                                 divRelative = div = createElement('div', null, {
15091                                         position: 'relative',
15092                                         height: 0,
15093                                         fontFamily: chartStyle.fontFamily,
15094                                         fontSize: chartStyle.fontSize,
15095                                         zIndex: 1 // above container
15096                                 });
15097
15098                                 container.parentNode.insertBefore(div, container);
15099
15100                                 // create an absolutely positionied div to keep the inputs
15101                                 divAbsolute = div = createElement('div', null, extend({
15102                                         position: 'absolute',
15103                                         top: (chart.plotTop - 25) + 'px',
15104                                         right: (chart.chartWidth - chart.plotLeft - chart.plotWidth) + 'px'
15105                                 }, options.inputBoxStyle), div);
15106
15107                                 leftBox = drawInput('min');
15108
15109                                 rightBox = drawInput('max');
15110                         }
15111                 }
15112
15113                 if (inputEnabled) {
15114                         setInputValue(leftBox, min);
15115                         setInputValue(rightBox, max);
15116                 }
15117
15118
15119                 rendered = true;
15120         }
15121
15122         /**
15123          * Destroys allocated elements.
15124          */
15125         function destroy() {
15126                 removeEvent(container, MOUSEDOWN, mouseDownHandler);
15127
15128                 // Destroy elements in collections
15129                 each([buttons], function (coll) {
15130                         destroyObjectProperties(coll);
15131                 });
15132
15133                 // Destroy zoomText
15134                 if (zoomText) {
15135                         zoomText = zoomText.destroy();
15136                 }
15137
15138                 // Clear input element events
15139                 if (leftBox) {
15140                         leftBox.onfocus = leftBox.onblur = leftBox.onchange = null;
15141                 }
15142                 if (rightBox) {
15143                         rightBox.onfocus = rightBox.onblur = rightBox.onchange = null;
15144                 }
15145
15146                 // Discard divs and spans
15147                 each([leftBox, rightBox, boxSpanElements.min, boxSpanElements.max, divAbsolute, divRelative], function (item) {
15148                         discardElement(item);
15149                 });
15150                 // Null the references
15151                 leftBox = rightBox = boxSpanElements = div = divAbsolute = divRelative = null;
15152
15153         }
15154
15155         // Run RangeSelector
15156         init();
15157
15158         // Expose
15159         return {
15160                 render: render,
15161                 destroy: destroy
15162         };
15163 };
15164
15165 /* ****************************************************************************
15166  * End Range Selector code                                                                                                      *
15167  *****************************************************************************/
15168
15169
15170
15171 Chart.prototype.callbacks.push(function (chart) {
15172         var extremes,
15173                 scroller = chart.scroller,
15174                 rangeSelector = chart.rangeSelector;
15175
15176         function renderScroller() {
15177                 extremes = chart.xAxis[0].getExtremes();
15178                 scroller.render(
15179                         mathMax(extremes.min, extremes.dataMin),
15180                         mathMin(extremes.max, extremes.dataMax)
15181                 );
15182         }
15183
15184         function renderRangeSelector() {
15185                 extremes = chart.xAxis[0].getExtremes();
15186                 rangeSelector.render(extremes.min, extremes.max);
15187         }
15188
15189         function afterSetExtremesHandlerScroller(e) {
15190                 scroller.render(e.min, e.max);
15191         }
15192
15193         function afterSetExtremesHandlerRangeSelector(e) {
15194                 rangeSelector.render(e.min, e.max);
15195         }
15196
15197         function destroyEvents() {
15198                 if (scroller) {
15199                         removeEvent(chart, 'resize', renderScroller);
15200                         removeEvent(chart.xAxis[0], 'afterSetExtremes', afterSetExtremesHandlerScroller);
15201                 }
15202                 if (rangeSelector) {
15203                         removeEvent(chart, 'resize', renderRangeSelector);
15204                         removeEvent(chart.xAxis[0], 'afterSetExtremes', afterSetExtremesHandlerRangeSelector);
15205                 }
15206         }
15207
15208         // initiate the scroller
15209         if (scroller) {
15210                 // redraw the scroller on setExtremes
15211                 addEvent(chart.xAxis[0], 'afterSetExtremes', afterSetExtremesHandlerScroller);
15212
15213                 // redraw the scroller chart resize
15214                 addEvent(chart, 'resize', renderScroller);
15215
15216                 // do it now
15217                 renderScroller();
15218         }
15219         if (rangeSelector) {
15220                 // redraw the scroller on setExtremes
15221                 addEvent(chart.xAxis[0], 'afterSetExtremes', afterSetExtremesHandlerRangeSelector);
15222
15223                 // redraw the scroller chart resize
15224                 addEvent(chart, 'resize', renderRangeSelector);
15225
15226                 // do it now
15227                 renderRangeSelector();
15228         }
15229
15230         // Remove resize/afterSetExtremes at chart destroy
15231         addEvent(chart, 'destroy', destroyEvents);
15232 });
15233 /**
15234  * A wrapper for Chart with all the default values for a Stock chart
15235  */
15236 Highcharts.StockChart = function (options, callback) {
15237         var seriesOptions = options.series, // to increase performance, don't merge the data 
15238                 opposite,
15239                 lineOptions = {
15240
15241                         marker: {
15242                                 enabled: false,
15243                                 states: {
15244                                         hover: {
15245                                                 enabled: true,
15246                                                 radius: 5
15247                                         }
15248                                 }
15249                         },
15250                         // gapSize: 0, // docs
15251                         shadow: false,
15252                         states: {
15253                                 hover: {
15254                                         lineWidth: 2
15255                                 }
15256                         },
15257                         dataGrouping: {
15258                                 enabled: true
15259                         }
15260                 };
15261
15262         // apply X axis options to both single and multi y axes
15263         options.xAxis = map(splat(options.xAxis || {}), function (xAxisOptions) {
15264                 return merge({ // defaults
15265                                 minPadding: 0,
15266                                 maxPadding: 0,
15267                                 ordinal: true,
15268                                 title: {
15269                                         text: null
15270                                 },
15271                                 showLastLabel: true
15272                         }, xAxisOptions, // user options 
15273                         { // forced options
15274                                 type: 'datetime',
15275                                 categories: null
15276                         });
15277         });
15278
15279         // apply Y axis options to both single and multi y axes
15280         options.yAxis = map(splat(options.yAxis || {}), function (yAxisOptions) {
15281                 opposite = yAxisOptions.opposite;
15282                 return merge({ // defaults
15283                         labels: {
15284                                 align: opposite ? 'right' : 'left',
15285                                 x: opposite ? -2 : 2,
15286                                 y: -2
15287                         },
15288                         showLastLabel: false,
15289                         title: {
15290                                 text: null
15291                         }
15292                 }, yAxisOptions // user options
15293                 );
15294         });
15295
15296         options.series = null;
15297
15298         options = merge({
15299                 chart: {
15300                         panning: true
15301                 },
15302                 navigator: {
15303                         enabled: true
15304                 },
15305                 scrollbar: {
15306                         enabled: true
15307                 },
15308                 rangeSelector: {
15309                         enabled: true
15310                 },
15311                 title: {
15312                         text: null
15313                 },
15314                 tooltip: {
15315                         shared: true,
15316                         crosshairs: true
15317                 },
15318                 legend: {
15319                         enabled: false
15320                 },
15321
15322                 plotOptions: {
15323                         line: lineOptions,
15324                         spline: lineOptions,
15325                         area: lineOptions,
15326                         areaspline: lineOptions,
15327                         column: {
15328                                 shadow: false,
15329                                 borderWidth: 0,
15330                                 dataGrouping: {
15331                                         enabled: true
15332                                 }
15333                         }
15334                 }
15335
15336         },
15337         options, // user's options
15338
15339         { // forced options
15340                 chart: {
15341                         inverted: false
15342                 }
15343         });
15344
15345         options.series = seriesOptions;
15346
15347
15348         return new Chart(options, callback);
15349 };
15350
15351
15352 /* ****************************************************************************
15353  * Start value compare logic                                                  *
15354  *****************************************************************************/
15355  
15356 var seriesInit = seriesProto.init, 
15357         seriesProcessData = seriesProto.processData,
15358         pointTooltipFormatter = Point.prototype.tooltipFormatter;
15359         
15360 /**
15361  * Extend series.init by adding a method to modify the y value used for plotting
15362  * on the y axis. This method is called both from the axis when finding dataMin
15363  * and dataMax, and from the series.translate method.
15364  */
15365 seriesProto.init = function () {
15366         
15367         // call base method
15368         seriesInit.apply(this, arguments);
15369         
15370         // local variables
15371         var series = this,
15372                 compare = series.options.compare;
15373         
15374         if (compare) {
15375                 series.modifyValue = function (value, point) {
15376                         var compareValue = this.compareValue;
15377                         
15378                         // get the modified value
15379                         value = compare === 'value' ? 
15380                                 value - compareValue : // compare value
15381                                 value = 100 * (value / compareValue) - 100; // compare percent
15382                                 
15383                         // record for tooltip etc.
15384                         if (point) {
15385                                 point.change = value;
15386                         }
15387                         
15388                         return value;
15389                 };
15390         }       
15391 };
15392
15393 /**
15394  * Extend series.processData by finding the first y value in the plot area,
15395  * used for comparing the following values 
15396  */
15397 seriesProto.processData = function () {
15398         var series = this;
15399         
15400         // call base method
15401         seriesProcessData.apply(this, arguments);
15402         
15403         if (series.options.compare) {
15404                 
15405                 // local variables
15406                 var i = 0,
15407                         processedXData = series.processedXData,
15408                         processedYData = series.processedYData,
15409                         length = processedYData.length,
15410                         min = series.xAxis.getExtremes().min;
15411                 
15412                 // find the first value for comparison
15413                 for (; i < length; i++) {
15414                         if (typeof processedYData[i] === NUMBER && processedXData[i] >= min) {
15415                                 series.compareValue = processedYData[i];
15416                                 break;
15417                         }
15418                 }
15419         }
15420 };
15421
15422 /**
15423  * Extend the tooltip formatter by adding support for the point.change variable
15424  * as well as the changeDecimals option
15425  */
15426 Point.prototype.tooltipFormatter = function (pointFormat) {
15427         var point = this;
15428         
15429         pointFormat = pointFormat.replace(
15430                 '{point.change}',
15431                 (point.change > 0 ? '+' : '') + numberFormat(point.change, point.series.tooltipOptions.changeDecimals || 2)
15432         ); 
15433         
15434         return pointTooltipFormatter.apply(this, [pointFormat]);
15435 };
15436
15437 /* ****************************************************************************
15438  * End value compare logic                                                    *
15439  *****************************************************************************/
15440
15441 /* ****************************************************************************
15442  * Start ordinal axis logic                                                   *
15443  *****************************************************************************/
15444
15445 (function () {
15446         var baseInit = seriesProto.init,
15447                 baseGetSegments = seriesProto.getSegments;
15448                 
15449         seriesProto.init = function () {
15450                 var series = this,
15451                         chart,
15452                         xAxis;
15453                 
15454                 // call base method
15455                 baseInit.apply(series, arguments);
15456                 
15457                 // chart and xAxis are set in base init
15458                 chart = series.chart;
15459                 xAxis = series.xAxis;
15460                 
15461                 // Destroy the extended ordinal index on updated data
15462                 if (xAxis && xAxis.options.ordinal) {
15463                         addEvent(series, 'updatedData', function () {
15464                                 delete xAxis.ordinalIndex;
15465                         });
15466                 }
15467                 
15468                 /**
15469                  * Extend the ordinal axis object. If we rewrite the axis object to a prototype model,
15470                  * we should add these properties to the prototype instead.
15471                  */
15472                 if (xAxis && xAxis.options.ordinal && !xAxis.hasOrdinalExtension) {
15473                                 
15474                         xAxis.hasOrdinalExtension = true;
15475                 
15476                         /**
15477                          * Calculate the ordinal positions before tick positions are calculated. 
15478                          * TODO: When we rewrite Axis to use a prototype model, this should be implemented
15479                          * as a method extension to avoid overhead in the core.
15480                          */
15481                         xAxis.beforeSetTickPositions = function () {
15482                                 var axis = this,
15483                                         len,
15484                                         ordinalPositions = [],
15485                                         useOrdinal = false,
15486                                         dist,
15487                                         extremes = axis.getExtremes(),
15488                                         min = extremes.min,
15489                                         max = extremes.max,
15490                                         minIndex,
15491                                         maxIndex,
15492                                         slope,
15493                                         i;
15494                                 
15495                                 // apply the ordinal logic
15496                                 if (axis.options.ordinal) {
15497                                         
15498                                         each(axis.series, function (series, i) {
15499                                                 
15500                                                 if (series.visible !== false) {
15501                                                         
15502                                                         // concatenate the processed X data into the existing positions, or the empty array 
15503                                                         ordinalPositions = ordinalPositions.concat(series.processedXData);
15504                                                         len = ordinalPositions.length;
15505                                                         
15506                                                         // if we're dealing with more than one series, remove duplicates
15507                                                         if (i && len) {
15508                                                         
15509                                                                 ordinalPositions.sort(function (a, b) {
15510                                                                         return a - b; // without a custom function it is sorted as strings
15511                                                                 });
15512                                                         
15513                                                                 i = len - 1;
15514                                                                 while (i--) {
15515                                                                         if (ordinalPositions[i] === ordinalPositions[i + 1]) {
15516                                                                                 ordinalPositions.splice(i, 1);
15517                                                                         }
15518                                                                 }
15519                                                         }
15520                                                 }
15521                                                 
15522                                         });
15523                                         
15524                                         // cache the length
15525                                         len = ordinalPositions.length;                                  
15526                                         
15527                                         // Check if we really need the overhead of mapping axis data against the ordinal positions.
15528                                         // If the series consist of evenly spaced data any way, we don't need any ordinal logic.
15529                                         if (len > 2) { // two points have equal distance by default
15530                                                 dist = ordinalPositions[1] - ordinalPositions[0]; 
15531                                                 i = len - 1;
15532                                                 while (i-- && !useOrdinal) {
15533                                                         if (ordinalPositions[i + 1] - ordinalPositions[i] !== dist) {
15534                                                                 useOrdinal = true;
15535                                                         }
15536                                                 }
15537                                         }
15538                                         
15539                                         // Record the slope and offset to compute the linear values from the array index.
15540                                         // Since the ordinal positions may exceed the current range, get the start and 
15541                                         // end positions within it (#719, #665b)
15542                                         if (useOrdinal) {
15543                                                 
15544                                                 // Register
15545                                                 axis.ordinalPositions = ordinalPositions;
15546                                                 
15547                                                 // This relies on the ordinalPositions being set
15548                                                 minIndex = xAxis.val2lin(min, true);
15549                                                 maxIndex = xAxis.val2lin(max, true);
15550                                 
15551                                                 // Set the slope and offset of the values compared to the indices in the ordinal positions
15552                                                 axis.ordinalSlope = slope = (max - min) / (maxIndex - minIndex);
15553                                                 axis.ordinalOffset = min - (minIndex * slope);
15554                                                 
15555                                         } else {
15556                                                 axis.ordinalPositions = axis.ordinalSlope = axis.ordinalOffset = UNDEFINED;
15557                                         }
15558                                 }
15559                         };
15560                         
15561                         /**
15562                          * Translate from a linear axis value to the corresponding ordinal axis position. If there
15563                          * are no gaps in the ordinal axis this will be the same. The translated value is the value
15564                          * that the point would have if the axis were linear, using the same min and max.
15565                          * 
15566                          * @param Number val The axis value
15567                          * @param Boolean toIndex Whether to return the index in the ordinalPositions or the new value
15568                          */
15569                         xAxis.val2lin = function (val, toIndex) {
15570                                 
15571                                 var axis = this,
15572                                         ordinalPositions = axis.ordinalPositions;
15573                                 
15574                                 if (!ordinalPositions) {
15575                                         return val;
15576                                 
15577                                 } else {
15578                                 
15579                                         var ordinalLength = ordinalPositions.length,
15580                                                 i,
15581                                                 distance,
15582                                                 ordinalIndex;
15583                                                 
15584                                         // first look for an exact match in the ordinalpositions array
15585                                         i = ordinalLength;
15586                                         while (i--) {
15587                                                 if (ordinalPositions[i] === val) {
15588                                                         ordinalIndex = i;
15589                                                         break;
15590                                                 }
15591                                         }
15592                                         
15593                                         // if that failed, find the intermediate position between the two nearest values
15594                                         i = ordinalLength - 1;
15595                                         while (i--) {
15596                                                 if (val > ordinalPositions[i] || i === 0) { // interpolate
15597                                                         distance = (val - ordinalPositions[i]) / (ordinalPositions[i + 1] - ordinalPositions[i]); // something between 0 and 1
15598                                                         ordinalIndex = i + distance;
15599                                                         break;
15600                                                 }
15601                                         }
15602                                         return toIndex ?
15603                                                 ordinalIndex :
15604                                                 axis.ordinalSlope * (ordinalIndex || 0) + axis.ordinalOffset;
15605                                 }
15606                         };
15607                         
15608                         /**
15609                          * Translate from linear (internal) to axis value
15610                          * 
15611                          * @param Number val The linear abstracted value
15612                          * @param Boolean fromIndex Translate from an index in the ordinal positions rather than a value
15613                          */
15614                         xAxis.lin2val = function (val, fromIndex) {
15615                                 var axis = this,
15616                                         ordinalPositions = axis.ordinalPositions;
15617                                 
15618                                 if (!ordinalPositions) { // the visible range contains only equally spaced values
15619                                         return val;
15620                                 
15621                                 } else {
15622                                 
15623                                         var ordinalSlope = axis.ordinalSlope,
15624                                                 ordinalOffset = axis.ordinalOffset,
15625                                                 i = ordinalPositions.length - 1,
15626                                                 linearEquivalentLeft,
15627                                                 linearEquivalentRight,
15628                                                 distance;
15629                                                 
15630                                         
15631                                         // Handle the case where we translate from the index directly, used only 
15632                                         // when panning an ordinal axis
15633                                         if (fromIndex) {
15634                                                 
15635                                                 if (val < 0) { // out of range, in effect panning to the left
15636                                                         val = ordinalPositions[0];
15637                                                 } else if (val > i) { // out of range, panning to the right
15638                                                         val = ordinalPositions[i];
15639                                                 } else { // split it up
15640                                                         i = mathFloor(val);
15641                                                         distance = val - i; // the decimal
15642                                                 }
15643                                                 
15644                                         // Loop down along the ordinal positions. When the linear equivalent of i matches
15645                                         // an ordinal position, interpolate between the left and right values.
15646                                         } else {
15647                                                 while (i--) {
15648                                                         linearEquivalentLeft = (ordinalSlope * i) + ordinalOffset;
15649                                                         if (val >= linearEquivalentLeft) {
15650                                                                 linearEquivalentRight = (ordinalSlope * (i + 1)) + ordinalOffset;
15651                                                                 distance = (val - linearEquivalentLeft) / (linearEquivalentRight - linearEquivalentLeft); // something between 0 and 1
15652                                                                 break;
15653                                                         }
15654                                                 }
15655                                         }
15656                                         
15657                                         // If the index is within the range of the ordinal positions, return the associated
15658                                         // or interpolated value. If not, just return the value
15659                                         return distance !== UNDEFINED && ordinalPositions[i] !== UNDEFINED ?
15660                                                 ordinalPositions[i] + (distance ? distance * (ordinalPositions[i + 1] - ordinalPositions[i]) : 0) : 
15661                                                 val;
15662                                 }
15663                         };
15664                         
15665                         /**
15666                          * Get the ordinal positions for the entire data set. This is necessary in chart panning
15667                          * because we need to find out what points or data groups are available outside the 
15668                          * visible range. When a panning operation starts, if an index for the given grouping
15669                          * does not exists, it is created and cached. This index is deleted on updated data, so
15670                          * it will be regenerated the next time a panning operation starts.
15671                          */
15672                         xAxis.getExtendedPositions = function () {
15673                                 var grouping = xAxis.series[0].currentDataGrouping,
15674                                         ordinalIndex = xAxis.ordinalIndex,
15675                                         key = grouping ? grouping.count + grouping.unitName : 'raw',
15676                                         extremes = xAxis.getExtremes(),
15677                                         fakeAxis,
15678                                         fakeSeries;
15679                                         
15680                                 // If this is the first time, or the ordinal index is deleted by updatedData,
15681                                 // create it.
15682                                 if (!ordinalIndex) {
15683                                         ordinalIndex = xAxis.ordinalIndex = {};
15684                                 }
15685                                 
15686                                 
15687                                 if (!ordinalIndex[key]) {
15688                                         
15689                                         // Create a fake axis object where the extended ordinal positions are emulated
15690                                         fakeAxis = {
15691                                                 series: [],
15692                                                 getExtremes: function () {
15693                                                         return {
15694                                                                 min: extremes.dataMin,
15695                                                                 max: extremes.dataMax
15696                                                         };
15697                                                 },
15698                                                 options: {
15699                                                         ordinal: true
15700                                                 }
15701                                         };
15702                                         
15703                                         // Add the fake series to hold the full data, then apply processData to it
15704                                         each(xAxis.series, function (series) {
15705                                                 fakeSeries = {
15706                                                         xAxis: fakeAxis,
15707                                                         xData: series.xData,
15708                                                         chart: chart
15709                                                 };
15710                                                 fakeSeries.options = {
15711                                                         dataGrouping : grouping ? {
15712                                                                 enabled: true,
15713                                                                 forced: true,
15714                                                                 approximation: 'open', // doesn't matter which, use the fastest
15715                                                                 units: [[grouping.unitName, [grouping.count]]]
15716                                                         } : {
15717                                                                 enabled: false
15718                                                         }
15719                                                 };
15720                                                 series.processData.apply(fakeSeries);
15721                                                 
15722                                                 fakeAxis.series.push(fakeSeries);
15723                                         });
15724                                         
15725                                         // Run beforeSetTickPositions to compute the ordinalPositions
15726                                         xAxis.beforeSetTickPositions.apply(fakeAxis);
15727                                         
15728                                         // Cache it
15729                                         ordinalIndex[key] = fakeAxis.ordinalPositions;
15730                                 }
15731                                 return ordinalIndex[key];
15732                         };
15733                         
15734                         /**
15735                          * Find the factor to estimate how wide the plot area would have been if ordinal
15736                          * gaps were included. This value is used to compute an imagined plot width in order
15737                          * to establish the data grouping interval. 
15738                          * 
15739                          * A real world case is the intraday-candlestick
15740                          * example. Without this logic, it would show the correct data grouping when viewing
15741                          * a range within each day, but once moving the range to include the gap between two
15742                          * days, the interval would include the cut-away night hours and the data grouping
15743                          * would be wrong. So the below method tries to compensate by identifying the most
15744                          * common point interval, in this case days. 
15745                          * 
15746                          * An opposite case is presented in issue #718. We have a long array of daily data,
15747                          * then one point is appended one hour after the last point. We expect the data grouping
15748                          * not to change.
15749                          * 
15750                          * In the future, if we find cases where this estimation doesn't work optimally, we
15751                          * might need to add a second pass to the data grouping logic, where we do another run
15752                          * with a greater interval if the number of data groups is more than a certain fraction
15753                          * of the desired group count.
15754                          */
15755                         xAxis.getGroupIntervalFactor = function (xMin, xMax, processedXData) {
15756                                 var i = 0,
15757                                         len = processedXData.length, 
15758                                         distances = [],
15759                                         median;
15760                                         
15761                                 // Register all the distances in an array
15762                                 for (; i < len - 1; i++) {
15763                                         distances[i] = processedXData[i + 1] - processedXData[i];
15764                                 }
15765                                 
15766                                 // Sort them and find the median
15767                                 distances.sort(function (a, b) {
15768                                         return a - b;
15769                                 });
15770                                 median = distances[mathFloor(len / 2)];
15771                                 
15772                                 // Return the factor needed for data grouping
15773                                 return (len * median) / (xMax - xMin);
15774                         };
15775                         
15776                         /**
15777                          * Make the tick intervals closer because the ordinal gaps make the ticks spread out or cluster
15778                          */
15779                         xAxis.postProcessTickInterval = function (tickInterval) {
15780                                 var ordinalSlope = this.ordinalSlope;
15781                                 
15782                                 return ordinalSlope ? 
15783                                         tickInterval / (ordinalSlope / xAxis.closestPointRange) : 
15784                                         tickInterval;
15785                         };
15786                         
15787                         /**
15788                          * In an ordinal axis, there might be areas with dense consentrations of points, then large
15789                          * gaps between some. Creating equally distributed ticks over this entire range
15790                          * may lead to a huge number of ticks that will later be removed. So instead, break the 
15791                          * positions up in segments, find the tick positions for each segment then concatenize them.
15792                          * This method is used from both data grouping logic and X axis tick position logic. 
15793                          */
15794                         xAxis.getNonLinearTimeTicks = function (normalizedInterval, min, max, startOfWeek, positions, closestDistance, findHigherRanks) {
15795                                 
15796                                 var start = 0,
15797                                         end = 0,
15798                                         segmentPositions,
15799                                         higherRanks = {},
15800                                         hasCrossedHigherRank,
15801                                         info,
15802                                         posLength,
15803                                         outsideMax,
15804                                         groupPositions = [];
15805                                         
15806                                 // The positions are not always defined, for example for ordinal positions when data
15807                                 // has regular interval
15808                                 if (!positions || min === UNDEFINED) {
15809                                         return getTimeTicks(normalizedInterval, min, max, startOfWeek);
15810                                 }
15811                                 
15812                                 // Analyze the positions array to split it into segments on gaps larger than 5 times
15813                                 // the closest distance. The closest distance is already found at this point, so 
15814                                 // we reuse that instead of computing it again.
15815                                 posLength = positions.length;
15816                                 for (; end < posLength; end++) {
15817                                         
15818                                         outsideMax = end && positions[end - 1] > max;
15819                                         
15820                                         if (positions[end] < min) { // Set the last position before min
15821                                                 start = end;
15822                                         
15823                                         } else if (end === posLength - 1 || positions[end + 1] - positions[end] > closestDistance * 5 || outsideMax) {
15824                                                 
15825                                                 // For each segment, calculate the tick positions from the getTimeTicks utility
15826                                                 // function. The interval will be the same regardless of how long the segment is.
15827                                                 segmentPositions = getTimeTicks(normalizedInterval, positions[start], positions[end], startOfWeek);             
15828                                                 
15829                                                 groupPositions = groupPositions.concat(segmentPositions);
15830                                                 
15831                                                 // Set start of next segment
15832                                                 start = end + 1;                                                
15833                                         }
15834                                         
15835                                         if (outsideMax) {
15836                                                 break;
15837                                         }
15838                                 }
15839                                 
15840                                 // Get the grouping info from the last of the segments. The info is the same for
15841                                 // all segments.
15842                                 info = segmentPositions.info;
15843                                 
15844                                 // Optionally identify ticks with higher rank, for example when the ticks
15845                                 // have crossed midnight.
15846                                 if (findHigherRanks && info.unitRange <= timeUnits[HOUR]) {
15847                                         end = groupPositions.length - 1;
15848                                         
15849                                         // Compare points two by two
15850                                         for (start = 1; start < end; start++) {
15851                                                 if (new Date(groupPositions[start])[getDate]() !== new Date(groupPositions[start - 1])[getDate]()) {
15852                                                         higherRanks[groupPositions[start]] = DAY;
15853                                                         hasCrossedHigherRank = true;
15854                                                 }
15855                                         }
15856                                         
15857                                         // If the complete array has crossed midnight, we want to mark the first
15858                                         // positions also as higher rank
15859                                         if (hasCrossedHigherRank) {
15860                                                 higherRanks[groupPositions[0]] = DAY;
15861                                         }
15862                                         info.higherRanks = higherRanks;
15863                                 }
15864                                 
15865                                 // Save the info
15866                                 groupPositions.info = info;                             
15867                                 
15868                                 
15869                                 // Return it
15870                                 return groupPositions;
15871                                 
15872                         };
15873                         
15874                         /**
15875                          * Post process tick positions. The tickPositions array is altered. Don't show ticks 
15876                          * within a gap in the ordinal axis, where the space between
15877                          * two points is greater than a portion of the tick pixel interval
15878                          */
15879                         addEvent(xAxis, 'afterSetTickPositions', function (e) {
15880                                 
15881                                 var options = xAxis.options,
15882                                         tickPixelIntervalOption = options.tickPixelInterval,
15883                                         tickPositions = e.tickPositions;
15884                                 
15885                                 if (xAxis.ordinalPositions && defined(tickPixelIntervalOption)) { // check for squashed ticks
15886                                         var i = tickPositions.length,
15887                                                 itemToRemove,
15888                                                 translated,
15889                                                 lastTranslated,
15890                                                 tickInfo = tickPositions.info,
15891                                                 higherRanks = tickInfo ? tickInfo.higherRanks : [];
15892                                         
15893                                         while (i--) {
15894                                                 translated = xAxis.translate(tickPositions[i]);
15895                                                 
15896                                                 // Remove ticks that are closer than 0.6 times the pixel interval from the one to the right 
15897                                                 if (lastTranslated && lastTranslated - translated < tickPixelIntervalOption * 0.6) {
15898                                                         
15899                                                         // Is this a higher ranked position with a normal position to the right?
15900                                                         if (higherRanks[tickPositions[i]] && !higherRanks[tickPositions[i + 1]]) {
15901                                                                 
15902                                                                 // Yes: remove the lower ranked neighbour to the right
15903                                                                 itemToRemove = i + 1;
15904                                                                 lastTranslated = translated; // #709
15905                                                                 
15906                                                         } else {
15907                                                                 
15908                                                                 // No: remove this one
15909                                                                 itemToRemove = i;
15910                                                         }
15911                                                         
15912                                                         tickPositions.splice(itemToRemove, 1);
15913                                                         
15914                                                 } else {
15915                                                         lastTranslated = translated;
15916                                                 }
15917                                         }
15918                                 }
15919                         });
15920                         
15921                         
15922                         /**
15923                          * Overrride the chart.pan method for ordinal axes. 
15924                          */
15925                         
15926                         var baseChartPan = chart.pan;
15927                         chart.pan = function (chartX) {
15928                                 var xAxis = chart.xAxis[0],
15929                                         runBase = false;
15930                                 if (xAxis.options.ordinal) {
15931                                         
15932                                         var mouseDownX = chart.mouseDownX,
15933                                                 extremes = xAxis.getExtremes(),
15934                                                 dataMax = extremes.dataMax,
15935                                                 min = extremes.min,
15936                                                 max = extremes.max,
15937                                                 newMin,
15938                                                 newMax,
15939                                                 hoverPoints = chart.hoverPoints,
15940                                                 closestPointRange = xAxis.closestPointRange,
15941                                                 pointPixelWidth = xAxis.translationSlope * (xAxis.ordinalSlope || closestPointRange),
15942                                                 movedUnits = (mouseDownX - chartX) / pointPixelWidth, // how many ordinal units did we move?
15943                                                 extendedAxis = { ordinalPositions: xAxis.getExtendedPositions() }, // get index of all the chart's points
15944                                                 ordinalPositions,
15945                                                 searchAxisLeft,
15946                                                 lin2val = xAxis.lin2val,
15947                                                 val2lin = xAxis.val2lin,
15948                                                 searchAxisRight;
15949                                         
15950                                         if (!extendedAxis.ordinalPositions) { // we have an ordinal axis, but the data is equally spaced
15951                                                 runBase = true;
15952                                         
15953                                         } else if (mathAbs(movedUnits) > 1) {
15954                                                 
15955                                                 // Remove active points for shared tooltip
15956                                                 if (hoverPoints) {
15957                                                         each(hoverPoints, function (point) {
15958                                                                 point.setState();
15959                                                         });
15960                                                 }
15961                                                 
15962                                                 if (movedUnits < 0) {
15963                                                         searchAxisLeft = extendedAxis;
15964                                                         searchAxisRight = xAxis.ordinalPositions ? xAxis : extendedAxis;
15965                                                 } else {
15966                                                         searchAxisLeft = xAxis.ordinalPositions ? xAxis : extendedAxis;
15967                                                         searchAxisRight = extendedAxis;
15968                                                 }
15969                                                 
15970                                                 // In grouped data series, the last ordinal position represents the grouped data, which is 
15971                                                 // to the left of the real data max. If we don't compensate for this, we will be allowed
15972                                                 // to pan grouped data series passed the right of the plot area. 
15973                                                 ordinalPositions = searchAxisRight.ordinalPositions;
15974                                                 if (dataMax > ordinalPositions[ordinalPositions.length - 1]) {
15975                                                         ordinalPositions.push(dataMax);
15976                                                 }
15977                                                 
15978                                                 // Get the new min and max values by getting the ordinal index for the current extreme, 
15979                                                 // then add the moved units and translate back to values. This happens on the 
15980                                                 // extended ordinal positions if the new position is out of range, else it happens
15981                                                 // on the current x axis which is smaller and faster.
15982                                                 newMin = lin2val.apply(searchAxisLeft, [
15983                                                         val2lin.apply(searchAxisLeft, [min, true]) + movedUnits, // the new index 
15984                                                         true // translate from index
15985                                                 ]);
15986                                                 newMax = lin2val.apply(searchAxisRight, [
15987                                                         val2lin.apply(searchAxisRight, [max, true]) + movedUnits, // the new index 
15988                                                         true // translate from index
15989                                                 ]);
15990                                                 
15991                                                 // Apply it if it is within the available data range
15992                                                 if (newMin > mathMin(extremes.dataMin, min) && newMax < mathMax(dataMax, max)) {
15993                                                         xAxis.setExtremes(newMin, newMax, true, false);
15994                                                 }
15995                                 
15996                                                 chart.mouseDownX = chartX; // set new reference for next run
15997                                                 css(chart.container, { cursor: 'move' });
15998                                         }
15999                                 
16000                                 } else {
16001                                         runBase = true;
16002                                 }
16003                                 
16004                                 // revert to the linear chart.pan version
16005                                 if (runBase) {
16006                                         baseChartPan.apply(chart, arguments);
16007                                 }
16008                         }; 
16009                 }
16010         };
16011                         
16012         /**
16013          * Extend getSegments by identifying gaps in the ordinal data so that we can draw a gap in the 
16014          * line or area
16015          */
16016         seriesProto.getSegments = function () {
16017                 
16018                 var series = this,
16019                         segments,
16020                         gapSize = series.options.gapSize;
16021         
16022                 // call base method
16023                 baseGetSegments.apply(series);
16024                 
16025                 if (series.xAxis.options.ordinal && gapSize) {
16026                 
16027                         // properties
16028                         segments = series.segments;
16029                         
16030                         // extension for ordinal breaks
16031                         each(segments, function (segment, no) {
16032                                 var i = segment.length - 1;
16033                                 while (i--) {
16034                                         if (segment[i + 1].x - segment[i].x > series.xAxis.closestPointRange * gapSize) {
16035                                                 segments.splice( // insert after this one
16036                                                         no + 1,
16037                                                         0,
16038                                                         segment.splice(i + 1, segment.length - i)
16039                                                 );
16040                                         }
16041                                 }
16042                         });
16043                 }
16044         };
16045 }());
16046
16047 /* ****************************************************************************
16048  * End ordinal axis logic                                                   *
16049  *****************************************************************************/
16050 // global variables
16051 extend(Highcharts, {
16052         Chart: Chart,
16053         dateFormat: dateFormat,
16054         pathAnim: pathAnim,
16055         getOptions: getOptions,
16056         hasRtlBug: hasRtlBug,
16057         numberFormat: numberFormat,
16058         Point: Point,
16059         Color: Color,
16060         Renderer: Renderer,
16061         seriesTypes: seriesTypes,
16062         setOptions: setOptions,
16063         Series: Series,
16064
16065         // Expose utility funcitons for modules
16066         addEvent: addEvent,
16067         removeEvent: removeEvent,
16068         createElement: createElement,
16069         discardElement: discardElement,
16070         css: css,
16071         each: each,
16072         extend: extend,
16073         map: map,
16074         merge: merge,
16075         pick: pick,
16076         splat: splat,
16077         extendClass: extendClass,
16078         product: 'Highstock',
16079         version: '1.1.4'
16080 });
16081 }());