~widelands-dev/widelands-website/trunk

« back to all changes in this revision

Viewing changes to media/js/highcharts_219/highcharts.src.js

  • Committer: Holger Rapp
  • Date: 2019-06-21 18:34:42 UTC
  • mfrom: (540.1.3 update_ops_script)
  • Revision ID: sirver@gmx.de-20190621183442-y2ulybzr0rdvfefd
Adapt the update script for the new server.

Show diffs side-by-side

added added

removed removed

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