~gfunkmonk/+junk/test

« back to all changes in this revision

Viewing changes to debian/tests/data/HTML5test/scripts/highcharts/highcharts.src.js

  • Committer: Matt Rinsch
  • Date: 2017-11-03 23:04:40 UTC
  • Revision ID: gfunkmonk@gmail.com-20171103230440-v5w0ra5qscspgcrg
init

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