/**
 *  Include JSON!
 */
/*
    http://www.JSON.org/json2.js
    2011-02-23

    Public Domain.

    NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.

    See http://www.JSON.org/js.html


    This code should be minified before deployment.
    See http://javascript.crockford.com/jsmin.html

    USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO
    NOT CONTROL.


    This file creates a global JSON object containing two methods: stringify
    and parse.

        JSON.stringify(value, replacer, space)
            value       any JavaScript value, usually an object or array.

            replacer    an optional parameter that determines how object
                        values are stringified for objects. It can be a
                        function or an array of strings.

            space       an optional parameter that specifies the indentation
                        of nested structures. If it is omitted, the text will
                        be packed without extra whitespace. If it is a number,
                        it will specify the number of spaces to indent at each
                        level. If it is a string (such as '\t' or '&nbsp;'),
                        it contains the characters used to indent at each level.

            This method produces a JSON text from a JavaScript value.

            When an object value is found, if the object contains a toJSON
            method, its toJSON method will be called and the result will be
            stringified. A toJSON method does not serialize: it returns the
            value represented by the name/value pair that should be serialized,
            or undefined if nothing should be serialized. The toJSON method
            will be passed the key associated with the value, and this will be
            bound to the value

            For example, this would serialize Dates as ISO strings.

                Date.prototype.toJSON = function (key) {
                    function f(n) {
                        // Format integers to have at least two digits.
                        return n < 10 ? '0' + n : n;
                    }

                    return this.getUTCFullYear()   + '-' +
                         f(this.getUTCMonth() + 1) + '-' +
                         f(this.getUTCDate())      + 'T' +
                         f(this.getUTCHours())     + ':' +
                         f(this.getUTCMinutes())   + ':' +
                         f(this.getUTCSeconds())   + 'Z';
                };

            You can provide an optional replacer method. It will be passed the
            key and value of each member, with this bound to the containing
            object. The value that is returned from your method will be
            serialized. If your method returns undefined, then the member will
            be excluded from the serialization.

            If the replacer parameter is an array of strings, then it will be
            used to select the members to be serialized. It filters the results
            such that only members with keys listed in the replacer array are
            stringified.

            Values that do not have JSON representations, such as undefined or
            functions, will not be serialized. Such values in objects will be
            dropped; in arrays they will be replaced with null. You can use
            a replacer function to replace those with JSON values.
            JSON.stringify(undefined) returns undefined.

            The optional space parameter produces a stringification of the
            value that is filled with line breaks and indentation to make it
            easier to read.

            If the space parameter is a non-empty string, then that string will
            be used for indentation. If the space parameter is a number, then
            the indentation will be that many spaces.

            Example:

            text = JSON.stringify(['e', {pluribus: 'unum'}]);
            // text is '["e",{"pluribus":"unum"}]'


            text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t');
            // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]'

            text = JSON.stringify([new Date()], function (key, value) {
                return this[key] instanceof Date ?
                    'Date(' + this[key] + ')' : value;
            });
            // text is '["Date(---current time---)"]'


        JSON.parse(text, reviver)
            This method parses a JSON text to produce an object or array.
            It can throw a SyntaxError exception.

            The optional reviver parameter is a function that can filter and
            transform the results. It receives each of the keys and values,
            and its return value is used instead of the original value.
            If it returns what it received, then the structure is not modified.
            If it returns undefined then the member is deleted.

            Example:

            // Parse the text. Values that look like ISO date strings will
            // be converted to Date objects.

            myData = JSON.parse(text, function (key, value) {
                var a;
                if (typeof value === 'string') {
                    a =
/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value);
                    if (a) {
                        return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4],
                            +a[5], +a[6]));
                    }
                }
                return value;
            });

            myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) {
                var d;
                if (typeof value === 'string' &&
                        value.slice(0, 5) === 'Date(' &&
                        value.slice(-1) === ')') {
                    d = new Date(value.slice(5, -1));
                    if (d) {
                        return d;
                    }
                }
                return value;
            });


    This is a reference implementation. You are free to copy, modify, or
    redistribute.
*/

/*jslint evil: true, strict: false, regexp: false */

/*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply,
    call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours,
    getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join,
    lastIndex, length, parse, prototype, push, replace, slice, stringify,
    test, toJSON, toString, valueOf
*/


// Create a JSON object only if one does not already exist. We create the
// methods in a closure to avoid creating global variables.

var JSON;
if (!JSON) {
    JSON = {};
}

(function () {
    "use strict";

    function f(n) {
        // Format integers to have at least two digits.
        return n < 10 ? '0' + n : n;
    }

    if (typeof Date.prototype.toJSON !== 'function') {

        Date.prototype.toJSON = function (key) {

            return isFinite(this.valueOf()) ?
                this.getUTCFullYear()     + '-' +
                f(this.getUTCMonth() + 1) + '-' +
                f(this.getUTCDate())      + 'T' +
                f(this.getUTCHours())     + ':' +
                f(this.getUTCMinutes())   + ':' +
                f(this.getUTCSeconds())   + 'Z' : null;
        };

        String.prototype.toJSON      =
            Number.prototype.toJSON  =
            Boolean.prototype.toJSON = function (key) {
                return this.valueOf();
            };
    }

    var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
        escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
        gap,
        indent,
        meta = {    // table of character substitutions
            '\b': '\\b',
            '\t': '\\t',
            '\n': '\\n',
            '\f': '\\f',
            '\r': '\\r',
            '"' : '\\"',
            '\\': '\\\\'
        },
        rep;


    function quote(string) {

// If the string contains no control characters, no quote characters, and no
// backslash characters, then we can safely slap some quotes around it.
// Otherwise we must also replace the offending characters with safe escape
// sequences.

        escapable.lastIndex = 0;
        return escapable.test(string) ? '"' + string.replace(escapable, function (a) {
            var c = meta[a];
            return typeof c === 'string' ? c :
                '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
        }) + '"' : '"' + string + '"';
    }


    function str(key, holder) {

// Produce a string from holder[key].

        var i,          // The loop counter.
            k,          // The member key.
            v,          // The member value.
            length,
            mind = gap,
            partial,
            value = holder[key];

// If the value has a toJSON method, call it to obtain a replacement value.

        if (value && typeof value === 'object' &&
                typeof value.toJSON === 'function') {
            value = value.toJSON(key);
        }

// If we were called with a replacer function, then call the replacer to
// obtain a replacement value.

        if (typeof rep === 'function') {
            value = rep.call(holder, key, value);
        }

// What happens next depends on the value's type.

        switch (typeof value) {
        case 'string':
            return quote(value);

        case 'number':

// JSON numbers must be finite. Encode non-finite numbers as null.

            return isFinite(value) ? String(value) : 'null';

        case 'boolean':
        case 'null':

// If the value is a boolean or null, convert it to a string. Note:
// typeof null does not produce 'null'. The case is included here in
// the remote chance that this gets fixed someday.

            return String(value);

// If the type is 'object', we might be dealing with an object or an array or
// null.

        case 'object':

// Due to a specification blunder in ECMAScript, typeof null is 'object',
// so watch out for that case.

            if (!value) {
                return 'null';
            }

// Make an array to hold the partial results of stringifying this object value.

            gap += indent;
            partial = [];

// Is the value an array?

            if (Object.prototype.toString.apply(value) === '[object Array]') {

// The value is an array. Stringify every element. Use null as a placeholder
// for non-JSON values.

                length = value.length;
                for (i = 0; i < length; i += 1) {
                    partial[i] = str(i, value) || 'null';
                }

// Join all of the elements together, separated with commas, and wrap them in
// brackets.

                v = partial.length === 0 ? '[]' : gap ?
                    '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']' :
                    '[' + partial.join(',') + ']';
                gap = mind;
                return v;
            }

// If the replacer is an array, use it to select the members to be stringified.

            if (rep && typeof rep === 'object') {
                length = rep.length;
                for (i = 0; i < length; i += 1) {
                    if (typeof rep[i] === 'string') {
                        k = rep[i];
                        v = str(k, value);
                        if (v) {
                            partial.push(quote(k) + (gap ? ': ' : ':') + v);
                        }
                    }
                }
            } else {

// Otherwise, iterate through all of the keys in the object.

                for (k in value) {
                    if (Object.prototype.hasOwnProperty.call(value, k)) {
                        v = str(k, value);
                        if (v) {
                            partial.push(quote(k) + (gap ? ': ' : ':') + v);
                        }
                    }
                }
            }

// Join all of the member texts together, separated with commas,
// and wrap them in braces.

            v = partial.length === 0 ? '{}' : gap ?
                '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}' :
                '{' + partial.join(',') + '}';
            gap = mind;
            return v;
        }
    }

// If the JSON object does not yet have a stringify method, give it one.

    if (typeof JSON.stringify !== 'function') {
        JSON.stringify = function (value, replacer, space) {

// The stringify method takes a value and an optional replacer, and an optional
// space parameter, and returns a JSON text. The replacer can be a function
// that can replace values, or an array of strings that will select the keys.
// A default replacer method can be provided. Use of the space parameter can
// produce text that is more easily readable.

            var i;
            gap = '';
            indent = '';

// If the space parameter is a number, make an indent string containing that
// many spaces.

            if (typeof space === 'number') {
                for (i = 0; i < space; i += 1) {
                    indent += ' ';
                }

// If the space parameter is a string, it will be used as the indent string.

            } else if (typeof space === 'string') {
                indent = space;
            }

// If there is a replacer, it must be a function or an array.
// Otherwise, throw an error.

            rep = replacer;
            if (replacer && typeof replacer !== 'function' &&
                    (typeof replacer !== 'object' ||
                    typeof replacer.length !== 'number')) {
                throw new Error('JSON.stringify');
            }

// Make a fake root object containing our value under the key of ''.
// Return the result of stringifying the value.

            return str('', {'': value});
        };
    }


// If the JSON object does not yet have a parse method, give it one.

    if (typeof JSON.parse !== 'function') {
        JSON.parse = function (text, reviver) {

// The parse method takes a text and an optional reviver function, and returns
// a JavaScript value if the text is a valid JSON text.

            var j;

            function walk(holder, key) {

// The walk method is used to recursively walk the resulting structure so
// that modifications can be made.

                var k, v, value = holder[key];
                if (value && typeof value === 'object') {
                    for (k in value) {
                        if (Object.prototype.hasOwnProperty.call(value, k)) {
                            v = walk(value, k);
                            if (v !== undefined) {
                                value[k] = v;
                            } else {
                                delete value[k];
                            }
                        }
                    }
                }
                return reviver.call(holder, key, value);
            }


// Parsing happens in four stages. In the first stage, we replace certain
// Unicode characters with escape sequences. JavaScript handles many characters
// incorrectly, either silently deleting them, or treating them as line endings.

            text = String(text);
            cx.lastIndex = 0;
            if (cx.test(text)) {
                text = text.replace(cx, function (a) {
                    return '\\u' +
                        ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
                });
            }

// In the second stage, we run the text against regular expressions that look
// for non-JSON patterns. We are especially concerned with '()' and 'new'
// because they can cause invocation, and '=' because it can cause mutation.
// But just to be safe, we want to reject all unexpected forms.

// We split the second stage into 4 regexp operations in order to work around
// crippling inefficiencies in IE's and Safari's regexp engines. First we
// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we
// replace all simple value tokens with ']' characters. Third, we delete all
// open brackets that follow a colon or comma or that begin the text. Finally,
// we look to see that the remaining characters are only whitespace or ']' or
// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.

            if (/^[\],:{}\s]*$/
                    .test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@')
                        .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']')
                        .replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {

// In the third stage we use the eval function to compile the text into a
// JavaScript structure. The '{' operator is subject to a syntactic ambiguity
// in JavaScript: it can begin a block or an object literal. We wrap the text
// in parens to eliminate the ambiguity.

                j = eval('(' + text + ')');

// In the optional fourth stage, we recursively walk the new structure, passing
// each name/value pair to a reviver function for possible transformation.

                return typeof reviver === 'function' ?
                    walk({'': j}, '') : j;
            }

// If the text is not JSON parseable, then a SyntaxError is thrown.

            throw new SyntaxError('JSON.parse');
        };
    }
}());
// VERSION: 1.8 LAST UPDATE: 9.03.2011
/* 
 * Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
 * 
 * Made by Wilq32, wilq32@gmail.com, Wroclaw, Poland, 01.2009
 * Website: http://code.google.com/p/jqueryrotate/ 
 */

// Documentation removed from script file (was kinda useless and outdated)

(function($) {
var supportedCSS,styles=document.getElementsByTagName("head")[0].style,toCheck="transformProperty WebkitTransform OTransform msTransform MozTransform".split(" "); //MozTransform <- firefox works slower with css!!!
for (var a=0;a<toCheck.length;a++) if (styles[toCheck[a]] !== undefined) supportedCSS = toCheck[a];
// Bad eval to preven google closure to remove it from code o_O
// After compresion replace it back to var IE = 'v' == '\v'
var IE = eval('"v"=="\v"');

jQuery.fn.extend({
ImageRotate:function(parameters)
{
    // If this element is already a Wilq32.PhotoEffect object, skip creation
    if (this.Wilq32&&this.Wilq32.PhotoEffect) return;
    // parameters might be applied to many objects - so because we use them later - a fresh instance is needed 
    var paramClone = $.extend(true, {}, parameters); 
    return (new Wilq32.PhotoEffect(this.get(0),paramClone))._rootObj;
},
rotate:function(parameters)
{
    if (this.length===0||typeof parameters=="undefined") return;
    if (typeof parameters=="number") parameters={angle:parameters};
    var returned=[];
    for (var i=0,i0=this.length;i<i0;i++)
    {
        var element=this.get(i);    
        if (typeof element.Wilq32 == "undefined") 
            returned.push($($(element).ImageRotate(parameters)));
        else 
            element.Wilq32.PhotoEffect._handleRotation(parameters);
    }
    return returned;
}
});

// Library agnostic interface

Wilq32=window.Wilq32||{};
Wilq32.PhotoEffect=(function(){

    if (supportedCSS) {
        return function(img,parameters){
            img.Wilq32 = {PhotoEffect: this};
            
            this._img = this._rootObj = this._eventObj = img;
            this._handleRotation(parameters);
        }
    } else if (IE) {
        return function(img,parameters) {
            // Make sure that class and id are also copied - just in case you would like to refeer to an newly created object
            this._img = img;

            this._rootObj=document.createElement('span');
            this._rootObj.style.display="inline-block";
            this._rootObj.Wilq32 = {PhotoEffect: this};
            img.parentNode.insertBefore(this._rootObj,img);
            this._Loader(parameters);
        }
    } else {
        return function(img,parameters){
            // Just for now... Dont do anything if CSS3 is not supported
        this._rootObj = img;
        }
    }
})();

Wilq32.PhotoEffect.prototype={
    _setupParameters : function (parameters){
        this._parameters = this._parameters || {};
        if (typeof this._angle !== "number") this._angle = 0 ;
        if (typeof parameters.angle==="number") this._angle = parameters.angle;
        this._parameters.animateTo = (typeof parameters.animateTo==="number") ? (parameters.animateTo) : (this._angle); 

        this._parameters.easing = parameters.easing || this._parameters.easing || function (x, t, b, c, d) { return -c * ((t=t/d-1)*t*t*t - 1) + b; }
        this._parameters.duration = parameters.duration || this._parameters.duration || 1000;
        this._parameters.callback = parameters.callback || this._parameters.callback || function(){};
        if (parameters.bind && parameters.bind != this._parameters.bind) this._BindEvents(parameters.bind); 
    },
    _handleRotation : function(parameters){
          this._setupParameters(parameters);
          if (this._angle==this._parameters.animateTo) {
              this._rotate(this._angle);
          }
          else { 
              this._animateStart();          
          }
    },

    _BindEvents:function(events){
        if (events && this._eventObj) 
        {
            // Unbinding previous Events
            if (this._parameters.bind){
                var oldEvents = this._parameters.bind;
                for (var a in oldEvents) if (oldEvents.hasOwnProperty(a)) 
                        // TODO: Remove jQuery dependency
                        jQuery(this._eventObj).unbind(a,oldEvents[a]);
            }

            this._parameters.bind = events;
            for (var a in events) if (events.hasOwnProperty(a)) 
                // TODO: Remove jQuery dependency
                    jQuery(this._eventObj).bind(a,events[a]);
        }
    },

    _Loader: function(parameters)
        {
            var width=this._img.width;
            var height=this._img.height;
            //this._img.parentNode.removeChild(this._img);
            //this._rootObj.parentNode.removeChild(this._rootObj);
            
            this._rootObj.appendChild(this._img);

            this._rootObj.style.width = this._img.offsetWidth;
            this._rootObj.style.height = this._img.offsetHeight;

            this._img.style.position = "absolute";

            this._rootObj = this._img;
            this._rootObj.Wilq32 = {PhotoEffect: this}

            this._rootObj.style.filter += "progid:DXImageTransform.Microsoft.Matrix(M11=1,M12=1,M21=1,M22=1,sizingMethod='auto expand')";

            this._eventObj = this._rootObj;    
            this._handleRotation(parameters);
        },

    _animateStart:function()
    {    
        if (this._timer) {
            clearTimeout(this._timer);
        }
        this._animateStartTime = +new Date;
        this._animateStartAngle = this._angle;
        this._animate();
    },
_animate:function()
     {
         var actualTime = +new Date;
         var checkEnd = actualTime - this._animateStartTime > this._parameters.duration;

         // TODO: Bug for animatedGif for static rotation ? (to test)
         if (checkEnd && !this._parameters.animatedGif) 
         {
             clearTimeout(this._timer);
         }
         else 
         {
             if (this._canvas||this._vimage||this._img) {
                 var angle = this._parameters.easing(0, actualTime - this._animateStartTime, this._animateStartAngle, this._parameters.animateTo - this._animateStartAngle, this._parameters.duration);
                 this._rotate((~~(angle*10))/10);
             }
             var self = this;
             this._timer = setTimeout(function()
                     {
                     self._animate.call(self);
                     }, 10);
         }

         // To fix Bug that prevents using recursive function in callback I moved this function to back
         if (this._parameters.callback && checkEnd){
             this._angle = this._parameters.animateTo;
             this._rotate(this._angle);
             this._parameters.callback.call(this._rootObj);
         }
     },

    _rotate : (function()
    {
        var rad = Math.PI/180;
        if (IE)
        return function(angle)
        {
            this._angle = angle;
            //this._container.style.rotation=(angle%360)+"deg";
            var _rad = angle * rad ;
            costheta = Math.cos(_rad);
            sintheta = Math.sin(_rad);
            var fil = this._rootObj.filters.item("DXImageTransform.Microsoft.Matrix");
            fil.M11=costheta; fil.M12=-sintheta; fil.M21=sintheta; fil.M22=costheta;

            this._rootObj.style.marginLeft = -(this._rootObj.offsetWidth - this._rootObj.clientWidth)/2 +"px";
            this._rootObj.style.marginTop = -(this._rootObj.offsetHeight - this._rootObj.clientHeight)/2 +"px";
        }
        else if (supportedCSS)
        return function(angle){
            this._angle = angle;
            this._img.style[supportedCSS]="rotate("+(angle%360)+"deg)";
        }

    })()
}
})(jQuery);


/**
 *  We need jQuery, and all its plugings, and translation files.
 */
﻿/* Dutch (UTF-8) initialisation for the jQuery UI date picker plugin. */
/* Written by Mathias Bynens <http://mathiasbynens.be/> */
jQuery(function($){
    $.datepicker.regional.nl = {
        closeText: 'Sluiten',
        prevText: '←',
        nextText: '→',
        currentText: 'Vandaag',
        monthNames: ['januari', 'februari', 'maart', 'april', 'mei', 'juni',
        'juli', 'augustus', 'september', 'oktober', 'november', 'december'],
        monthNamesShort: ['jan', 'feb', 'maa', 'apr', 'mei', 'jun',
        'jul', 'aug', 'sep', 'okt', 'nov', 'dec'],
        dayNames: ['zondag', 'maandag', 'dinsdag', 'woensdag', 'donderdag', 'vrijdag', 'zaterdag'],
        dayNamesShort: ['zon', 'maa', 'din', 'woe', 'don', 'vri', 'zat'],
        dayNamesMin: ['zo', 'ma', 'di', 'wo', 'do', 'vr', 'za'],
        weekHeader: 'Wk',
        dateFormat: 'dd-mm-yy',
        firstDay: 1,
        isRTL: false,
        showMonthAfterYear: false,
        yearSuffix: ''};
});

﻿/* English/UK initialisation for the jQuery UI date picker plugin. */
/* Written by Stuart. */
jQuery(function($){
    $.datepicker.regional['en-GB'] = {
        closeText: 'Done',
        prevText: 'Prev',
        nextText: 'Next',
        currentText: 'Today',
        monthNames: ['January','February','March','April','May','June',
        'July','August','September','October','November','December'],
        monthNamesShort: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
        'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
        dayNames: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
        dayNamesShort: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
        dayNamesMin: ['Su','Mo','Tu','We','Th','Fr','Sa'],
        weekHeader: 'Wk',
        dateFormat: 'dd/mm/yy',
        firstDay: 1,
        isRTL: false,
        showMonthAfterYear: false,
        yearSuffix: ''};
});



var datatable = {regional:[]};
$(function(){
    datatable.regional['nl'] = {
        "sProcessing":   "Bezig met verwerken...",
        "sLengthMenu":   "Toon _MENU_ rijen",
        "sZeroRecords":  "Geen resultaten gevonden",
        "sInfo":         "_START_ tot _END_ van _TOTAL_ rijen",
        "sInfoEmpty":    "Er zijn geen records om te tonen",
        "sInfoFiltered": "(gefilterd uit _MAX_ rijen)",
        "sInfoPostFix":  "",
        "sSearch":       "Zoek:",
        "sUrl":          "",
        "oPaginate": {
            "sFirst":    "Eerste",
            "sPrevious": "Vorige",
            "sNext":     "Volgende",
            "sLast":     "Laatste"
        }
    }
});

$(function(){
    datatable.regional['en'] = {
        "sProcessing":   "Processing...",
        "sLengthMenu":   "Show _MENU_ entries",
        "sZeroRecords":  "No matching records found",
        "sInfo":         "Showing _START_ to _END_ of _TOTAL_ entries",
        "sInfoEmpty":    "Showing 0 to 0 of 0 entries",
        "sInfoFiltered": "(filtered from _MAX_ total entries)",
        "sInfoPostFix":  "",
        "sSearch":       "Search:",
        "sUrl":          "",
        "oPaginate": {
            "sFirst":    "First",
            "sPrevious": "Previous",
            "sNext":     "Next",
            "sLast":     "Last"
        }
    }
});


/*
 * 
 * TableSorter 2.0 - Client-side table sorting with ease!
 * Version 2.0.5b
 * @requires jQuery v1.2.3
 * 
 * Copyright (c) 2007 Christian Bach
 * Examples and docs at: http://tablesorter.com
 * Dual licensed under the MIT and GPL licenses:
 * http://www.opensource.org/licenses/mit-license.php
 * http://www.gnu.org/licenses/gpl.html
 * 
 */
/**
 * 
 * @description Create a sortable table with multi-column sorting capabilitys
 * 
 * @example $('table').tablesorter();
 * @desc Create a simple tablesorter interface.
 * 
 * @example $('table').tablesorter({ sortList:[[0,0],[1,0]] });
 * @desc Create a tablesorter interface and sort on the first and secound column column headers.
 * 
 * @example $('table').tablesorter({ headers: { 0: { sorter: false}, 1: {sorter: false} } });
 *          
 * @desc Create a tablesorter interface and disableing the first and second  column headers.
 *      
 * 
 * @example $('table').tablesorter({ headers: { 0: {sorter:"integer"}, 1: {sorter:"currency"} } });
 * 
 * @desc Create a tablesorter interface and set a column parser for the first
 *       and second column.
 * 
 * 
 * @param Object
 *            settings An object literal containing key/value pairs to provide
 *            optional settings.
 * 
 * 
 * @option String cssHeader (optional) A string of the class name to be appended
 *         to sortable tr elements in the thead of the table. Default value:
 *         "header"
 * 
 * @option String cssAsc (optional) A string of the class name to be appended to
 *         sortable tr elements in the thead on a ascending sort. Default value:
 *         "headerSortUp"
 * 
 * @option String cssDesc (optional) A string of the class name to be appended
 *         to sortable tr elements in the thead on a descending sort. Default
 *         value: "headerSortDown"
 * 
 * @option String sortInitialOrder (optional) A string of the inital sorting
 *         order can be asc or desc. Default value: "asc"
 * 
 * @option String sortMultisortKey (optional) A string of the multi-column sort
 *         key. Default value: "shiftKey"
 * 
 * @option String textExtraction (optional) A string of the text-extraction
 *         method to use. For complex html structures inside td cell set this
 *         option to "complex", on large tables the complex option can be slow.
 *         Default value: "simple"
 * 
 * @option Object headers (optional) An array containing the forces sorting
 *         rules. This option let's you specify a default sorting rule. Default
 *         value: null
 * 
 * @option Array sortList (optional) An array containing the forces sorting
 *         rules. This option let's you specify a default sorting rule. Default
 *         value: null
 * 
 * @option Array sortForce (optional) An array containing forced sorting rules.
 *         This option let's you specify a default sorting rule, which is
 *         prepended to user-selected rules. Default value: null
 * 
 * @option Boolean sortLocaleCompare (optional) Boolean flag indicating whatever
 *         to use String.localeCampare method or not. Default set to true.
 * 
 * 
 * @option Array sortAppend (optional) An array containing forced sorting rules.
 *         This option let's you specify a default sorting rule, which is
 *         appended to user-selected rules. Default value: null
 * 
 * @option Boolean widthFixed (optional) Boolean flag indicating if tablesorter
 *         should apply fixed widths to the table columns. This is usefull when
 *         using the pager companion plugin. This options requires the dimension
 *         jquery plugin. Default value: false
 * 
 * @option Boolean cancelSelection (optional) Boolean flag indicating if
 *         tablesorter should cancel selection of the table headers text.
 *         Default value: true
 * 
 * @option Boolean debug (optional) Boolean flag indicating if tablesorter
 *         should display debuging information usefull for development.
 * 
 * @type jQuery
 * 
 * @name tablesorter
 * 
 * @cat Plugins/Tablesorter
 * 
 * @author Christian Bach/christian.bach@polyester.se
 */

(function ($) {
    $.extend({
        tablesorter: new
        function () {

            var parsers = [],
                widgets = [];

            this.defaults = {
                cssHeader: "header",
                cssAsc: "headerSortUp",
                cssDesc: "headerSortDown",
                cssChildRow: "expand-child",
                sortInitialOrder: "asc",
                sortMultiSortKey: "shiftKey",
                sortForce: null,
                sortAppend: null,
                sortLocaleCompare: true,
                textExtraction: "simple",
                parsers: {}, widgets: [],
                widgetZebra: {
                    css: ["even", "odd"]
                }, headers: {}, widthFixed: false,
                cancelSelection: true,
                sortList: [],
                headerList: [],
                dateFormat: "us",
                decimal: '/\.|\,/g',
                onRenderHeader: null,
                selectorHeaders: 'thead th',
                debug: false
            };

            /* debuging utils */

            function benchmark(s, d) {
                log(s + "," + (new Date().getTime() - d.getTime()) + "ms");
            }

            this.benchmark = benchmark;

            function log(s) {
                if (typeof console != "undefined" && typeof console.debug != "undefined") {
                    console.log(s);
                } else {
                    alert(s);
                }
            }

            /* parsers utils */

            function buildParserCache(table, $headers) {

                if (table.config.debug) {
                    var parsersDebug = "";
                }

                if (table.tBodies.length == 0) return; // In the case of empty tables
                var rows = table.tBodies[0].rows;

                if (rows[0]) {

                    var list = [],
                        cells = rows[0].cells,
                        l = cells.length;

                    for (var i = 0; i < l; i++) {

                        var p = false;

                        if ($.metadata && ($($headers[i]).metadata() && $($headers[i]).metadata().sorter)) {

                            p = getParserById($($headers[i]).metadata().sorter);

                        } else if ((table.config.headers[i] && table.config.headers[i].sorter)) {

                            p = getParserById(table.config.headers[i].sorter);
                        }
                        if (!p) {

                            p = detectParserForColumn(table, rows, -1, i);
                        }

                        if (table.config.debug) {
                            parsersDebug += "column:" + i + " parser:" + p.id + "\n";
                        }

                        list.push(p);
                    }
                }

                if (table.config.debug) {
                    log(parsersDebug);
                }

                return list;
            };

            function detectParserForColumn(table, rows, rowIndex, cellIndex) {
                var l = parsers.length,
                    node = false,
                    nodeValue = false,
                    keepLooking = true;
                while (nodeValue == '' && keepLooking) {
                    rowIndex++;
                    if (rows[rowIndex]) {
                        node = getNodeFromRowAndCellIndex(rows, rowIndex, cellIndex);
                        nodeValue = trimAndGetNodeText(table.config, node);
                        if (table.config.debug) {
                            log('Checking if value was empty on row:' + rowIndex);
                        }
                    } else {
                        keepLooking = false;
                    }
                }
                for (var i = 1; i < l; i++) {
                    if (parsers[i].is(nodeValue, table, node)) {
                        return parsers[i];
                    }
                }
                // 0 is always the generic parser (text)
                return parsers[0];
            }

            function getNodeFromRowAndCellIndex(rows, rowIndex, cellIndex) {
                return rows[rowIndex].cells[cellIndex];
            }

            function trimAndGetNodeText(config, node) {
                return $.trim(getElementText(config, node));
            }

            function getParserById(name) {
                var l = parsers.length;
                for (var i = 0; i < l; i++) {
                    if (parsers[i].id.toLowerCase() == name.toLowerCase()) {
                        return parsers[i];
                    }
                }
                return false;
            }

            /* utils */

            function buildCache(table) {

                if (table.config.debug) {
                    var cacheTime = new Date();
                }

                var totalRows = (table.tBodies[0] && table.tBodies[0].rows.length) || 0,
                    totalCells = (table.tBodies[0].rows[0] && table.tBodies[0].rows[0].cells.length) || 0,
                    parsers = table.config.parsers,
                    cache = {
                        row: [],
                        normalized: []
                    };

                for (var i = 0; i < totalRows; ++i) {

                    /** Add the table data to main data array */
                    var c = $(table.tBodies[0].rows[i]),
                        cols = [];

                    // if this is a child row, add it to the last row's children and
                    // continue to the next row
                    if (c.hasClass(table.config.cssChildRow)) {
                        cache.row[cache.row.length - 1] = cache.row[cache.row.length - 1].add(c);
                        // go to the next for loop
                        continue;
                    }

                    cache.row.push(c);

                    for (var j = 0; j < totalCells; ++j) {
                        cols.push(parsers[j].format(getElementText(table.config, c[0].cells[j]), table, c[0].cells[j]));
                    }

                    cols.push(cache.normalized.length); // add position for rowCache
                    cache.normalized.push(cols);
                    cols = null;
                };

                if (table.config.debug) {
                    benchmark("Building cache for " + totalRows + " rows:", cacheTime);
                }

                return cache;
            };

            function getElementText(config, node) {

                var text = "";

                if (!node) return "";

                if (!config.supportsTextContent) config.supportsTextContent = node.textContent || false;

                if (config.textExtraction == "simple") {
                    if (config.supportsTextContent) {
                        text = node.textContent;
                    } else {
                        if (node.childNodes[0] && node.childNodes[0].hasChildNodes()) {
                            text = node.childNodes[0].innerHTML;
                        } else {
                            text = node.innerHTML;
                        }
                    }
                } else {
                    if (typeof(config.textExtraction) == "function") {
                        text = config.textExtraction(node);
                    } else {
                        text = $(node).text();
                    }
                }
                return text;
            }

            function appendToTable(table, cache) {

                if (table.config.debug) {
                    var appendTime = new Date()
                }

                var c = cache,
                    r = c.row,
                    n = c.normalized,
                    totalRows = n.length,
                    checkCell = (n[0].length - 1),
                    tableBody = $(table.tBodies[0]),
                    rows = [];


                for (var i = 0; i < totalRows; i++) {
                    var pos = n[i][checkCell];

                    rows.push(r[pos]);

                    if (!table.config.appender) {

                        //var o = ;
                        var l = r[pos].length;
                        for (var j = 0; j < l; j++) {
                            tableBody[0].appendChild(r[pos][j]);
                        }

                        // 
                    }
                }



                if (table.config.appender) {

                    table.config.appender(table, rows);
                }

                rows = null;

                if (table.config.debug) {
                    benchmark("Rebuilt table:", appendTime);
                }

                // apply table widgets
                applyWidget(table);

                // trigger sortend
                setTimeout(function () {
                    $(table).trigger("sortEnd");
                }, 0);

            };

            function buildHeaders(table) {

                if (table.config.debug) {
                    var time = new Date();
                }

                var meta = ($.metadata) ? true : false;
                
                var header_index = computeTableHeaderCellIndexes(table);

                $tableHeaders = $(table.config.selectorHeaders, table).each(function (index) {

                    this.column = header_index[this.parentNode.rowIndex + "-" + this.cellIndex];
                    // this.column = index;
                    this.order = formatSortingOrder(table.config.sortInitialOrder);
                    
                    
                    this.count = this.order;

                    if (checkHeaderMetadata(this) || checkHeaderOptions(table, index)) this.sortDisabled = true;
                    if (checkHeaderOptionsSortingLocked(table, index)) this.order = this.lockedOrder = checkHeaderOptionsSortingLocked(table, index);

                    if (!this.sortDisabled) {
                        var $th = $(this).addClass(table.config.cssHeader);
                        if (table.config.onRenderHeader) table.config.onRenderHeader.apply($th);
                    }

                    // add cell to headerList
                    table.config.headerList[index] = this;
                });

                if (table.config.debug) {
                    benchmark("Built headers:", time);
                    log($tableHeaders);
                }

                return $tableHeaders;

            };

            // from:
            // http://www.javascripttoolbox.com/lib/table/examples.php
            // http://www.javascripttoolbox.com/temp/table_cellindex.html


            function computeTableHeaderCellIndexes(t) {
                var matrix = [];
                var lookup = {};
                var thead = t.getElementsByTagName('THEAD')[0];
                var trs = thead.getElementsByTagName('TR');

                for (var i = 0; i < trs.length; i++) {
                    var cells = trs[i].cells;
                    for (var j = 0; j < cells.length; j++) {
                        var c = cells[j];

                        var rowIndex = c.parentNode.rowIndex;
                        var cellId = rowIndex + "-" + c.cellIndex;
                        var rowSpan = c.rowSpan || 1;
                        var colSpan = c.colSpan || 1
                        var firstAvailCol;
                        if (typeof(matrix[rowIndex]) == "undefined") {
                            matrix[rowIndex] = [];
                        }
                        // Find first available column in the first row
                        for (var k = 0; k < matrix[rowIndex].length + 1; k++) {
                            if (typeof(matrix[rowIndex][k]) == "undefined") {
                                firstAvailCol = k;
                                break;
                            }
                        }
                        lookup[cellId] = firstAvailCol;
                        for (var k = rowIndex; k < rowIndex + rowSpan; k++) {
                            if (typeof(matrix[k]) == "undefined") {
                                matrix[k] = [];
                            }
                            var matrixrow = matrix[k];
                            for (var l = firstAvailCol; l < firstAvailCol + colSpan; l++) {
                                matrixrow[l] = "x";
                            }
                        }
                    }
                }
                return lookup;
            }

            function checkCellColSpan(table, rows, row) {
                var arr = [],
                    r = table.tHead.rows,
                    c = r[row].cells;

                for (var i = 0; i < c.length; i++) {
                    var cell = c[i];

                    if (cell.colSpan > 1) {
                        arr = arr.concat(checkCellColSpan(table, headerArr, row++));
                    } else {
                        if (table.tHead.length == 1 || (cell.rowSpan > 1 || !r[row + 1])) {
                            arr.push(cell);
                        }
                        // headerArr[row] = (i+row);
                    }
                }
                return arr;
            };

            function checkHeaderMetadata(cell) {
                if (($.metadata) && ($(cell).metadata().sorter === false)) {
                    return true;
                };
                return false;
            }

            function checkHeaderOptions(table, i) {
                if ((table.config.headers[i]) && (table.config.headers[i].sorter === false)) {
                    return true;
                };
                return false;
            }
            
             function checkHeaderOptionsSortingLocked(table, i) {
                if ((table.config.headers[i]) && (table.config.headers[i].lockedOrder)) return table.config.headers[i].lockedOrder;
                return false;
            }
            
            function applyWidget(table) {
                var c = table.config.widgets;
                var l = c.length;
                for (var i = 0; i < l; i++) {

                    getWidgetById(c[i]).format(table);
                }

            }

            function getWidgetById(name) {
                var l = widgets.length;
                for (var i = 0; i < l; i++) {
                    if (widgets[i].id.toLowerCase() == name.toLowerCase()) {
                        return widgets[i];
                    }
                }
            };

            function formatSortingOrder(v) {
                if (typeof(v) != "Number") {
                    return (v.toLowerCase() == "desc") ? 1 : 0;
                } else {
                    return (v == 1) ? 1 : 0;
                }
            }

            function isValueInArray(v, a) {
                var l = a.length;
                for (var i = 0; i < l; i++) {
                    if (a[i][0] == v) {
                        return true;
                    }
                }
                return false;
            }

            function setHeadersCss(table, $headers, list, css) {
                // remove all header information
                $headers.removeClass(css[0]).removeClass(css[1]);

                var h = [];
                $headers.each(function (offset) {
                    if (!this.sortDisabled) {
                        h[this.column] = $(this);
                    }
                });

                var l = list.length;
                for (var i = 0; i < l; i++) {
                    h[list[i][0]].addClass(css[list[i][1]]);
                }
            }

            function fixColumnWidth(table, $headers) {
                var c = table.config;
                if (c.widthFixed) {
                    var colgroup = $('<colgroup>');
                    $("tr:first td", table.tBodies[0]).each(function () {
                        colgroup.append($('<col>').css('width', $(this).width()));
                    });
                    $(table).prepend(colgroup);
                };
            }

            function updateHeaderSortCount(table, sortList) {
                var c = table.config,
                    l = sortList.length;
                for (var i = 0; i < l; i++) {
                    var s = sortList[i],
                        o = c.headerList[s[0]];
                    o.count = s[1];
                    o.count++;
                }
            }

            /* sorting methods */

            function multisort(table, sortList, cache) {

                if (table.config.debug) {
                    var sortTime = new Date();
                }

                var dynamicExp = "var sortWrapper = function(a,b) {",
                    l = sortList.length;

                // TODO: inline functions.
                for (var i = 0; i < l; i++) {

                    var c = sortList[i][0];
                    var order = sortList[i][1];
                    // var s = (getCachedSortType(table.config.parsers,c) == "text") ?
                    // ((order == 0) ? "sortText" : "sortTextDesc") : ((order == 0) ?
                    // "sortNumeric" : "sortNumericDesc");
                    // var s = (table.config.parsers[c].type == "text") ? ((order == 0)
                    // ? makeSortText(c) : makeSortTextDesc(c)) : ((order == 0) ?
                    // makeSortNumeric(c) : makeSortNumericDesc(c));
                    var s = (table.config.parsers[c].type == "text") ? ((order == 0) ? makeSortFunction("text", "asc", c) : makeSortFunction("text", "desc", c)) : ((order == 0) ? makeSortFunction("numeric", "asc", c) : makeSortFunction("numeric", "desc", c));
                    var e = "e" + i;

                    dynamicExp += "var " + e + " = " + s; // + "(a[" + c + "],b[" + c
                    // + "]); ";
                    dynamicExp += "if(" + e + ") { return " + e + "; } ";
                    dynamicExp += "else { ";

                }

                // if value is the same keep orignal order
                var orgOrderCol = cache.normalized[0].length - 1;
                dynamicExp += "return a[" + orgOrderCol + "]-b[" + orgOrderCol + "];";

                for (var i = 0; i < l; i++) {
                    dynamicExp += "}; ";
                }

                dynamicExp += "return 0; ";
                dynamicExp += "}; ";

                if (table.config.debug) {
                    benchmark("Evaling expression:" + dynamicExp, new Date());
                }

                eval(dynamicExp);

                cache.normalized.sort(sortWrapper);

                if (table.config.debug) {
                    benchmark("Sorting on " + sortList.toString() + " and dir " + order + " time:", sortTime);
                }

                return cache;
            };

            function makeSortFunction(type, direction, index) {
                var a = "a[" + index + "]",
                    b = "b[" + index + "]";
                if (type == 'text' && direction == 'asc') {
                    return "(" + a + " == " + b + " ? 0 : (" + a + " === null ? Number.POSITIVE_INFINITY : (" + b + " === null ? Number.NEGATIVE_INFINITY : (" + a + " < " + b + ") ? -1 : 1 )));";
                } else if (type == 'text' && direction == 'desc') {
                    return "(" + a + " == " + b + " ? 0 : (" + a + " === null ? Number.POSITIVE_INFINITY : (" + b + " === null ? Number.NEGATIVE_INFINITY : (" + b + " < " + a + ") ? -1 : 1 )));";
                } else if (type == 'numeric' && direction == 'asc') {
                    return "(" + a + " === null && " + b + " === null) ? 0 :(" + a + " === null ? Number.POSITIVE_INFINITY : (" + b + " === null ? Number.NEGATIVE_INFINITY : " + a + " - " + b + "));";
                } else if (type == 'numeric' && direction == 'desc') {
                    return "(" + a + " === null && " + b + " === null) ? 0 :(" + a + " === null ? Number.POSITIVE_INFINITY : (" + b + " === null ? Number.NEGATIVE_INFINITY : " + b + " - " + a + "));";
                }
            };

            function makeSortText(i) {
                return "((a[" + i + "] < b[" + i + "]) ? -1 : ((a[" + i + "] > b[" + i + "]) ? 1 : 0));";
            };

            function makeSortTextDesc(i) {
                return "((b[" + i + "] < a[" + i + "]) ? -1 : ((b[" + i + "] > a[" + i + "]) ? 1 : 0));";
            };

            function makeSortNumeric(i) {
                return "a[" + i + "]-b[" + i + "];";
            };

            function makeSortNumericDesc(i) {
                return "b[" + i + "]-a[" + i + "];";
            };

            function sortText(a, b) {
                if (table.config.sortLocaleCompare) return a.localeCompare(b);
                return ((a < b) ? -1 : ((a > b) ? 1 : 0));
            };

            function sortTextDesc(a, b) {
                if (table.config.sortLocaleCompare) return b.localeCompare(a);
                return ((b < a) ? -1 : ((b > a) ? 1 : 0));
            };

            function sortNumeric(a, b) {
                return a - b;
            };

            function sortNumericDesc(a, b) {
                return b - a;
            };

            function getCachedSortType(parsers, i) {
                return parsers[i].type;
            }; /* public methods */
            this.construct = function (settings) {
                return this.each(function () {
                    // if no thead or tbody quit.
                    if (!this.tHead || !this.tBodies) return;
                    // declare
                    var $this, $document, $headers, cache, config, shiftDown = 0,
                        sortOrder;
                    // new blank config object
                    this.config = {};
                    // merge and extend.
                    config = $.extend(this.config, $.tablesorter.defaults, settings);
                    // store common expression for speed
                    $this = $(this);
                    // save the settings where they read
                    $.data(this, "tablesorter", config);
                    // build headers
                    $headers = buildHeaders(this);
                    // try to auto detect column type, and store in tables config
                    this.config.parsers = buildParserCache(this, $headers);
                    // build the cache for the tbody cells
                    cache = buildCache(this);
                    // get the css class names, could be done else where.
                    var sortCSS = [config.cssDesc, config.cssAsc];
                    // fixate columns if the users supplies the fixedWidth option
                    fixColumnWidth(this);
                    // apply event handling to headers
                    // this is to big, perhaps break it out?
                    $headers.click(

                    function (e) {
                        var totalRows = ($this[0].tBodies[0] && $this[0].tBodies[0].rows.length) || 0;
                        if (!this.sortDisabled && totalRows > 0) {
                            // Only call sortStart if sorting is
                            // enabled.
                            $this.trigger("sortStart");
                            // store exp, for speed
                            var $cell = $(this);
                            // get current column index
                            var i = this.column;
                            // get current column sort order
                            this.order = this.count++ % 2;
                            // always sort on the locked order.
                            if(this.lockedOrder) this.order = this.lockedOrder;
                            
                            // user only whants to sort on one
                            // column
                            if (!e[config.sortMultiSortKey]) {
                                // flush the sort list
                                config.sortList = [];
                                if (config.sortForce != null) {
                                    var a = config.sortForce;
                                    for (var j = 0; j < a.length; j++) {
                                        if (a[j][0] != i) {
                                            config.sortList.push(a[j]);
                                        }
                                    }
                                }
                                // add column to sort list
                                config.sortList.push([i, this.order]);
                                // multi column sorting
                            } else {
                                // the user has clicked on an all
                                // ready sortet column.
                                if (isValueInArray(i, config.sortList)) {
                                    // revers the sorting direction
                                    // for all tables.
                                    for (var j = 0; j < config.sortList.length; j++) {
                                        var s = config.sortList[j],
                                            o = config.headerList[s[0]];
                                        if (s[0] == i) {
                                            o.count = s[1];
                                            o.count++;
                                            s[1] = o.count % 2;
                                        }
                                    }
                                } else {
                                    // add column to sort list array
                                    config.sortList.push([i, this.order]);
                                }
                            };
                            setTimeout(function () {
                                // set css for headers
                                setHeadersCss($this[0], $headers, config.sortList, sortCSS);
                                appendToTable(
                                    $this[0], multisort(
                                    $this[0], config.sortList, cache)
                                );
                            }, 1);
                            // stop normal event by returning false
                            return false;
                        }
                        // cancel selection
                    }).mousedown(function () {
                        if (config.cancelSelection) {
                            this.onselectstart = function () {
                                return false
                            };
                            return false;
                        }
                    });
                    // apply easy methods that trigger binded events
                    $this.bind("update", function () {
                        var me = this;
                        setTimeout(function () {
                            // rebuild parsers.
                            me.config.parsers = buildParserCache(
                            me, $headers);
                            // rebuild the cache map
                            cache = buildCache(me);
                        }, 1);
                    }).bind("updateCell", function (e, cell) {
                        var config = this.config;
                        // get position from the dom.
                        var pos = [(cell.parentNode.rowIndex - 1), cell.cellIndex];
                        // update cache
                        cache.normalized[pos[0]][pos[1]] = config.parsers[pos[1]].format(
                        getElementText(config, cell), cell);
                    }).bind("sorton", function (e, list) {
                        $(this).trigger("sortStart");
                        config.sortList = list;
                        // update and store the sortlist
                        var sortList = config.sortList;
                        // update header count index
                        updateHeaderSortCount(this, sortList);
                        // set css for headers
                        setHeadersCss(this, $headers, sortList, sortCSS);
                        // sort the table and append it to the dom
                        appendToTable(this, multisort(this, sortList, cache));
                    }).bind("appendCache", function () {
                        appendToTable(this, cache);
                    }).bind("applyWidgetId", function (e, id) {
                        getWidgetById(id).format(this);
                    }).bind("applyWidgets", function () {
                        // apply widgets
                        applyWidget(this);
                    });
                    if ($.metadata && ($(this).metadata() && $(this).metadata().sortlist)) {
                        config.sortList = $(this).metadata().sortlist;
                    }
                    // if user has supplied a sort list to constructor.
                    if (config.sortList.length > 0) {
                        $this.trigger("sorton", [config.sortList]);
                    }
                    // apply widgets
                    applyWidget(this);
                });
            };
            this.addParser = function (parser) {
                var l = parsers.length,
                    a = true;
                for (var i = 0; i < l; i++) {
                    if (parsers[i].id.toLowerCase() == parser.id.toLowerCase()) {
                        a = false;
                    }
                }
                if (a) {
                    parsers.push(parser);
                };
            };
            this.addWidget = function (widget) {
                widgets.push(widget);
            };
            this.formatFloat = function (s) {
                var i = parseFloat(s);
                return (isNaN(i)) ? 0 : i;
            };
            this.formatInt = function (s) {
                var i = parseInt(s);
                return (isNaN(i)) ? 0 : i;
            };
            this.isDigit = function (s, config) {
                // replace all an wanted chars and match.
                return /^[-+]?\d*$/.test($.trim(s.replace(/[,.']/g, '')));
            };
            this.clearTableBody = function (table) {
                if ($.browser.msie) {
                    function empty() {
                        while (this.firstChild)
                        this.removeChild(this.firstChild);
                    }
                    empty.apply(table.tBodies[0]);
                } else {
                    table.tBodies[0].innerHTML = "";
                }
            };
        }
    });

    // extend plugin scope
    $.fn.extend({
        tablesorter: $.tablesorter.construct
    });

    // make shortcut
    var ts = $.tablesorter;

    // add default parsers
    ts.addParser({
        id: "text",
        is: function (s) {
            return true;
        }, format: function (s) {
            return $.trim(s.toLocaleLowerCase());
        }, type: "text"
    });

    ts.addParser({
        id: "digit",
        is: function (s, table) {
            var c = table.config;
            return $.tablesorter.isDigit(s, c);
        }, format: function (s) {
            return $.tablesorter.formatFloat(s);
        }, type: "numeric"
    });

    ts.addParser({
        id: "currency",
        is: function (s) {
            return /^[£$€?.]/.test(s);
        }, format: function (s) {
            return $.tablesorter.formatFloat(s.replace(new RegExp(/[£$€]/g), ""));
        }, type: "numeric"
    });

    ts.addParser({
        id: "ipAddress",
        is: function (s) {
            return /^\d{2,3}[\.]\d{2,3}[\.]\d{2,3}[\.]\d{2,3}$/.test(s);
        }, format: function (s) {
            var a = s.split("."),
                r = "",
                l = a.length;
            for (var i = 0; i < l; i++) {
                var item = a[i];
                if (item.length == 2) {
                    r += "0" + item;
                } else {
                    r += item;
                }
            }
            return $.tablesorter.formatFloat(r);
        }, type: "numeric"
    });

    ts.addParser({
        id: "url",
        is: function (s) {
            return /^(https?|ftp|file):\/\/$/.test(s);
        }, format: function (s) {
            return jQuery.trim(s.replace(new RegExp(/(https?|ftp|file):\/\//), ''));
        }, type: "text"
    });

    ts.addParser({
        id: "isoDate",
        is: function (s) {
            return /^\d{4}[\/-]\d{1,2}[\/-]\d{1,2}$/.test(s);
        }, format: function (s) {
            return $.tablesorter.formatFloat((s != "") ? new Date(s.replace(
            new RegExp(/-/g), "/")).getTime() : "0");
        }, type: "numeric"
    });

    ts.addParser({
        id: "percent",
        is: function (s) {
            return /\%$/.test($.trim(s));
        }, format: function (s) {
            return $.tablesorter.formatFloat(s.replace(new RegExp(/%/g), ""));
        }, type: "numeric"
    });

    ts.addParser({
        id: "usLongDate",
        is: function (s) {
            return s.match(new RegExp(/^[A-Za-z]{3,10}\.? [0-9]{1,2}, ([0-9]{4}|'?[0-9]{2}) (([0-2]?[0-9]:[0-5][0-9])|([0-1]?[0-9]:[0-5][0-9]\s(AM|PM)))$/));
        }, format: function (s) {
            return $.tablesorter.formatFloat(new Date(s).getTime());
        }, type: "numeric"
    });

    ts.addParser({
        id: "shortDate",
        is: function (s) {
            return /\d{1,2}[\/\-]\d{1,2}[\/\-]\d{2,4}/.test(s);
        }, format: function (s, table) {
            var c = table.config;
            s = s.replace(/\-/g, "/");
            if (c.dateFormat == "us") {
                // reformat the string in ISO format
                s = s.replace(/(\d{1,2})[\/\-](\d{1,2})[\/\-](\d{4})/, "$3/$1/$2");
            } else if (c.dateFormat == "uk") {
                // reformat the string in ISO format
                s = s.replace(/(\d{1,2})[\/\-](\d{1,2})[\/\-](\d{4})/, "$3/$2/$1");
            } else if (c.dateFormat == "dd/mm/yy" || c.dateFormat == "dd-mm-yy") {
                s = s.replace(/(\d{1,2})[\/\-](\d{1,2})[\/\-](\d{2})/, "$1/$2/$3");
            }
            return $.tablesorter.formatFloat(new Date(s).getTime());
        }, type: "numeric"
    });
    ts.addParser({
        id: "time",
        is: function (s) {
            return /^(([0-2]?[0-9]:[0-5][0-9])|([0-1]?[0-9]:[0-5][0-9]\s(am|pm)))$/.test(s);
        }, format: function (s) {
            return $.tablesorter.formatFloat(new Date("2000/01/01 " + s).getTime());
        }, type: "numeric"
    });
    ts.addParser({
        id: "metadata",
        is: function (s) {
            return false;
        }, format: function (s, table, cell) {
            var c = table.config,
                p = (!c.parserMetadataName) ? 'sortValue' : c.parserMetadataName;
            return $(cell).metadata()[p];
        }, type: "numeric"
    });
    // add default widgets
    ts.addWidget({
        id: "zebra",
        format: function (table) {
            if (table.config.debug) {
                var time = new Date();
            }
            var $tr, row = -1,
                odd;
            // loop through the visible rows
            $("tr:visible", table.tBodies[0]).each(function (i) {
                $tr = $(this);
                // style children rows the same way the parent
                // row was styled
                if (!$tr.hasClass(table.config.cssChildRow)) row++;
                odd = (row % 2 == 0);
                $tr.removeClass(
                table.config.widgetZebra.css[odd ? 0 : 1]).addClass(
                table.config.widgetZebra.css[odd ? 1 : 0])
            });
            if (table.config.debug) {
                $.tablesorter.benchmark("Applying Zebra widget", time);
            }
        }
    });
})(jQuery);
/*
 * @File:        jquery.formLabels1.0.js
 * @Version:     1.0
 * @Author:      Andrei Zharau (www.o2v.net) - Senior UX Engineer at LibertyConcepts.com
 *
 * @Requires:    jQuery v1.4++ & jQueryUI v1.8++
 * @Usage:       $j.fn.formLabels()
 * @Options:     excludeElts - Excludes certain elements from being 'labelized'. Default: ''. Example:  $.fn.formLabels(excludeElts:'#email, .nolabel')
 *               refreshOnResize - whether or not refresh labels on window resize. Default: true
 *               safemode - if enabled the plugin runs in the safemode without using spans and animation. Default: false
 *               labelParent - parentContainer for your 'labels'. Default: 'form'
 *               semantic - puts label before input element. Default: true
 * @Methods:     $.fn.formLabels.refreshLabels() - refresh labels' position. Useful when input positioning has been changed due to DOM modifications, elements resizing, etc.
 * @Changelog
 *               1.0
 *                  + depending on which element is a parent element, script creates formLabels either as spans or labels (suggested by Alex Hall)
 *                  + new option 'semantic'. If true, then plugin renders label before input box (works only if labelParent:'form')
 *                  * 'labelParent' option supports any element on the page, but labelParent: 'form' puts labels into closest form. Default value has been changed to form. (suggested by Ferry Mulyono)
 *                  * changed logic for label's background detection
 *                  * fixed problem with blur event (formLabel did not disappear on auto-complete)
 *               1.0RC3
 *                  + fixed probmlems with input events in IE
 *                  + Green Hue Opacity bug in FF (thanks to Ionut Staicu)
 *               1.0RC2
 *                  + plugin supports a new refresh method
 *                  + new options: safemode and labelParent
 *                  + refresh on window resize is now an option
 *                  + slightly updated performance
 *
 * Copyright 2010 Liberty Concepts, all rights reserved.
 * 
 * This source file is free software, under either the GPL v2 license or a
 * BSD style license, as supplied with this software.
 *
 * This source file is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE. See the license files for details.
 */
;
(function($){
    var formLabels,
    opts,
    elts;

    $.fn.tagName = function() {
        return this.get(0).tagName;
    }
    formLabels = $.fn.formLabels = function(options) {
        opts = $.extend({
            excludeElts: '', //elements to be excluded
            refreshOnResize: false, //whether or not refresh labels on 'resize' event for window
            safemode:false, //enable safe mode without DOM modifications
            labelParent: 'form', //specifies a block that will store the form labels (body or form)
            semantic: true //if true, then plugin renders label before input box (works only if labelParent:'form')
        },options);
        var spanID = 0;
        elts = $("textarea, input[type='email'], input[type='text'], input[type='password']").not(opts.excludeElts).filter(":visible[title]");
        if (elts.length && !opts.safemode) {
            elts.each(function(){
                var $this = $(this);
                var spanBg = '';
                var $thisBC,        //background-color of input box
                    $thisBI,        //background-image of input box
                    labelParent,    //parent element
                    tagName;        //type of the element to be inserted
                if(this.value == '' && this.title != '') {
                    if (opts.labelParent == 'form') {
                        labelParent = $this.closest('form');
                        tagName = '<label/>';
                    }
                    else {
                        labelParent = $(opts.labelParent);
                        tagName = '<span/>';
                    }
                    
                    if (labelParent.css('position') == 'static') {
                            labelParent.css({'position':'relative'})
                    }
                    var label = this.title;
                    var parentPosition, offsetValue, myPosition;
                    var paddingValue = {
                        top: parseFloat($this.css("padding-top")) + 1,
                        left: (parseFloat($this.css("padding-left")) < 2) ? 2 : parseFloat($this.css("padding-left")) + 1
                    }
                    if ($this.tagName() == 'TEXTAREA') {
                        parentPosition = 'left top';
                        myPosition = 'left top';
                        offsetValue = paddingValue.left + ' ' + paddingValue.top;
                    }
                    else {
                        parentPosition = 'left center';
                        myPosition = 'left center';
                        offsetValue = paddingValue.left + ' 0'
                    }
                    if ($.browser.mozilla) {
                        $thisBC = $this.css("background-color");
                        $thisBI = $this.css("background-image");
                        if ($thisBI == 'none') {
                           if ($thisBC != '') {
                               spanBg = $thisBC
                           }
                           else {
                               spanBg = '#fff'
                           }
                        }
                    }
                    
                    var formLabel = $(tagName, {
                        css: {
                            'font-family'       : $this.css("font-family"),
                            'font-size'         : $this.css("font-size"),
                            'font-style'        : $this.css("font-style"),
                            'font-weight'       : $this.css("font-weight"),
                            'text-shadow'       : ($.support.opacity) ? $this.css("text-shadow") : '',
                            'line-height'       : $this.css("line-height"),
                            'background-color'  : spanBg,
                            'position'          : 'absolute',
                            'top'               : 0,
                            'left'              : 0,
                            'color'             : $this.css("color"),
                            '-moz-user-select'  : 'none',
                            '-webkit-user-select'  : 'none',
                            'cursor'            : 'text',
                            'z-index'           : '999'
                        },
                        id: "spanLabel" + spanID,
                        'for':($this.attr("id") == '') ? '' :  $this.attr("id"),
                        "class" : "fLabel",
                        html : label ,
                        click : function(){
                            $this.trigger('focus');
                            return false;
                        }
                    });
                    if (opts.semantic && opts.labelParent == 'form') {
                        $this.before(formLabel)
                    }
                    else {
                        formLabel.appendTo(labelParent)
                    }                    
                    formLabel.position({
                        my: myPosition,
                        at: parentPosition,
                        of: $this,
                        offset: offsetValue,
                        collision: 'none'
                    });
                    $this.data({
                        "spanID"    : "#spanLabel" + spanID,
                        "my"        : myPosition,
                        "at"        : parentPosition,
                        "offset"    : offsetValue
                    })
                }
                spanID++; //Increasing spanID by one
            });

            elts.bind('focus blur change cut paste input keyup', function(e){
                var LabelSpanID = $($(this).data("spanID"));
                if (e.type == 'focus') {
                    if(this.title != '' && this.value == '') {
                        LabelSpanID.stop().fadeTo(300,0.2);
                    }
                }
                if (e.type == 'blur') {
                    if(this.title != '') {
                        if (this.value == '') {
                            LabelSpanID.stop().fadeTo(300,1, function(){
                                if (!$.support.opacity) this.style.removeAttribute("filter");
                            })
                        }
                        else {
                            LabelSpanID.stop().fadeOut(300)
                        }
                    }
                }
                if (e.type == 'change' || e.type == 'cut' || e.type == 'paste' || e.type == 'input' || e.type == 'keyup') {
                    if(this.title != '') {
                        LabelSpanID.stop().fadeOut(300)
                    }
                    if(this.value == '') {
                        LabelSpanID.stop().fadeTo(100,0.2)
                    }
                }

            });
            if (opts.refreshOnResize) {
                $(window).bind("resize", function(){
                    formLabels.refreshLabels(elts);
                });
            }
        }
        if (elts.length && opts.safemode) {
            elts.val(function(i,v){
                if(v == '')
                    return this.title;
                else return v
            });
            elts.bind('focus blur', function(e){
                if (e.type == 'focus') {
                    if(this.value == this.title)
                        this.value = '';
                }
                else {
                    if(this.value == '')
                        this.value = this.title;
                }
            })
            $("input:image, button, input:submit").click(function(){
                $(this.form.elements).each(function(){
                    if(this.type =='email' || this.type =='text' || this.type =='textarea' || this.type =='password' ){
                        if(this.value == this.title && this.title != ''){
                            this.value='';
                        }
                    }
                });
            });
        }
    }

    //refresh method
    formLabels.refreshLabels = function() {
        elts.each(function(){
            var $this = $(this);
            var assocSpan = $($this.data("spanID"));
            assocSpan.position({
                my: $this.data("my"),
                at: $this.data("at"),
                of: $this,
                offset: $this.data("offset"),
                collision: 'none'
            });
        })
    }
    

})(jQuery);

/*
 * jQuery.fn.autoResize 1.14
 * --
 * https://github.com/jamespadolsey/jQuery.fn.autoResize
 * --
 * This program is free software. It comes without any warranty, to
 * the extent permitted by applicable law. You can redistribute it
 * and/or modify it under the terms of the Do What The Fuck You Want
 * To Public License, Version 2, as published by Sam Hocevar. See
 * http://sam.zoy.org/wtfpl/COPYING for more details. */ 

(function($){

    var uid = 'ar' + +new Date,

        defaults = autoResize.defaults = {
            onResize: function(){},
            onBeforeResize: function(){return 123},
            onAfterResize: function(){return 555},
            animate: {
                duration: 200,
                complete: function(){}
            },
            extraSpace: 50,
            minHeight: 'original',
            maxHeight: 500,
            minWidth: 'original',
            maxWidth: 500
        };

    autoResize.cloneCSSProperties = [
        'lineHeight', 'textDecoration', 'letterSpacing',
        'fontSize', 'fontFamily', 'fontStyle', 'fontWeight',
        'textTransform', 'textAlign', 'direction', 'wordSpacing', 'fontSizeAdjust',
        'paddingTop', 'paddingLeft', 'paddingBottom', 'paddingRight', 'width'
    ];

    autoResize.cloneCSSValues = {
        position: 'absolute',
        top: -9999,
        left: -9999,
        opacity: 0,
        overflow: 'hidden'
    };

    autoResize.resizableFilterSelector = [
        'textarea:not(textarea.' + uid + ')',
        'input:not(input[type])',
        'input[type=text]',
        'input[type=password]',
        'input[type=email]',
        'input[type=url]'
    ].join(',');

    autoResize.AutoResizer = AutoResizer;

    $.fn.autoResize = autoResize;

    function autoResize(config) {
        this.filter(autoResize.resizableFilterSelector).each(function(){
            new AutoResizer( $(this), config );
        });
        return this;
    }

    function AutoResizer(el, config) {

        if (el.data('AutoResizer')) {
            el.data('AutoResizer').destroy();
        }
        
        config = this.config = $.extend({}, autoResize.defaults, config);
        this.el = el;

        this.nodeName = el[0].nodeName.toLowerCase();

        this.originalHeight = el.height();
        this.previousScrollTop = null;

        this.value = el.val();

        if (config.maxWidth === 'original') config.maxWidth = el.width();
        if (config.minWidth === 'original') config.minWidth = el.width();
        if (config.maxHeight === 'original') config.maxHeight = el.height();
        if (config.minHeight === 'original') config.minHeight = el.height();

        if (this.nodeName === 'textarea') {
            el.css({
                resize: 'none',
                overflowY: 'hidden'
            });
        }

        el.data('AutoResizer', this);

        // Make sure onAfterResize is called upon animation completion
        config.animate.complete = (function(f){
            return function() {
                config.onAfterResize.call(el);
                return f.apply(this, arguments);
            };
        }(config.animate.complete));

        this.bind();

    }

    AutoResizer.prototype = {

        bind: function() {

            var check = $.proxy(function(){
                this.check();
                return true;
            }, this);

            this.unbind();

            this.el
                .bind('keyup.autoResize', check)
                //.bind('keydown.autoResize', check)
                .bind('change.autoResize', check)
                .bind('paste.autoResize', function() {
                    setTimeout(function() { check(); }, 0);
                });
            
            if (!this.el.is(':hidden')) {
                this.check(null, true);
            }

        },

        unbind: function() {
            this.el.unbind('.autoResize');
        },

        createClone: function() {

            var el = this.el,
                clone = this.nodeName === 'textarea' ? el.clone() : $('<span/>');

            this.clone = clone;

            $.each(autoResize.cloneCSSProperties, function(i, p){
                clone[0].style[p] = el.css(p);
            });

            clone
                .removeAttr('name')
                .removeAttr('id')
                .addClass(uid)
                .attr('tabIndex', -1)
                .css(autoResize.cloneCSSValues);

            if (this.nodeName === 'textarea') {
                clone.height('auto');
            } else {
                clone.width('auto').css({
                    whiteSpace: 'nowrap'
                });
            }

        },

        check: function(e, immediate) {

            if (!this.clone) {
        this.createClone();
        this.injectClone();
            }

            var config = this.config,
                clone = this.clone,
                el = this.el,
                value = el.val();

            // Do nothing if value hasn't changed
            if (value === this.prevValue) { return true; }
            this.prevValue = value;

            if (this.nodeName === 'input') {

                clone.text(value);

                // Calculate new width + whether to change
                var cloneWidth = clone.width(),
                    newWidth = (cloneWidth + config.extraSpace) >= config.minWidth ?
                        cloneWidth + config.extraSpace : config.minWidth,
                    currentWidth = el.width();

                newWidth = Math.min(newWidth, config.maxWidth);

                if (
                    (newWidth < currentWidth && newWidth >= config.minWidth) ||
                    (newWidth >= config.minWidth && newWidth <= config.maxWidth)
                ) {

                    config.onBeforeResize.call(el);
                    config.onResize.call(el);

                    el.scrollLeft(0);

                    if (config.animate && !immediate) {
                        el.stop(1,1).animate({
                            width: newWidth
                        }, config.animate);
                    } else {
                        el.width(newWidth);
                        config.onAfterResize.call(el);
                    }

                }

                return;

            }

            // TEXTAREA
            
            clone.width(el.width()).height(0).val(value).scrollTop(10000);
            
            var scrollTop = clone[0].scrollTop;
                
            // Don't do anything if scrollTop hasen't changed:
            if (this.previousScrollTop === scrollTop) {
                return;
            }

            this.previousScrollTop = scrollTop;
            
            if (scrollTop + config.extraSpace >= config.maxHeight) {
                el.css('overflowY', '');
                scrollTop = config.maxHeight;
                immediate = true;
            } else if (scrollTop <= config.minHeight) {
                scrollTop = config.minHeight;
            } else {
                el.css('overflowY', 'hidden');
                scrollTop += config.extraSpace;
            }

            config.onBeforeResize.call(el);
            config.onResize.call(el);

            // Either animate or directly apply height:
            if (config.animate && !immediate) {
                el.stop(1,1).animate({
                    height: scrollTop
                }, config.animate);
            } else {
                el.height(scrollTop);
                config.onAfterResize.call(el);
            }

        },

        destroy: function() {
            this.unbind();
            this.el.removeData('AutoResizer');
            this.clone.remove();
            delete this.el;
            delete this.clone;
        },

        injectClone: function() {
            (
                autoResize.cloneContainer ||
                (autoResize.cloneContainer = $('<arclones/>').appendTo('body'))
            ).append(this.clone);
        }

    };
    
})(jQuery);

/*

    jQuery selectBox (version 1.0.7)

        A cosmetic, styleable replacement for SELECT elements.

        Homepage:   http://abeautifulsite.net/blog/2011/01/jquery-selectbox-plugin/
        Demo page:  http://labs.abeautifulsite.net/projects/js/jquery/selectBox/

        Copyright 2011 Cory LaViska for A Beautiful Site, LLC.

    Features:

        - Supports OPTGROUPS
        - Supports standard dropdown controls
        - Supports multi-select controls (i.e. multiple="multiple")
        - Supports inline controls (i.e. size="5")
        - Fully accessible via keyboard
        - Shift + click (or shift + enter) to select a range of options in multi-select controls
        - Type to search when the control has focus
        - Auto-height based on the size attribute (to use, omit the height property in your CSS!)
        - Tested in IE7-IE9, Firefox 3-4, recent webkit browsers, and Opera


    License:

        Licensed under both the MIT license and the GNU GPLv2 (same as jQuery: http://jquery.org/license)


    Usage:

        Link to the JS file:

            <script src="jquery.selectbox.min.js" type="text/javascript"></script>

        Add the CSS file (or append contents to your own stylesheet):

            <link href="jquery.selectbox.min.css" rel="stylesheet" type="text/css" />

        To create:

            $("SELECT").selectBox([settings]);


    Settings:

        To specify settings, use this syntax: $("SELECT").selectBox('settings', { settingName: value, ... });

            menuTransition: ['default', 'slide', 'fade'] - the show/hide transition for dropdown menus
            menuSpeed: [integer, 'slow', 'normal', 'fast'] - the show/hide transition speed
            loopOptions: [boolean] - flag to allow arrow keys to loop through options


    Methods:

        To call a method use this syntax: $("SELECT").selectBox('methodName', [options]);

            create - Creates the control (default method)
            destroy - Destroys the selectBox control and reverts back to the original form control
            disable - Disables the control (i.e. disabled="disabled")
            enable - Enables the control
            value - if passed with a value, sets the control to that value; otherwise returns the current value
            options - pass in either a string of HTML or a JSON object to replace the existing options
            control - returns the selectBox control element (an anchor tag) for working with directly


    Events:

        Events are fired on the original select element. You can bind events like this:

            $("SELECT").selectBox().change( function() { alert( $(this).val() ); } );

            focus - Fired when the control gains focus
            blur - Fired when the control loses focus
            change - Fired when the value of a control changes


    Change Log:

        v1.0.0 (2011-04-03) - Complete rewrite with added support for inline and multi-select controls
        v1.0.1 (2011-04-04) - Fixed options method so it doesn't destroy/recreate the control when called.
                            - Added a check for iOS devices (their native controls are much better for
                              touch-based devices; you can still use selectBox API methods for theme)
                            - Fixed issue where IE window would lose focus on XP
                            - Fixed premature selection issue in Webkit browsers
        v1.0.2 (2011-04-13) - Fixed auto-height for inline controls when control is invisible on load
                            - Removed auto-width for dropdown and inline controls; now relies 100% on CSS
                              for setting the width
                               - Added 'control' method for working directly with the selectBox control
        v1.0.3 (2011-04-22) - Fixed bug in value method that errored if the control didn't exist
        v1.0.4 (2011-04-22) - Fixed bug where controls without any options would render with incorrect heights
        v1.0.5 (2011-04-22) - Removed 'tick' image in lieu of background colors to indicate selection
                            - Clicking no longer toggles selected/unselected in multi-selects; use CTRL/CMD and
                              SHIFT like in normal browser controls
                            - Fixed bug where inline controls would not receive focus unless tabbed into
        v1.0.6 (2011-04-29) - Fixed bug where inline controls could be "dragged" when selecting an empty area
        v1.0.7 (2011-05-18) - Expanded iOS check to include Android devices as well
                            - Added autoWidth option; set to false on init to use CSS widths for dropdown menus

    Known Issues:

        - The blur and focus callbacks are not very reliable in IE7. The change callback works fine.

*/
if(jQuery) (function($) {

    $.extend($.fn, {

        selectBox: function(method, data) {

            var typeTimer, typeSearch = '';


            //
            // Private methods
            //


            var init = function(select, data) {

                // Disable for iOS devices (their native controls are more suitable for a touch device)
                if( navigator.userAgent.match(/iPad|iPhone|Android/i) ) return false;

                // Element must be a select control
                if( select.tagName.toLowerCase() !== 'select' ) return false;

                select = $(select);
                if( select.data('selectBox-control') ) return false;

                var control = $('<a class="selectBox" />'),
                    inline = select.attr('multiple') || parseInt(select.attr('size')) > 1;

                var settings = data || {};
                if( settings.autoWidth === undefined ) settings.autoWidth = true;

                // Inherit class names, style, and title attributes
                control
                    .addClass(select.attr('class'))
                    .attr('style', select.attr('style') || '')
                    .attr('title', select.attr('title') || '')
                    .attr('tabindex', parseInt(select.attr('tabindex')))
                    .css('display', 'inline-block')
                    .bind('focus.selectBox', function() {
                        if( this !== document.activeElement ) $(document.activeElement).blur();
                        if( control.hasClass('selectBox-active') ) return;
                        control.addClass('selectBox-active');
                        select.trigger('focus');
                    })
                    .bind('blur.selectBox', function() {
                        if( !control.hasClass('selectBox-active') ) return;
                        control.removeClass('selectBox-active');
                        select.trigger('blur');
                    });

                if( select.attr('disabled') ) control.addClass('selectBox-disabled');

                // Generate control
                if( inline ) {

                    //
                    // Inline controls
                    //
                    var options = getOptions(select, 'inline');

                    control
                        .append(options)
                        .data('selectBox-options', options)
                        .addClass('selectBox-inline selectBox-menuShowing')
                        .bind('keydown.selectBox', function(event) {
                            handleKeyDown(select, event);
                        })
                        .bind('keypress.selectBox', function(event) {
                            handleKeyPress(select, event);
                        })
                        .bind('mousedown.selectBox', function(event) {
                            if( $(event.target).is('A.selectBox-inline') ) event.preventDefault();
                            if( !control.hasClass('selectBox-focus') ) control.focus();
                        })
                        .insertAfter(select);

                    // Auto-height based on size attribute
                    if( !select[0].style.height ) {

                        var size = select.attr('size') ? parseInt(select.attr('size')) : 5;

                        // Draw a dummy control off-screen, measure, and remove it
                        var tmp = control
                            .clone()
                            .removeAttr('id')
                            .css({
                                position: 'absolute',
                                top: '-9999em'
                            })
                            .show()
                            .appendTo('body');
                        tmp.find('.selectBox-options').html('<li><a>\u00A0</a></li>');
                        optionHeight = parseInt(tmp.find('.selectBox-options A:first').html('&nbsp;').outerHeight());
                        tmp.remove();

                        control.height(optionHeight * size);

                    }

                    disableSelection(control);

                } else {

                    //
                    // Dropdown controls
                    //

                    var label = $('<span class="selectBox-label" />'),
                        arrow = $('<span class="selectBox-arrow" />');

                    label.text( $(select).find('OPTION:selected').text() || '\u00A0' );

                    var options = getOptions(select, 'dropdown');
                    options.appendTo('BODY');

                    control
                        .data('selectBox-options', options)
                        .addClass('selectBox-dropdown')
                        .append(label)
                        .append(arrow)
                        .bind('mousedown.selectBox', function(event) {
                            if( control.hasClass('selectBox-menuShowing') ) {
                                hideMenus();
                            } else {
                                event.stopPropagation();
                                // Webkit fix to prevent premature selection of options
                                options.data('selectBox-down-at-x', event.screenX).data('selectBox-down-at-y', event.screenY);
                                showMenu(select);
                            }
                        })
                        .bind('keydown.selectBox', function(event) {
                            handleKeyDown(select, event);
                        })
                        .bind('keypress.selectBox', function(event) {
                            handleKeyPress(select, event);
                        })
                        .insertAfter(select);
                    
                    disableSelection(control);

                }

                // Store data for later use and show the control
                select
                    .addClass('selectBox')
                    .data('selectBox-control', control)
                    .data('selectBox-settings', settings)
                    .hide();

                if (settings.autoWidth) setAutoWidth(select);
                
            };


            var getOptions = function(select, type) {
                var options;

                switch( type ) {

                    case 'inline':


                        options = $('<ul class="selectBox-options" />');

                        if( select.find('OPTGROUP').length ) {

                            select.find('OPTGROUP').each( function() {

                                var optgroup = $('<li class="selectBox-optgroup" />');
                                optgroup.text($(this).attr('label'));
                                options.append(optgroup);

                                generateOptions($(this).find('OPTION'), options);

                            });

                        } else {
                            generateOptions(select.find('OPTION'), options);
                        }

                        options
                            .find('A')
                                .bind('mouseover.selectBox', function(event) {
                                    addHover(select, $(this).parent());
                                })
                                .bind('mouseout.selectBox', function(event) {
                                    removeHover(select, $(this).parent());
                                })
                                .bind('mousedown.selectBox', function(event) {
                                    event.preventDefault(); // Prevent options from being "dragged"
                                    if( !select.selectBox('control').hasClass('selectBox-active') ) select.selectBox('control').focus();
                                })
                                .bind('mouseup.selectBox', function(event) {
                                    hideMenus();
                                    selectOption(select, $(this).parent(), event);
                                });

                        disableSelection(options);

                        return options;

                    case 'dropdown':
                        options = $('<ul class="selectBox-dropdown-menu selectBox-options" />');

                        if( select.find('OPTGROUP').length ) {

                            select.find('OPTGROUP').each( function() {

                                var optgroup = $('<li class="selectBox-optgroup" />');
                                optgroup.text($(this).attr('label'));
                                options.append(optgroup);
                                generateOptions($(this).find('OPTION'), options);

                            });

                        } else {

                            if( select.find('OPTION').length > 0 ) {
                                generateOptions(select.find('OPTION'), options);
                            } else {
                                options.append('<li>\u00A0</li>');
                            }

                        }

                        options
                            .data('selectBox-select', select)
                            .css('display', 'none')
                            .appendTo('BODY')
                            .find('A')
                                .bind('mousedown.selectBox', function(event) {
                                    event.preventDefault(); // Prevent options from being "dragged"
                                    if( event.screenX === options.data('selectBox-down-at-x') && event.screenY === options.data('selectBox-down-at-y') ) {
                                        options.removeData('selectBox-down-at-x').removeData('selectBox-down-at-y');
                                        hideMenus();
                                    }
                                })
                                .bind('mouseup.selectBox', function(event) {
                                    if( event.screenX === options.data('selectBox-down-at-x') && event.screenY === options.data('selectBox-down-at-y') ) {
                                        return;
                                    } else {
                                        options.removeData('selectBox-down-at-x').removeData('selectBox-down-at-y');
                                    }
                                    selectOption(select, $(this).parent());
                                    hideMenus();
                                }).bind('mouseover.selectBox', function(event) {
                                    addHover(select, $(this).parent());
                                })
                                .bind('mouseout.selectBox', function(event) {
                                    removeHover(select, $(this).parent());
                                });

                        disableSelection(options);

                        return options;

                }

            };


            var destroy = function(select) {

                select = $(select);

                var control = select.data('selectBox-control');
                if( !control ) return;
                var options = control.data('selectBox-options');

                options.remove();
                control.remove();
                select
                    .removeClass('selectBox')
                    .removeData('selectBox-control')
                    .removeData('selectBox-settings')
                    .show();

            };


            var showMenu = function(select) {

                select = $(select);
                var control = select.data('selectBox-control'),
                    settings = select.data('selectBox-settings'),
                    options = control.data('selectBox-options');
                if( control.hasClass('selectBox-disabled') ) return false;

                hideMenus();

                // Auto-width
                /*
                if( settings.autoWidth ) options.css('width', control.innerWidth());
                else if(options.innerWidth() < control.innerWidth()) {
                    options.css('width', control.innerWidth() - parseInt(options.css('padding-left')) - parseInt(options.css('padding-right')))
                }*/

                var borderBottomWidth = isNaN(control.css('borderBottomWidth')) ? 0 : parseInt(control.css('borderBottomWidth'));
                // Menu position
                options.css({
                    top: control.offset().top + control.outerHeight() - borderBottomWidth,
                    left: control.offset().left
                });

                // Show menu
                switch( settings.menuTransition ) {

                    case 'fade':
                        options.fadeIn(settings.menuSpeed);
                        break;

                    case 'slide':
                        options.slideDown(settings.menuSpeed);
                        break;

                    default:
                        options.show(settings.menuSpeed);
                        break;

                }

                // Center on selected option
                var li = options.find('.selectBox-selected:first');
                keepOptionInView(select, li, true);
                addHover(select, li);

                control.addClass('selectBox-menuShowing');

                $(document).bind('mousedown.selectBox', function(event) {
                    if( $(event.target).parents().andSelf().hasClass('selectBox-options') ) return;
                    hideMenus();
                });

            };


            var hideMenus = function() {

                if( $(".selectBox-dropdown-menu").length === 0 ) return;
                $(document).unbind('mousedown.selectBox');

                $(".selectBox-dropdown-menu").each( function() {

                    var options = $(this),
                        select = options.data('selectBox-select'),
                        control = select.data('selectBox-control'),
                        settings = select.data('selectBox-settings');

                    switch( settings.menuTransition ) {

                        case 'fade':
                            options.fadeOut(settings.menuSpeed);
                            break;

                        case 'slide':
                            options.slideUp(settings.menuSpeed);
                            break;

                        default:
                            options.hide(settings.menuSpeed);
                            break;

                    }

                    control.removeClass('selectBox-menuShowing');

                });

            };


            var selectOption = function(select, li, event) {

                select = $(select);
                li = $(li);
                var control = select.data('selectBox-control'),
                    settings = select.data('selectBox-settings');

                if( control.hasClass('selectBox-disabled') ) return false;
                if( li.length === 0 || li.hasClass('selectBox-disabled') ) return false;

                if( select.attr('multiple') ) {

                    // If event.shiftKey is true, this will select all options between li and the last li selected
                    if( event.shiftKey && control.data('selectBox-last-selected') ) {

                        li.toggleClass('selectBox-selected');

                        var affectedOptions;
                        if( li.index() > control.data('selectBox-last-selected').index() ) {
                            affectedOptions = li.siblings().slice(control.data('selectBox-last-selected').index(), li.index());
                        } else {
                            affectedOptions = li.siblings().slice(li.index(), control.data('selectBox-last-selected').index());
                        }

                        affectedOptions = affectedOptions.not('.selectBox-optgroup, .selectBox-disabled');

                        if( li.hasClass('selectBox-selected') ) {
                            affectedOptions.addClass('selectBox-selected');
                        } else {
                            affectedOptions.removeClass('selectBox-selected');
                        }

                    } else if( event.metaKey ) {
                        li.toggleClass('selectBox-selected');
                    } else {
                        li.siblings().removeClass('selectBox-selected');
                        li.addClass('selectBox-selected');
                    }

                } else {
                    li.siblings().removeClass('selectBox-selected');
                    li.addClass('selectBox-selected');
                }

                if( control.hasClass('selectBox-dropdown') ) {
                    control.find('.selectBox-label').text(li.text());
                }

                // Update original control's value
                var i = 0, selection = [];
                if( select.attr('multiple') ) {
                    control.find('.selectBox-selected A').each( function() {
                        selection[i++] = $(this).attr('rel');
                    });
                } else {
                    selection = li.find('A').attr('rel');
                }

                // Remember most recently selected item
                control.data('selectBox-last-selected', li);

                // Change callback
                if( select.val() !== selection ) {
                    select.val(selection);
                    select.trigger('change');
                }

                return true;

            };


            var addHover = function(select, li) {
                select = $(select);
                li = $(li);
                var control = select.data('selectBox-control'),
                    options = control.data('selectBox-options');

                options.find('.selectBox-hover').removeClass('selectBox-hover');
                li.addClass('selectBox-hover');
            };


            var removeHover = function(select, li) {
                select = $(select);
                li = $(li);
                var control = select.data('selectBox-control'),
                    options = control.data('selectBox-options');
                options.find('.selectBox-hover').removeClass('selectBox-hover');
            };


            var keepOptionInView = function(select, li, center) {

                if( !li || li.length === 0 ) return;

                select = $(select);
                var control = select.data('selectBox-control'),
                    options = control.data('selectBox-options'),
                    scrollBox = control.hasClass('selectBox-dropdown') ? options : options.parent(),
                    top = parseInt(li.offset().top - scrollBox.position().top),
                    bottom = parseInt(top + li.outerHeight());

                if( center ) {
                    scrollBox.scrollTop( li.offset().top - scrollBox.offset().top + scrollBox.scrollTop() - (scrollBox.height() / 2) );
                } else {
                    if( top < 0 ) {
                        scrollBox.scrollTop( li.offset().top - scrollBox.offset().top + scrollBox.scrollTop() );
                    }
                    if( bottom > scrollBox.height() ) {
                        scrollBox.scrollTop( (li.offset().top + li.outerHeight()) - scrollBox.offset().top + scrollBox.scrollTop() - scrollBox.height() );
                    }
                }

            };


            var handleKeyDown = function(select, event) {

                //
                // Handles open/close and arrow key functionality
                //

                select = $(select);
                var control = select.data('selectBox-control'),
                    options = control.data('selectBox-options'),
                    settings = select.data('selectBox-settings'),
                    totalOptions = 0,
                    i = 0;

                if( control.hasClass('selectBox-disabled') ) return;

                switch( event.keyCode ) {

                    case 8: // backspace
                        event.preventDefault();
                        typeSearch = '';
                        break;

                    case 9: // tab
                    case 27: // esc
                        hideMenus();
                        removeHover(select);
                        break;

                    case 13: // enter
                        if( control.hasClass('selectBox-menuShowing') ) {
                            selectOption(select, options.find('LI.selectBox-hover:first'), event);
                            if( control.hasClass('selectBox-dropdown') ) hideMenus();
                        } else {
                            showMenu(select);
                        }
                        break;

                    case 38: // up
                    case 37: // left

                        event.preventDefault();

                        if( control.hasClass('selectBox-menuShowing') ) {

                            var prev = options.find('.selectBox-hover').prev('LI');
                            totalOptions = options.find('LI:not(.selectBox-optgroup)').length;
                            i = 0;

                            while( prev.length === 0 || prev.hasClass('selectBox-disabled') || prev.hasClass('selectBox-optgroup') ) {
                                prev = prev.prev('LI');
                                if( prev.length === 0 ) {
                                    if (settings.loopOptions) {
                                        prev = options.find('LI:last');
                                    } else {
                                        prev = options.find('LI:first');
                                    }
                                }
                                if( ++i >= totalOptions ) break;
                            }

                            addHover(select, prev);
                            selectOption(select, prev, event);
                            keepOptionInView(select, prev);

                        } else {
                            showMenu(select);
                        }

                        break;

                    case 40: // down
                    case 39: // right

                        event.preventDefault();

                        if( control.hasClass('selectBox-menuShowing') ) {

                            var next = options.find('.selectBox-hover').next('LI');
                            totalOptions = options.find('LI:not(.selectBox-optgroup)').length;
                            i = 0;

                            while( next.length === 0 || next.hasClass('selectBox-disabled') || next.hasClass('selectBox-optgroup') ) {
                                next = next.next('LI');
                                if( next.length === 0 ) {
                                    if (settings.loopOptions) {
                                        next = options.find('LI:first');
                                    } else {
                                        next = options.find('LI:last');
                                    }
                                }
                                if( ++i >= totalOptions ) break;
                            }

                            addHover(select, next);
                            selectOption(select, next, event);
                            keepOptionInView(select, next);

                        } else {
                            showMenu(select);
                        }

                        break;

                }

            };


            var handleKeyPress = function(select, event) {

                //
                // Handles type-to-find functionality
                //

                select = $(select);
                var control = select.data('selectBox-control'),
                    options = control.data('selectBox-options');

                if( control.hasClass('selectBox-disabled') ) return;

                switch( event.keyCode ) {

                    case 9: // tab
                    case 27: // esc
                    case 13: // enter
                    case 38: // up
                    case 37: // left
                    case 40: // down
                    case 39: // right
                        // Don't interfere with the keydown event!
                        break;

                    default: // Type to find

                        if( !control.hasClass('selectBox-menuShowing') ) showMenu(select);

                        event.preventDefault();

                        clearTimeout(typeTimer);
                        typeSearch += String.fromCharCode(event.charCode || event.keyCode);

                        options.find('A').each( function() {
                            if( $(this).text().substr(0, typeSearch.length).toLowerCase() === typeSearch.toLowerCase() ) {
                                addHover(select, $(this).parent());
                                keepOptionInView(select, $(this).parent());
                                return false;
                            }
                        });

                        // Clear after a brief pause
                        typeTimer = setTimeout( function() { typeSearch = ''; }, 1000);

                        break;

                }

            };


            var enable = function(select) {
                select = $(select);
                select.attr('disabled', false);
                var control = select.data('selectBox-control');
                if( !control ) return;
                control.removeClass('selectBox-disabled');
            };


            var disable = function(select) {
                select = $(select);
                select.attr('disabled', true);
                var control = select.data('selectBox-control');
                if( !control ) return;
                control.addClass('selectBox-disabled');
            };


            var setValue = function(select, value) {
                select = $(select);
                select.val(value);
                value = select.val();
                var control = select.data('selectBox-control');
                if( !control ) return;
                var settings = select.data('selectBox-settings'),
                    options = control.data('selectBox-options');

                // Update label
                control.find('.selectBox-label').text( $(select).find('OPTION:selected').text() || '\u00A0' );

                // Update control values
                options.find('.selectBox-selected').removeClass('selectBox-selected');
                options.find('A').each( function() {
                    if( typeof(value) === 'object' ) {
                        for( var i = 0; i < value.length; i++ ) {
                            if( $(this).attr('rel') == value[i] ) {
                                $(this).parent().addClass('selectBox-selected');
                            }
                        }
                    } else {
                        if( $(this).attr('rel') == value ) {
                            $(this).parent().addClass('selectBox-selected');
                        }
                    }
                });

                if( settings.change ) settings.change.call(select);

            };


            var setOptions = function(select, options) {

                select = $(select);
                var control = select.data('selectBox-control'),
                    settings = select.data('selectBox-settings');

                switch( typeof(data) ) {

                    case 'string':
                        select.html(data);
                        break;

                    case 'object':
                        select.html('');
                        for( var i in data ) {
                            if( data[i] === null ) continue;
                            if( typeof(data[i]) === 'object' ) {
                                var optgroup = $('<optgroup label="' + i + '" />');
                                for( var j in data[i] ) {
                                    optgroup.append('<option value="' + j + '">' + data[i][j] + '</option>');
                                }
                                select.append(optgroup);
                            } else {
                                var option = $('<option value="' + i + '">' + data[i] + '</option>');
                                select.append(option);
                            }
                        }
                        break;

                }

                if( !control ) return;

                // Remove old options
                control.data('selectBox-options').remove();

                // Generate new options
                var type = control.hasClass('selectBox-dropdown') ? 'dropdown' : 'inline',
                    options = getOptions(select, type);
                control.data('selectBox-options', options);

                switch( type ) {
                    case 'inline':
                        control.append(options);
                        break;
                    case 'dropdown':
                        control.find('.selectBox-label').text( $(select).find('OPTION:selected').text() || '\u00A0' );
                        $("BODY").append(options);
                        break;
                }
                
                setAutoWidth(select);

            };

            
            var appendOptions = function(select, options) {
                
                var currentOptions = {};
                
                $(select).find('option').each(function() {
                    var option = $(this);
                    currentOptions[option.val()] = option.text();
                });
                
                data = $.extend(currentOptions, options);
                setOptions(select, data);
            };


            var disableSelection = function(selector) {
                $(selector)
                    .css('MozUserSelect', 'none')
                    .bind('selectstart', function(event) {
                        event.preventDefault();
                    });
            };

            var generateOptions = function(originalOptions, options){
                originalOptions.each(function(){
                    var self = $(this);
                    var li = $('<li />'),
                    a = $('<a />');
                    li.addClass( self.attr('class') );
                    li.data( self.data() );
                    a.attr('rel', self.val()).text( self.text() );
                    li.append(a);
                    if( self.attr('disabled') ) li.addClass('selectBox-disabled');
                    if( self.attr('selected') ) li.addClass('selectBox-selected');
                    options.append(li);
                })
            };
            
            var setAutoWidth = function(select) {
                
                select = $(select);

                var control = select.data('selectBox-control');
                if( !control ) return;
                var options = control.data('selectBox-options');

                control.width(options.innerWidth() - parseInt(control.css("marginLeft")) - parseInt(control.css("paddingLeft")) - parseInt(control.css("marginRight")) - parseInt(control.css("paddingRight")));
                
            }
            
            var show = function(select) {

                select = $(select);

                var control = select.data('selectBox-control');
                if( !control ) return;
                
                control.show();

            };
            
            var hide = function(select) {

                select = $(select);

                var control = select.data('selectBox-control');
                if( !control ) return;
                
                control.hide();

            };


            //
            // Public methods
            //


            switch( method ) {

                case 'control':
                    return $(this).data('selectBox-control');
                    break;

                case 'settings':
                    if( !data ) return $(this).data('selectBox-settings');
                    $(this).each( function() {
                        $(this).data('selectBox-settings', $.extend(true, $(this).data('selectBox-settings'), data));
                    });
                    break;

                case 'options':
                    $(this).each( function() {
                        setOptions(this, data);
                    });
                    break;
                    
                case 'appendOptions':
                    $(this).each( function() {
                        appendOptions(this, data);
                    });
                    break;

                case 'value':
                    // Empty string is a valid value
                    if( data === undefined ) return $(this).val();
                    $(this).each( function() {
                        setValue(this, data);
                    });
                    break;

                case 'enable':
                    $(this).each( function() {
                        enable(this);
                    });
                    break;

                case 'disable':
                    $(this).each( function() {
                        disable(this);
                    });
                    break;

                case 'destroy':
                    $(this).each( function() {
                        destroy(this);
                    });
                    break;
                    
                case 'show':
                    $(this).each( function() {
                        show(this);
                    });
                    break;
                    
                case 'hide':
                    $(this).each( function() {
                        hide(this);
                    });
                    break;

                default:
                    $(this).each( function() {
                        init(this, method);
                    });
                    break;

            }

            return $(this);

        }

    });

})(jQuery);

/**
* hoverIntent is similar to jQuery's built-in "hover" function except that
* instead of firing the onMouseOver event immediately, hoverIntent checks
* to see if the user's mouse has slowed down (beneath the sensitivity
* threshold) before firing the onMouseOver event.
* 
* hoverIntent r6 // 2011.02.26 // jQuery 1.5.1+
* <http://cherne.net/brian/resources/jquery.hoverIntent.html>
* 
* hoverIntent is currently available for use in all personal or commercial 
* projects under both MIT and GPL licenses. This means that you can choose 
* the license that best suits your project, and use it accordingly.
* 
* // basic usage (just like .hover) receives onMouseOver and onMouseOut functions
* $("ul li").hoverIntent( showNav , hideNav );
* 
* // advanced usage receives configuration object only
* $("ul li").hoverIntent({
*   sensitivity: 7, // number = sensitivity threshold (must be 1 or higher)
*   interval: 100,   // number = milliseconds of polling interval
*   over: showNav,  // function = onMouseOver callback (required)
*   timeout: 0,   // number = milliseconds delay before onMouseOut function call
*   out: hideNav    // function = onMouseOut callback (required)
* });
* 
* @param  f  onMouseOver function || An object with configuration options
* @param  g  onMouseOut function  || Nothing (use configuration options object)
* @author    Brian Cherne brian(at)cherne(dot)net
*/
(function($) {
    $.fn.hoverIntent = function(f,g) {
        // default configuration options
        var cfg = {
            sensitivity: 7,
            interval: 100,
            timeout: 0
        };
        // override configuration options with user supplied object
        cfg = $.extend(cfg, g ? { over: f, out: g } : f );

        // instantiate variables
        // cX, cY = current X and Y position of mouse, updated by mousemove event
        // pX, pY = previous X and Y position of mouse, set by mouseover and polling interval
        var cX, cY, pX, pY;

        // A private function for getting mouse position
        var track = function(ev) {
            cX = ev.pageX;
            cY = ev.pageY;
        };

        // A private function for comparing current and previous mouse position
        var compare = function(ev,ob) {
            ob.hoverIntent_t = clearTimeout(ob.hoverIntent_t);
            // compare mouse positions to see if they've crossed the threshold
            if ( ( Math.abs(pX-cX) + Math.abs(pY-cY) ) < cfg.sensitivity ) {
                $(ob).unbind("mousemove",track);
                // set hoverIntent state to true (so mouseOut can be called)
                ob.hoverIntent_s = 1;
                return cfg.over.apply(ob,[ev]);
            } else {
                // set previous coordinates for next time
                pX = cX; pY = cY;
                // use self-calling timeout, guarantees intervals are spaced out properly (avoids JavaScript timer bugs)
                ob.hoverIntent_t = setTimeout( function(){compare(ev, ob);} , cfg.interval );
            }
        };

        // A private function for delaying the mouseOut function
        var delay = function(ev,ob) {
            ob.hoverIntent_t = clearTimeout(ob.hoverIntent_t);
            ob.hoverIntent_s = 0;
            return cfg.out.apply(ob,[ev]);
        };

        // A private function for handling mouse 'hovering'
        var handleHover = function(e) {
            // copy objects to be passed into t (required for event object to be passed in IE)
            var ev = jQuery.extend({},e);
            var ob = this;

            // cancel hoverIntent timer if it exists
            if (ob.hoverIntent_t) { ob.hoverIntent_t = clearTimeout(ob.hoverIntent_t); }

            // if e.type == "mouseenter"
            if (e.type == "mouseenter") {
                // set "previous" X and Y position based on initial entry point
                pX = ev.pageX; pY = ev.pageY;
                // update "current" X and Y position based on mousemove
                $(ob).bind("mousemove",track);
                // start polling interval (self-calling timeout) to compare mouse coordinates over time
                if (ob.hoverIntent_s != 1) { ob.hoverIntent_t = setTimeout( function(){compare(ev,ob);} , cfg.interval );}

            // else e.type == "mouseleave"
            } else {
                // unbind expensive mousemove event
                $(ob).unbind("mousemove",track);
                // if hoverIntent state is true, then call the mouseOut function after the specified delay
                if (ob.hoverIntent_s == 1) { ob.hoverIntent_t = setTimeout( function(){delay(ev,ob);} , cfg.timeout );}
            }
        };

        // bind the function to the two event listeners
        return this.bind('mouseenter',handleHover).bind('mouseleave',handleHover);
    };
})(jQuery);

/*
 * ----------------------------- JSTORAGE -------------------------------------
 * Simple local storage wrapper to save data on the browser side, supporting
 * all major browsers - IE6+, Firefox2+, Safari4+, Chrome4+ and Opera 10.5+
 *
 * Copyright (c) 2010 Andris Reinman, andris.reinman@gmail.com
 * Project homepage: www.jstorage.info
 *
 * Licensed under MIT-style license:
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

/**
 * $.jStorage
 * 
 * USAGE:
 *
 * jStorage requires Prototype, MooTools or jQuery! If jQuery is used, then
 * jQuery-JSON (http://code.google.com/p/jquery-json/) is also needed.
 * (jQuery-JSON needs to be loaded BEFORE jStorage!)
 *
 * Methods:
 *
 * -set(key, value)
 * $.jStorage.set(key, value) -> saves a value
 *
 * -get(key[, default])
 * value = $.jStorage.get(key [, default]) ->
 *    retrieves value if key exists, or default if it doesn't
 *
 * -deleteKey(key)
 * $.jStorage.deleteKey(key) -> removes a key from the storage
 *
 * -flush()
 * $.jStorage.flush() -> clears the cache
 * 
 * -storageObj()
 * $.jStorage.storageObj() -> returns a read-ony copy of the actual storage
 * 
 * -storageSize()
 * $.jStorage.storageSize() -> returns the size of the storage in bytes
 *
 * -index()
 * $.jStorage.index() -> returns the used keys as an array
 * 
 * -storageAvailable()
 * $.jStorage.storageAvailable() -> returns true if storage is available
 * 
 * -reInit()
 * $.jStorage.reInit() -> reloads the data from browser storage
 * 
 * <value> can be any JSON-able value, including objects and arrays.
 *
 **/

(function($){
    if(!$ || !($.toJSON || Object.toJSON || window.JSON)){
        throw new Error("jQuery, MooTools or Prototype needs to be loaded before jStorage!");
    }
    
    var
        /* This is the object, that holds the cached values */ 
        _storage = {},

        /* Actual browser storage (localStorage or globalStorage['domain']) */
        _storage_service = {jStorage:"{}"},

        /* DOM element for older IE versions, holds userData behavior */
        _storage_elm = null,
        
        /* How much space does the storage take */
        _storage_size = 0,

        /* function to encode objects to JSON strings */
        json_encode = $.toJSON || Object.toJSON || (window.JSON && (JSON.encode || JSON.stringify)),

        /* function to decode objects from JSON strings */
        json_decode = $.evalJSON || (window.JSON && (JSON.decode || JSON.parse)) || function(str){
            return String(str).evalJSON();
        },
        
        /* which backend is currently used */
        _backend = false;
        
        /**
         * XML encoding and decoding as XML nodes can't be JSON'ized
         * XML nodes are encoded and decoded if the node is the value to be saved
         * but not if it's as a property of another object
         * Eg. -
         *   $.jStorage.set("key", xmlNode);        // IS OK
         *   $.jStorage.set("key", {xml: xmlNode}); // NOT OK
         */
        _XMLService = {
            
            /**
             * Validates a XML node to be XML
             * based on jQuery.isXML function
             */
            isXML: function(elm){
                var documentElement = (elm ? elm.ownerDocument || elm : 0).documentElement;
                return documentElement ? documentElement.nodeName !== "HTML" : false;
            },
            
            /**
             * Encodes a XML node to string
             * based on http://www.mercurytide.co.uk/news/article/issues-when-working-ajax/
             */
            encode: function(xmlNode) {
                if(!this.isXML(xmlNode)){
                    return false;
                }
                try{ // Mozilla, Webkit, Opera
                    return new XMLSerializer().serializeToString(xmlNode);
                }catch(E1) {
                    try {  // IE
                        return xmlNode.xml;
                    }catch(E2){}
                }
                return false;
            },
            
            /**
             * Decodes a XML node from string
             * loosely based on http://outwestmedia.com/jquery-plugins/xmldom/
             */
            decode: function(xmlString){
                var dom_parser = ("DOMParser" in window && (new DOMParser()).parseFromString) ||
                        (window.ActiveXObject && function(_xmlString) {
                    var xml_doc = new ActiveXObject('Microsoft.XMLDOM');
                    xml_doc.async = 'false';
                    xml_doc.loadXML(_xmlString);
                    return xml_doc;
                }),
                resultXML;
                if(!dom_parser){
                    return false;
                }
                resultXML = dom_parser.call("DOMParser" in window && (new DOMParser()) || window, xmlString, 'text/xml');
                return this.isXML(resultXML)?resultXML:false;
            }
        };

    ////////////////////////// PRIVATE METHODS ////////////////////////

    /**
     * Initialization function. Detects if the browser supports DOM Storage
     * or userData behavior and behaves accordingly.
     * @returns undefined
     */
    function _init(){
        /* Check if browser supports localStorage */
        if("localStorage" in window){
            try {
                if(window.localStorage) {
                    _storage_service = window.localStorage;
                    _backend = "localStorage";
                }
            } catch(E3) {/* Firefox fails when touching localStorage and cookies are disabled */}
        }
        /* Check if browser supports globalStorage */
        else if("globalStorage" in window){
            try {
                if(window.globalStorage) {
                    _storage_service = window.globalStorage[window.location.hostname];
                    _backend = "globalStorage";
                }
            } catch(E4) {/* Firefox fails when touching localStorage and cookies are disabled */}
        }
        /* Check if browser supports userData behavior */
        else {
            _storage_elm = document.createElement('link');
            if(_storage_elm.addBehavior){

                /* Use a DOM element to act as userData storage */
                _storage_elm.style.behavior = 'url(#default#userData)';

                /* userData element needs to be inserted into the DOM! */
                document.getElementsByTagName('head')[0].appendChild(_storage_elm);

                _storage_elm.load("jStorage");
                var data = "{}";
                try{
                    data = _storage_elm.getAttribute("jStorage");
                }catch(E5){}
                _storage_service.jStorage = data;
                _backend = "userDataBehavior";
            }else{
                _storage_elm = null;
                return;
            }
        }

        _load_storage();
    }
    
    /**
     * Loads the data from the storage based on the supported mechanism
     * @returns undefined
     */
    function _load_storage(){
        /* if jStorage string is retrieved, then decode it */
        if(_storage_service.jStorage){
            try{
                _storage = json_decode(String(_storage_service.jStorage));
            }catch(E6){_storage_service.jStorage = "{}";}
        }else{
            _storage_service.jStorage = "{}";
        }
        _storage_size = _storage_service.jStorage?String(_storage_service.jStorage).length:0;    
    }

    /**
     * This functions provides the "save" mechanism to store the jStorage object
     * @returns undefined
     */
    function _save(){
        try{
            _storage_service.jStorage = json_encode(_storage);
            // If userData is used as the storage engine, additional
            if(_storage_elm) {
                _storage_elm.setAttribute("jStorage",_storage_service.jStorage);
                _storage_elm.save("jStorage");
            }
            _storage_size = _storage_service.jStorage?String(_storage_service.jStorage).length:0;
        }catch(E7){/* probably cache is full, nothing is saved this way*/}
    }

    /**
     * Function checks if a key is set and is string or numberic
     */
    function _checkKey(key){
        if(!key || (typeof key != "string" && typeof key != "number")){
            throw new TypeError('Key name must be string or numeric');
        }
        return true;
    }

    ////////////////////////// PUBLIC INTERFACE /////////////////////////

    $.jStorage = {
        /* Version number */
        version: "0.1.5.2",

        /**
         * Sets a key's value.
         * 
         * @param {String} key - Key to set. If this value is not set or not
         *              a string an exception is raised.
         * @param value - Value to set. This can be any value that is JSON
         *              compatible (Numbers, Strings, Objects etc.).
         * @returns the used value
         */
        set: function(key, value){
            _checkKey(key);
            if(_XMLService.isXML(value)){
                value = {_is_xml:true,xml:_XMLService.encode(value)};
            }
            _storage[key] = value;
            _save();
            return value;
        },
        
        /**
         * Looks up a key in cache
         * 
         * @param {String} key - Key to look up.
         * @param {mixed} def - Default value to return, if key didn't exist.
         * @returns the key value, default value or <null>
         */
        get: function(key, def){
            _checkKey(key);
            if(key in _storage){
                if(_storage[key] && typeof _storage[key] == "object" &&
                        _storage[key]._is_xml &&
                            _storage[key]._is_xml){
                    return _XMLService.decode(_storage[key].xml);
                }else{
                    return _storage[key];
                }
            }
            return typeof(def) == 'undefined' ? null : def;
        },
        
        /**
         * Deletes a key from cache.
         * 
         * @param {String} key - Key to delete.
         * @returns true if key existed or false if it didn't
         */
        deleteKey: function(key){
            _checkKey(key);
            if(key in _storage){
                delete _storage[key];
                _save();
                return true;
            }
            return false;
        },

        /**
         * Deletes everything in cache.
         * 
         * @returns true
         */
        flush: function(){
            _storage = {};
            _save();
            return true;
        },
        
        /**
         * Returns a read-only copy of _storage
         * 
         * @returns Object
        */
        storageObj: function(){
            function F() {}
            F.prototype = _storage;
            return new F();
        },
        
        /**
         * Returns an index of all used keys as an array
         * ['key1', 'key2',..'keyN']
         * 
         * @returns Array
        */
        index: function(){
            var index = [], i;
            for(i in _storage){
                if(_storage.hasOwnProperty(i)){
                    index.push(i);
                }
            }
            return index;
        },
        
        /**
         * How much space in bytes does the storage take?
         * 
         * @returns Number
         */
        storageSize: function(){
            return _storage_size;
        },
        
        /**
         * Which backend is currently in use?
         * 
         * @returns String
         */
        currentBackend: function(){
            return _backend;
        },
        
        /**
         * Test if storage is available
         * 
         * @returns Boolean
         */
        storageAvailable: function(){
            return !!_backend;
        },
        
        /**
         * Reloads the data from browser storage
         * 
         * @returns undefined
         */
        reInit: function(){
            var new_storage_elm, data;
            if(_storage_elm && _storage_elm.addBehavior){
                new_storage_elm = document.createElement('link');
                
                _storage_elm.parentNode.replaceChild(new_storage_elm, _storage_elm);
                _storage_elm = new_storage_elm;
                
                /* Use a DOM element to act as userData storage */
                _storage_elm.style.behavior = 'url(#default#userData)';

                /* userData element needs to be inserted into the DOM! */
                document.getElementsByTagName('head')[0].appendChild(_storage_elm);

                _storage_elm.load("jStorage");
                data = "{}";
                try{
                    data = _storage_elm.getAttribute("jStorage");
                }catch(E5){}
                _storage_service.jStorage = data;
                _backend = "userDataBehavior";
            }
            
            _load_storage();
        }
    };

    // Initialize jStorage
    _init();

})(window.jQuery || window.$);
/*!
 * jQuery serializeObject - v0.2 - 1/20/2010
 * http://benalman.com/projects/jquery-misc-plugins/
 * 
 * Copyright (c) 2010 "Cowboy" Ben Alman
 * Dual licensed under the MIT and GPL licenses.
 * http://benalman.com/about/license/
 */

// Whereas .serializeArray() serializes a form into an array, .serializeObject()
// serializes a form into an (arguably more useful) object.

(function($,undefined){
  '$:nomunge'; // Used by YUI compressor.
  
  $.fn.serializeObject = function(){
    var obj = {};
    
    $.each( this.serializeArray(), function(i,o){
        var n = o.name,
            v = o.value,
            a = n.substr(n.length-2,n.length) === '[]'; // is it an array?
        
        if (!a) return obj[n] = v;
        
        n = n.substr(0,n.length-2); // strip '[]'
        if (obj[n] === undefined) obj[n] = []; // create array if needed
        obj[n].push(v); // push the value
    });
    
    return obj;
  };
  
})(jQuery);

/**
 *  Validate functions
 *
 *  This plugin gives each form a validator method, with the following
 *  possible calls:
 *
 *  form.validator('add', function, [element])
 *      This adds a function to the form. This function is called
 *      before the form is submitted. If the function returns true,
 *      the form is considered to be valid, and will be submitted. You
 *      can add multiple validation functions with this method.
 *      The form will only be submtted if all validators return true.
 *      If you supply an element, the element will receive the focus
 *      in case of an invalid form.
 * 
 *      Example:
 * 
 *          form.validator('add', function() {
 *              
 *              // check if the form is valid
 *              if (...) return true;
 *              else return false;
 *          });
 * 
 *
 *  form.validator('async', function, [element])
 *      This also adds a validation function to the form, but with
 *      one different: this function should not return true or false,
 *      but it receives one parameter: a callback function that it
 *      should call with the true or false parameter to indicate if
 *      the form was submitted successfully. If you supply an element, 
 *      the element will receive the focus in case of an invalid form.
 *      
 *      Example:
 * 
 *      form.validator('async', function(callback) {
 * 
 *          // do an async call to check if the form is valid (like ajax)
 *          $.get('data', function(answer) {
 *  
 *              // call the callback to tell the system if the form is valid
 *              if (...) callback(true);
 *              else callback(false);
 *          }
 *      }
 *
 * 
 *  form.validator('run', function)
 *      This will run all validator functions, and it will call the
 *      supplied callback function with one parameter: either 'true'
 *      or 'false' to indicate if the form was validated successfully.
 * 
 *      Example:
 * 
 *      form.validator('run', function(valid) {
 * 
 *          if (valid) alert('the form is valid :)');
 *          else alert('the form is not valid :(');
 *      }
 * 
 *  form.validator('report', element, warning, ignoreMessage, callback)
 *      This function will show a warning message right below the field
 *      that the parameter 'element' refers to. You can call this method
 *      from within your validator functions so that the user sees
 *      exactly what fields were wrong. Note that the validator will escape
 *      any HTML code in the warning message, so you do not have to worry
 *      about HTML injections. If the warning should have a link to ignore
 *      it, you can use the two other parameters
 * 
 *      Example:
 * 
 *      form.validator('report', el, 'There is a problem');
 * 
 *      form.validator('report', el, 'There is a problem', 'Ignore this problem', function() {
 *
 *          // code to ignore the problem
 * 
 *      });
 * 
 */
(function($) {

    /**
     *  The available methods
     *  @var array
     */
    var methods = {
        
        /**
         *  Public method to add an synchrounous validator
         *  @param  callback
         */
        add : function(callback) {
        
            // we treat this as a async function
            return methods.async.call(this, function(report) {
                
                // we use a small timeout to ensure we are async
                setTimeout(function() {
                    report(callback());
                });
            });
        },
        
        /**
         *  Public method to add an asynchrounous validator
         *  @param  callback
         */
        async : function(callback) {
         
            // get all current tests
            var $this = $(this);
            var data = $this.data('validator');
            if (!data) data = {tests:[],submitted:false};
         
            // add the function
            data.tests.push(callback);
            
            // put back the data
            $this.data('validator', data);
            
            // was this a subsequent test?
            if (data.tests.length > 1) return this;
            
            // the button that was used for submitting
            var clickedButton = false;
            
            // for all buttons in the form we want to remember which of them was clicked
            // because when we delay the submit, we loose this button information
            $this.find('button[type=submit]').click(function(event) {
                
                // ok, remember that this submit button was clicked
                clickedButton = this;

                // set a small timeout to reset the clicked button (we only want
                // to remember this button if it turns out that clicking this
                // button is the cause for the actual submit, the submit event will
                // start before this timer expires
                setTimeout(function() { clickedButton = false; });
            });
            
            // this was the first test, set up an event handler to block the submit
            $this.submit(function(event) {
                
                // copy the clicked button (because the settimer will set it back to false soon)
                var button = clickedButton;
            
                // was it the cancel button is pressed by the user?
                if ($(button).hasClass('cancel')) return;
            
                // retrieve the data and remember that it was submitted
                $this.data('validator', $.extend($this.data('validator'), {submitted:true}));
            
                // hide all current error reports in the form
                $this.find('.report').fadeOut();
                $this.find('.invalid').removeClass('invalid');
    
                // run the tests
                methods.run.call(this, function(valid) {

                    // if the form was valid, we call the original submit method
                    if (valid) {
                        
                        // we are going to submit the form, but from the browsers viewpoint,
                        // the submit will just come from a script call. but we know of course
                        // the button - if any - that triggered the initial submit. we include
                        // a hidden form field that holds the same button information
                        if (button && $(button).attr('name')) {
                            
                            // append hidden field to the form so that the processing code will
                            // think that the button with this name was clicked
                            $this.append($('<input type="hidden"/>').prop('name', $(button).attr('name')).val($(button).val()));
                        }
                        
                        // fire an event that the form was validated, with a little delay
                        // because it should run after the submit handler was fully executed
                        setTimeout(function() {
                            
                            // construct and trigger the event
                            var event = jQuery.Event("validate");
                            $this.trigger(event);
                            
                            // if the event handler stopped default behaviour we should leap out
                            if (event.isDefaultPrevented()) return;
                            
                            // submit the original form
                            $this[0].submit();
                        });
                    }
                    else {
                        
                        // fire an event that the form was invalidated - with a little delay 
                        // because it should run after the submit event
                        setTimeout(function() {
                            $this.trigger("invalidate");
                        });
                    }
                });
                
                // cancel default submit handler
                event.preventDefault();
                return false;
            });

            // done
            return this;
        },
        
        /**
         *  Run the async tests
         *  @param  callback        This method will be called with a true or
         *                          false parameter to indicate if all checks 
         *                          where valid
         */
        run : function(callback) {

            // get all tests
            var data = $(this).data('validator');
            var tests = data ? data.tests : [];
            
            // the next test that should run
            var nextTest = 0;
            
            // function that will be called when a test completes
            var report = function(success) {
                
                // if the test failed, we should report an error
                if (!success) {
                    
                    // report error
                    callback(false);
                }
                else if (nextTest >= tests.length) {
                
                    // all tests were successfull, report success
                    callback(true);
                }
                else {
                    
                    // move to the next test
                    tests[nextTest++](report);
                }
            }
            
            // we just call the first report function (that will start the first test
            report(true);
            
            // done
            return this;
        },
        
        /**
         *  Report an error - in case the form contained an error
         *  @param  object      The form element that caused the error
         *  @param  string1     The error message
         *  @param  string2     Optional message to ignore this message
         *  @param  function    Callback function in case the message is ignored
         */
        report : function(element, message, ignoreMessage, callback) {
            
            // make sure the element is a jquery object
            element = $(element).first();
            
            // function find the container for the field
            var getContainer = function() {
                
                // we want to put it in the same field 
                var field = element.closest('.field');
                if (field.length > 0) return field.last();
                
                // if there is no field, we want to put it in the same row
                var row = element.closest('.row');
                if (row.length > 0) return row.last();
                
                // if we have no field or row, we will wrap it ourselves
                var wrap = element.closest('.input_wrap');
                if (wrap.length > 0) return wrap.last();
                
                // wrap it and return it
                return element.wrap($('<div/>').addClass('input_wrap')).parent().last();
            };
            
            // remember self
            var self = this;
            
            // function to show the next error
            var show = function() {
                
                // we report nothing if the form was never submitted
                var data = $(self).data('validator');
                //if (!data || !data.submitted) return;
                
                // find the field in which this 
                var container = getContainer();
                
                // is there already an error message here?
                var report = container.find('.report');
                var messageText = report.find('.text');
                
                // create a new report element if none exists
                if (report.length == 0) 
                {
                    // create the wrapper element of the report
                    container.append(report = $('<div/>').addClass('report'));
                    
                    // append the error title
                    report.append($('<span class="close"/>').click(function() {
                        $(this).parent().fadeOut();
                    }));
                    
                    // append the error title
                    report.append($('<span class="title small"/>').text("Foutmelding"));
                    
                    // append the error text
                    report.append(messageText = $('<span class="text small_label"/>'));
                    
                    // append an element to hold the balloon arrow
                    report.append($('<span class="arrow"/>'));
                    
                    // add a class to indicate the report type is a balloon                        
                    report.addClass('notice');
                }
                
                // set the message
                messageText.text(message);
                
                // do we have an ignore message?
                if (ignoreMessage) {
                    
                    // create a link for the ignore message
                    var link = $('<a/>').attr('href', '#').text(ignoreMessage).click(function(event) {
                        
                        // we don't want to do anything with the click
                        event.preventDefault();
                        
                        // hide the error message
                        element.removeClass('invalid');
                        report.fadeOut();

                        // call the callback to inform that the ignore text was clicked
                        if (callback) callback();
                    });
                    
                    // append the link
                    messageText.append($('<span/>').text(' '));
                    messageText.append(link);
                }
                
                // set the balloon in the right position
                if ($('body').hasClass('landing')) report.css('right', container.outerWidth()+2);
                else report.css('top', '-' + (report.outerHeight()+2) + 'px');
                
                // show the report
                element.addClass('invalid');
                report.fadeIn();
                
                // when the element receives focus, it should remove the error message
                if (!element.hasClass('hasEvents')) element.bind('focus keyup click focusout', function(e) {
                    
                    // we dont want the react on the enter key
                    if (e.keyCode == 13) return;
                    
                    // fade out
                    report.fadeOut();
                    element.removeClass('invalid');
                });
                
                // one time event binding is enough
                element.addClass('hasEvents');
            };
            
            // show the error
            show();
        }
    }

    // install the validator plugin
    $.fn.validator = function(method) {
        
        // do we have a method with this name?
        if (methods[method]) return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
        
        // run the validator
        methods.run.apply(this, arguments);
    }   

})(jQuery);


;window.Modernizr=function(a,b,c){function z(a,b){return!!~(""+a).indexOf(b)}function y(a,b){return typeof a===b}function x(a,b){return w(n.join(a+";")+(b||""))}function w(a){k.cssText=a}var d="2.0.6",e={},f=!0,g=b.documentElement,h=b.head||b.getElementsByTagName("head")[0],i="modernizr",j=b.createElement(i),k=j.style,l,m=Object.prototype.toString,n=" -webkit- -moz- -o- -ms- -khtml- ".split(" "),o={},p={},q={},r=[],s=function(a,c,d,e){var f,h,j,k=b.createElement("div");if(parseInt(d,10))while(d--)j=b.createElement("div"),j.id=e?e[d]:i+(d+1),k.appendChild(j);f=["&shy;","<style>",a,"</style>"].join(""),k.id=i,k.innerHTML+=f,g.appendChild(k),h=c(k,a),k.parentNode.removeChild(k);return!!h},t,u={}.hasOwnProperty,v;!y(u,c)&&!y(u.call,c)?v=function(a,b){return u.call(a,b)}:v=function(a,b){return b in a&&y(a.constructor.prototype[b],c)};var A=function(c,d){var f=c.join(""),g=d.length;s(f,function(c,d){var f=b.styleSheets[b.styleSheets.length-1],h=f.cssRules&&f.cssRules[0]?f.cssRules[0].cssText:f.cssText||"",i=c.childNodes,j={};while(g--)j[i[g].id]=i[g];e.touch="ontouchstart"in a||j.touch.offsetTop===9},g,d)}([,["@media (",n.join("touch-enabled),("),i,")","{#touch{top:9px;position:absolute}}"].join("")],[,"touch"]);o.touch=function(){return e.touch};for(var B in o)v(o,B)&&(t=B.toLowerCase(),e[t]=o[B](),r.push((e[t]?"":"no-")+t));w(""),j=l=null,a.attachEvent&&function(){var a=b.createElement("div");a.innerHTML="<elem></elem>";return a.childNodes.length!==1}()&&function(a,b){function s(a){var b=-1;while(++b<g)a.createElement(f[b])}a.iepp=a.iepp||{};var d=a.iepp,e=d.html5elements||"abbr|article|aside|audio|canvas|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",f=e.split("|"),g=f.length,h=new RegExp("(^|\\s)("+e+")","gi"),i=new RegExp("<(/*)("+e+")","gi"),j=/^\s*[\{\}]\s*$/,k=new RegExp("(^|[^\\n]*?\\s)("+e+")([^\\n]*)({[\\n\\w\\W]*?})","gi"),l=b.createDocumentFragment(),m=b.documentElement,n=m.firstChild,o=b.createElement("body"),p=b.createElement("style"),q=/print|all/,r;d.getCSS=function(a,b){if(a+""===c)return"";var e=-1,f=a.length,g,h=[];while(++e<f){g=a[e];if(g.disabled)continue;b=g.media||b,q.test(b)&&h.push(d.getCSS(g.imports,b),g.cssText),b="all"}return h.join("")},d.parseCSS=function(a){var b=[],c;while((c=k.exec(a))!=null)b.push(((j.exec(c[1])?"\n":c[1])+c[2]+c[3]).replace(h,"$1.iepp_$2")+c[4]);return b.join("\n")},d.writeHTML=function(){var a=-1;r=r||b.body;while(++a<g){var c=b.getElementsByTagName(f[a]),d=c.length,e=-1;while(++e<d)c[e].className.indexOf("iepp_")<0&&(c[e].className+=" iepp_"+f[a])}l.appendChild(r),m.appendChild(o),o.className=r.className,o.id=r.id,o.innerHTML=r.innerHTML.replace(i,"<$1font")},d._beforePrint=function(){p.styleSheet.cssText=d.parseCSS(d.getCSS(b.styleSheets,"all")),d.writeHTML()},d.restoreHTML=function(){o.innerHTML="",m.removeChild(o),m.appendChild(r)},d._afterPrint=function(){d.restoreHTML(),p.styleSheet.cssText=""},s(b),s(l);d.disablePP||(n.insertBefore(p,n.firstChild),p.media="print",p.className="iepp-printshim",a.attachEvent("onbeforeprint",d._beforePrint),a.attachEvent("onafterprint",d._afterPrint))}(a,b),e._version=d,e._prefixes=n,e.testStyles=s,g.className=g.className.replace(/\bno-js\b/,"")+(f?" js "+r.join(" "):"");return e}(this,this.document),function(a,b,c){function k(a){return!a||a=="loaded"||a=="complete"}function j(){var a=1,b=-1;while(p.length- ++b)if(p[b].s&&!(a=p[b].r))break;a&&g()}function i(a){var c=b.createElement("script"),d;c.src=a.s,c.onreadystatechange=c.onload=function(){!d&&k(c.readyState)&&(d=1,j(),c.onload=c.onreadystatechange=null)},m(function(){d||(d=1,j())},H.errorTimeout),a.e?c.onload():n.parentNode.insertBefore(c,n)}function h(a){var c=b.createElement("link"),d;c.href=a.s,c.rel="stylesheet",c.type="text/css";if(!a.e&&(w||r)){var e=function(a){m(function(){if(!d)try{a.sheet.cssRules.length?(d=1,j()):e(a)}catch(b){b.code==1e3||b.message=="security"||b.message=="denied"?(d=1,m(function(){j()},0)):e(a)}},0)};e(c)}else c.onload=function(){d||(d=1,m(function(){j()},0))},a.e&&c.onload();m(function(){d||(d=1,j())},H.errorTimeout),!a.e&&n.parentNode.insertBefore(c,n)}function g(){var a=p.shift();q=1,a?a.t?m(function(){a.t=="c"?h(a):i(a)},0):(a(),j()):q=0}function f(a,c,d,e,f,h){function i(){!o&&k(l.readyState)&&(r.r=o=1,!q&&j(),l.onload=l.onreadystatechange=null,m(function(){u.removeChild(l)},0))}var l=b.createElement(a),o=0,r={t:d,s:c,e:h};l.src=l.data=c,!s&&(l.style.display="none"),l.width=l.height="0",a!="object"&&(l.type=d),l.onload=l.onreadystatechange=i,a=="img"?l.onerror=i:a=="script"&&(l.onerror=function(){r.e=r.r=1,g()}),p.splice(e,0,r),u.insertBefore(l,s?null:n),m(function(){o||(u.removeChild(l),r.r=r.e=o=1,j())},H.errorTimeout)}function e(a,b,c){var d=b=="c"?z:y;q=0,b=b||"j",C(a)?f(d,a,b,this.i++,l,c):(p.splice(this.i++,0,a),p.length==1&&g());return this}function d(){var a=H;a.loader={load:e,i:0};return a}var l=b.documentElement,m=a.setTimeout,n=b.getElementsByTagName("script")[0],o={}.toString,p=[],q=0,r="MozAppearance"in l.style,s=r&&!!b.createRange().compareNode,t=r&&!s,u=s?l:n.parentNode,v=a.opera&&o.call(a.opera)=="[object Opera]",w="webkitAppearance"in l.style,x=w&&"async"in b.createElement("script"),y=r?"object":v||x?"img":"script",z=w?"img":y,A=Array.isArray||function(a){return o.call(a)=="[object Array]"},B=function(a){return Object(a)===a},C=function(a){return typeof a=="string"},D=function(a){return o.call(a)=="[object Function]"},E=[],F={},G,H;H=function(a){function f(a){var b=a.split("!"),c=E.length,d=b.pop(),e=b.length,f={url:d,origUrl:d,prefixes:b},g,h;for(h=0;h<e;h++)g=F[b[h]],g&&(f=g(f));for(h=0;h<c;h++)f=E[h](f);return f}function e(a,b,e,g,h){var i=f(a),j=i.autoCallback;if(!i.bypass){b&&(b=D(b)?b:b[a]||b[g]||b[a.split("/").pop().split("?")[0]]);if(i.instead)return i.instead(a,b,e,g,h);e.load(i.url,i.forceCSS||!i.forceJS&&/css$/.test(i.url)?"c":c,i.noexec),(D(b)||D(j))&&e.load(function(){d(),b&&b(i.origUrl,h,g),j&&j(i.origUrl,h,g)})}}function b(a,b){function c(a){if(C(a))e(a,h,b,0,d);else if(B(a))for(i in a)a.hasOwnProperty(i)&&e(a[i],h,b,i,d)}var d=!!a.test,f=d?a.yep:a.nope,g=a.load||a.both,h=a.callback,i;c(f),c(g),a.complete&&b.load(a.complete)}var g,h,i=this.yepnope.loader;if(C(a))e(a,0,i,0);else if(A(a))for(g=0;g<a.length;g++)h=a[g],C(h)?e(h,0,i,0):A(h)?H(h):B(h)&&b(h,i);else B(a)&&b(a,i)},H.addPrefix=function(a,b){F[a]=b},H.addFilter=function(a){E.push(a)},H.errorTimeout=1e4,b.readyState==null&&b.addEventListener&&(b.readyState="loading",b.addEventListener("DOMContentLoaded",G=function(){b.removeEventListener("DOMContentLoaded",G,0),b.readyState="complete"},0)),a.yepnope=d()}(this,this.document),Modernizr.load=function(){yepnope.apply(window,[].slice.call(arguments,0))};

/**
 * Set all passed elements to the same height as the highest element.
 * 
 * Copyright (c) 2010 Ewen Elder
 * Dual licensed under the MIT and GPL licenses:
 * http://www.opensource.org/licenses/mit-license.php
 * http://www.gnu.org/licenses/gpl.html
 *
 * @author: Ewen Elder <glomainn at yahoo dot co dot uk> <ewen at jainaewen dot com>
 * @version: 1.0
 * 
 * @todo: Recaluclate height if extra content is loaded into one of the elements after it has been resized
 *        possibly detect if the highest column has a fixed CSS height to being with or is set to 'auto'; if set to auto
 *        then leave as auto so that it well expend or contract naturally as it would normally.
**/ 

(function ($)
{
    $.fn.equalHeightColumns = function (options)
    {
        var height, elements;
        
        options = $.extend({}, $.equalHeightColumns.defaults, options);
        height = options.height;
        elements = $(this);
        
        $(this).each
        (
            function ()
            {
                // Apply equal height to the children of this element??
                if (options.children)
                {
                    elements = $(this).children(options.children);
                }
                
                // If options.height is 0, then find which element is the highest.
                if (!options.height)
                {
                    // If applying to this elements children, then loop each child element and find which is the highest.
                    if (options.children)
                    {
                        elements.each
                        (
                            function ()
                            {
                                // If this element's height is more than is store in 'height' then update 'height'.
                                if ($(this).height() > height)
                                {
                                    height = $(this).height();
                                }
                            }
                        );
                    }
                    
                    else
                    {
                        // If this element's height is more than is store in 'height' then update 'height'.
                        if ($(this).height() > height)
                        {
                            height = $(this).height();
                        }
                    }
                }
            }
        );
        
        
        // Enforce min height.
        if (options.minHeight && height < options.minHeight)
        {
            height = options.minHeight;
        }
        
        
        // Enforce max height.
        if (options.maxHeight && height > options.maxHeight)
        {
            height = options.maxHeight;
        }
        
        
        // Animate the column's height change.
        elements.animate
        (
            {
                height : height
            },
            options.speed
        );
        
        return $(this);
    };
    
    
    $.equalHeightColumns = {
        version : 1.0,
        defaults : {
            children : false,
            height : 0,
            minHeight : 0,
            maxHeight : 0,
            speed : 0
        }
    };
})(jQuery);

/*
 * Lazy Load - jQuery plugin for lazy loading images
 *
 * Copyright (c) 2007-2011 Mika Tuupola
 *
 * Licensed under the MIT license:
 *   http://www.opensource.org/licenses/mit-license.php
 *
 * Project home:
 *   http://www.appelsiini.net/projects/lazyload
 *
 * Version:  1.6.0
 *
 */
(function($) {

    $.fn.lazyload = function(options) {
        var settings = {
            threshold       : 0,
            failure_limit   : 0,
            event           : "scroll",
            effect          : "show",
            container       : window,
            skip_invisible  : true
        };
                
        if(options) {
            /* Maintain BC for a couple of version. */
            if (null !== options.failurelimit) {
                options.failure_limit = options.failurelimit; 
                delete options.failurelimit;
            }
            
            $.extend(settings, options);
        }

        /* Fire one scroll event per scroll. Not one scroll event per image. */
        var elements = this;
        if (0 == settings.event.indexOf("scroll")) {
            $(settings.container).bind(settings.event, function(event) {
                var counter = 0;
                elements.each(function() {
                    if (settings.skip_invisible && !$(this).is(":visible")) return;
                    if ($.abovethetop(this, settings) ||
                        $.leftofbegin(this, settings)) {
                            /* Nothing. */
                    } else if (!$.belowthefold(this, settings) &&
                        !$.rightoffold(this, settings)) {
                            $(this).trigger("appear");
                    } else {
                        if (++counter > settings.failure_limit) {
                            return false;
                        }
                    }
                });

                /* Remove image from array so it is not looped next time. */
                var temp = $.grep(elements, function(element) {
                    return !element.loaded;
                });
                elements = $(temp);

            });
        }
        
        this.each(function() {
            var self = this;            
            self.loaded = false;
            
            /* When appear is triggered load original image. */
            $(self).one("appear", function() {
                if (!this.loaded) {
                    $("<img />")
                        .bind("load", function() {
                            $(self)
                                .hide()
                                .attr("src", $(self).data("original"))
                                [settings.effect](settings.effectspeed);
                            self.loaded = true;
                        })
                        .attr("src", $(self).data("original"));
                };
            });

            /* When wanted event is triggered load original image */
            /* by triggering appear.                              */
            if (0 != settings.event.indexOf("scroll")) {
                $(self).bind(settings.event, function(event) {
                    if (!self.loaded) {
                        $(self).trigger("appear");
                    }
                });
            }
        });
        
        /* Force initial check if images should appear. */
        $(settings.container).trigger(settings.event);
        
        return this;

    };

    /* Convenience methods in jQuery namespace.           */
    /* Use as  $.belowthefold(element, {threshold : 100, container : window}) */

    $.belowthefold = function(element, settings) {
        if (settings.container === undefined || settings.container === window) {
            var fold = $(window).height() + $(window).scrollTop();
        } else {
            var fold = $(settings.container).offset().top + $(settings.container).height();
        }
        return fold <= $(element).offset().top - settings.threshold;
    };
    
    $.rightoffold = function(element, settings) {
        if (settings.container === undefined || settings.container === window) {
            var fold = $(window).width() + $(window).scrollLeft();
        } else {
            var fold = $(settings.container).offset().left + $(settings.container).width();
        }
        return fold <= $(element).offset().left - settings.threshold;
    };
        
    $.abovethetop = function(element, settings) {
        if (settings.container === undefined || settings.container === window) {
            var fold = $(window).scrollTop();
        } else {
            var fold = $(settings.container).offset().top;
        }
        return fold >= $(element).offset().top + settings.threshold  + $(element).height();
    };
    
    $.leftofbegin = function(element, settings) {
        if (settings.container === undefined || settings.container === window) {
            var fold = $(window).scrollLeft();
        } else {
            var fold = $(settings.container).offset().left;
        }
        return fold >= $(element).offset().left + settings.threshold + $(element).width();
    };
    /* Custom selectors for your convenience.   */
    /* Use as $("img:below-the-fold").something() */

    $.extend($.expr[':'], {
        "below-the-fold" : function(a) { return $.belowthefold(a, {threshold : 0, container: window}) },
        "above-the-fold" : function(a) { return !$.belowthefold(a, {threshold : 0, container: window}) },
        "right-of-fold"  : function(a) { return $.rightoffold(a, {threshold : 0, container: window}) },
        "left-of-fold"   : function(a) { return !$.rightoffold(a, {threshold : 0, container: window}) }
    });
    
})(jQuery);

/*
 * File:        jquery.dataTables.min.js
 * Version:     1.7.6
 * Author:      Allan Jardine (www.sprymedia.co.uk)
 * Info:        www.datatables.net
 * 
 * Copyright 2008-2011 Allan Jardine, all rights reserved.
 *
 * This source file is free software, under either the GPL v2 license or a
 * BSD style license, as supplied with this software.
 * 
 * This source file is distributed in the hope that it will be useful, but 
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 
 * or FITNESS FOR A PARTICULAR PURPOSE. See the license files for details.
 */
(function(j,ra,p){j.fn.dataTableSettings=[];var D=j.fn.dataTableSettings;j.fn.dataTableExt={};var n=j.fn.dataTableExt;n.sVersion="1.7.6";n.sErrMode="alert";n.iApiIndex=0;n.oApi={};n.afnFiltering=[];n.aoFeatures=[];n.ofnSearch={};n.afnSortData=[];n.oStdClasses={sPagePrevEnabled:"paginate_enabled_previous",sPagePrevDisabled:"paginate_disabled_previous",sPageNextEnabled:"paginate_enabled_next",sPageNextDisabled:"paginate_disabled_next",sPageJUINext:"",sPageJUIPrev:"",sPageButton:"paginate_button",sPageButtonActive:"paginate_active",
sPageButtonStaticDisabled:"paginate_button",sPageFirst:"first",sPagePrevious:"previous",sPageNext:"next",sPageLast:"last",sStripOdd:"odd",sStripEven:"even",sRowEmpty:"dataTables_empty",sWrapper:"dataTables_wrapper",sFilter:"dataTables_filter",sInfo:"dataTables_info",sPaging:"dataTables_paginate paging_",sLength:"dataTables_length",sProcessing:"dataTables_processing",sSortAsc:"sorting_asc",sSortDesc:"sorting_desc",sSortable:"sorting",sSortableAsc:"sorting_asc_disabled",sSortableDesc:"sorting_desc_disabled",
sSortableNone:"sorting_disabled",sSortColumn:"sorting_",sSortJUIAsc:"",sSortJUIDesc:"",sSortJUI:"",sSortJUIAscAllowed:"",sSortJUIDescAllowed:"",sSortJUIWrapper:"",sScrollWrapper:"dataTables_scroll",sScrollHead:"dataTables_scrollHead",sScrollHeadInner:"dataTables_scrollHeadInner",sScrollBody:"dataTables_scrollBody",sScrollFoot:"dataTables_scrollFoot",sScrollFootInner:"dataTables_scrollFootInner",sFooterTH:""};n.oJUIClasses={sPagePrevEnabled:"fg-button ui-button ui-state-default ui-corner-left",sPagePrevDisabled:"fg-button ui-button ui-state-default ui-corner-left ui-state-disabled",
sPageNextEnabled:"fg-button ui-button ui-state-default ui-corner-right",sPageNextDisabled:"fg-button ui-button ui-state-default ui-corner-right ui-state-disabled",sPageJUINext:"ui-icon ui-icon-circle-arrow-e",sPageJUIPrev:"ui-icon ui-icon-circle-arrow-w",sPageButton:"fg-button ui-button ui-state-default",sPageButtonActive:"fg-button ui-button ui-state-default ui-state-disabled",sPageButtonStaticDisabled:"fg-button ui-button ui-state-default ui-state-disabled",sPageFirst:"first ui-corner-tl ui-corner-bl",
sPagePrevious:"previous",sPageNext:"next",sPageLast:"last ui-corner-tr ui-corner-br",sStripOdd:"odd",sStripEven:"even",sRowEmpty:"dataTables_empty",sWrapper:"dataTables_wrapper",sFilter:"dataTables_filter",sInfo:"dataTables_info",sPaging:"dataTables_paginate fg-buttonset ui-buttonset fg-buttonset-multi ui-buttonset-multi paging_",sLength:"dataTables_length",sProcessing:"dataTables_processing",sSortAsc:"ui-state-default",sSortDesc:"ui-state-default",sSortable:"ui-state-default",sSortableAsc:"ui-state-default",
sSortableDesc:"ui-state-default",sSortableNone:"ui-state-default",sSortColumn:"sorting_",sSortJUIAsc:"css_right ui-icon ui-icon-triangle-1-n",sSortJUIDesc:"css_right ui-icon ui-icon-triangle-1-s",sSortJUI:"css_right ui-icon ui-icon-carat-2-n-s",sSortJUIAscAllowed:"css_right ui-icon ui-icon-carat-1-n",sSortJUIDescAllowed:"css_right ui-icon ui-icon-carat-1-s",sSortJUIWrapper:"DataTables_sort_wrapper",sScrollWrapper:"dataTables_scroll",sScrollHead:"dataTables_scrollHead ui-state-default",sScrollHeadInner:"dataTables_scrollHeadInner",
sScrollBody:"dataTables_scrollBody",sScrollFoot:"dataTables_scrollFoot ui-state-default",sScrollFootInner:"dataTables_scrollFootInner",sFooterTH:"ui-state-default"};n.oPagination={two_button:{fnInit:function(g,m,r){var s,w,y;if(g.bJUI){s=p.createElement("a");w=p.createElement("a");y=p.createElement("span");y.className=g.oClasses.sPageJUINext;w.appendChild(y);y=p.createElement("span");y.className=g.oClasses.sPageJUIPrev;s.appendChild(y)}else{s=p.createElement("div");w=p.createElement("div")}s.className=
g.oClasses.sPagePrevDisabled;w.className=g.oClasses.sPageNextDisabled;s.title=g.oLanguage.oPaginate.sPrevious;w.title=g.oLanguage.oPaginate.sNext;m.appendChild(s);m.appendChild(w);j(s).bind("click.DT",function(){g.oApi._fnPageChange(g,"previous")&&r(g)});j(w).bind("click.DT",function(){g.oApi._fnPageChange(g,"next")&&r(g)});j(s).bind("selectstart.DT",function(){return false});j(w).bind("selectstart.DT",function(){return false});if(g.sTableId!==""&&typeof g.aanFeatures.p=="undefined"){m.setAttribute("id",
g.sTableId+"_paginate");s.setAttribute("id",g.sTableId+"_previous");w.setAttribute("id",g.sTableId+"_next")}},fnUpdate:function(g){if(g.aanFeatures.p)for(var m=g.aanFeatures.p,r=0,s=m.length;r<s;r++)if(m[r].childNodes.length!==0){m[r].childNodes[0].className=g._iDisplayStart===0?g.oClasses.sPagePrevDisabled:g.oClasses.sPagePrevEnabled;m[r].childNodes[1].className=g.fnDisplayEnd()==g.fnRecordsDisplay()?g.oClasses.sPageNextDisabled:g.oClasses.sPageNextEnabled}}},iFullNumbersShowPages:5,full_numbers:{fnInit:function(g,
m,r){var s=p.createElement("span"),w=p.createElement("span"),y=p.createElement("span"),F=p.createElement("span"),x=p.createElement("span");s.innerHTML=g.oLanguage.oPaginate.sFirst;w.innerHTML=g.oLanguage.oPaginate.sPrevious;F.innerHTML=g.oLanguage.oPaginate.sNext;x.innerHTML=g.oLanguage.oPaginate.sLast;var u=g.oClasses;s.className=u.sPageButton+" "+u.sPageFirst;w.className=u.sPageButton+" "+u.sPagePrevious;F.className=u.sPageButton+" "+u.sPageNext;x.className=u.sPageButton+" "+u.sPageLast;m.appendChild(s);
m.appendChild(w);m.appendChild(y);m.appendChild(F);m.appendChild(x);j(s).bind("click.DT",function(){g.oApi._fnPageChange(g,"first")&&r(g)});j(w).bind("click.DT",function(){g.oApi._fnPageChange(g,"previous")&&r(g)});j(F).bind("click.DT",function(){g.oApi._fnPageChange(g,"next")&&r(g)});j(x).bind("click.DT",function(){g.oApi._fnPageChange(g,"last")&&r(g)});j("span",m).bind("mousedown.DT",function(){return false}).bind("selectstart.DT",function(){return false});if(g.sTableId!==""&&typeof g.aanFeatures.p==
"undefined"){m.setAttribute("id",g.sTableId+"_paginate");s.setAttribute("id",g.sTableId+"_first");w.setAttribute("id",g.sTableId+"_previous");F.setAttribute("id",g.sTableId+"_next");x.setAttribute("id",g.sTableId+"_last")}},fnUpdate:function(g,m){if(g.aanFeatures.p){var r=n.oPagination.iFullNumbersShowPages,s=Math.floor(r/2),w=Math.ceil(g.fnRecordsDisplay()/g._iDisplayLength),y=Math.ceil(g._iDisplayStart/g._iDisplayLength)+1,F="",x,u=g.oClasses;if(w<r){s=1;x=w}else if(y<=s){s=1;x=r}else if(y>=w-s){s=
w-r+1;x=w}else{s=y-Math.ceil(r/2)+1;x=s+r-1}for(r=s;r<=x;r++)F+=y!=r?'<span class="'+u.sPageButton+'">'+r+"</span>":'<span class="'+u.sPageButtonActive+'">'+r+"</span>";x=g.aanFeatures.p;var z,U=function(){g._iDisplayStart=(this.innerHTML*1-1)*g._iDisplayLength;m(g);return false},C=function(){return false};r=0;for(s=x.length;r<s;r++)if(x[r].childNodes.length!==0){z=j("span:eq(2)",x[r]);z.html(F);j("span",z).bind("click.DT",U).bind("mousedown.DT",C).bind("selectstart.DT",C);z=x[r].getElementsByTagName("span");
z=[z[0],z[1],z[z.length-2],z[z.length-1]];j(z).removeClass(u.sPageButton+" "+u.sPageButtonActive+" "+u.sPageButtonStaticDisabled);if(y==1){z[0].className+=" "+u.sPageButtonStaticDisabled;z[1].className+=" "+u.sPageButtonStaticDisabled}else{z[0].className+=" "+u.sPageButton;z[1].className+=" "+u.sPageButton}if(w===0||y==w||g._iDisplayLength==-1){z[2].className+=" "+u.sPageButtonStaticDisabled;z[3].className+=" "+u.sPageButtonStaticDisabled}else{z[2].className+=" "+u.sPageButton;z[3].className+=" "+
u.sPageButton}}}}}};n.oSort={"string-asc":function(g,m){g=g.toLowerCase();m=m.toLowerCase();return g<m?-1:g>m?1:0},"string-desc":function(g,m){g=g.toLowerCase();m=m.toLowerCase();return g<m?1:g>m?-1:0},"html-asc":function(g,m){g=g.replace(/<.*?>/g,"").toLowerCase();m=m.replace(/<.*?>/g,"").toLowerCase();return g<m?-1:g>m?1:0},"html-desc":function(g,m){g=g.replace(/<.*?>/g,"").toLowerCase();m=m.replace(/<.*?>/g,"").toLowerCase();return g<m?1:g>m?-1:0},"date-asc":function(g,m){g=Date.parse(g);m=Date.parse(m);
if(isNaN(g)||g==="")g=Date.parse("01/01/1970 00:00:00");if(isNaN(m)||m==="")m=Date.parse("01/01/1970 00:00:00");return g-m},"date-desc":function(g,m){g=Date.parse(g);m=Date.parse(m);if(isNaN(g)||g==="")g=Date.parse("01/01/1970 00:00:00");if(isNaN(m)||m==="")m=Date.parse("01/01/1970 00:00:00");return m-g},"numeric-asc":function(g,m){return(g=="-"||g===""?0:g*1)-(m=="-"||m===""?0:m*1)},"numeric-desc":function(g,m){return(m=="-"||m===""?0:m*1)-(g=="-"||g===""?0:g*1)}};n.aTypes=[function(g){if(g.length===
0)return"numeric";var m,r=false;m=g.charAt(0);if("0123456789-".indexOf(m)==-1)return null;for(var s=1;s<g.length;s++){m=g.charAt(s);if("0123456789.".indexOf(m)==-1)return null;if(m=="."){if(r)return null;r=true}}return"numeric"},function(g){var m=Date.parse(g);if(m!==null&&!isNaN(m)||g.length===0)return"date";return null},function(g){if(g.indexOf("<")!=-1&&g.indexOf(">")!=-1)return"html";return null}];n.fnVersionCheck=function(g){var m=function(x,u){for(;x.length<u;)x+="0";return x},r=n.sVersion.split(".");
g=g.split(".");for(var s="",w="",y=0,F=g.length;y<F;y++){s+=m(r[y],3);w+=m(g[y],3)}return parseInt(s,10)>=parseInt(w,10)};n._oExternConfig={iNextUnique:0};j.fn.dataTable=function(g){function m(){this.fnRecordsTotal=function(){return this.oFeatures.bServerSide?parseInt(this._iRecordsTotal,10):this.aiDisplayMaster.length};this.fnRecordsDisplay=function(){return this.oFeatures.bServerSide?parseInt(this._iRecordsDisplay,10):this.aiDisplay.length};this.fnDisplayEnd=function(){return this.oFeatures.bServerSide?
this.oFeatures.bPaginate===false||this._iDisplayLength==-1?this._iDisplayStart+this.aiDisplay.length:Math.min(this._iDisplayStart+this._iDisplayLength,this._iRecordsDisplay):this._iDisplayEnd};this.sInstance=this.oInstance=null;this.oFeatures={bPaginate:true,bLengthChange:true,bFilter:true,bSort:true,bInfo:true,bAutoWidth:true,bProcessing:false,bSortClasses:true,bStateSave:false,bServerSide:false};this.oScroll={sX:"",sXInner:"",sY:"",bCollapse:false,bInfinite:false,iLoadGap:100,iBarWidth:0,bAutoCss:true};
this.aanFeatures=[];this.oLanguage={sProcessing:"Processing...",sLengthMenu:"Show _MENU_ entries",sZeroRecords:"No matching records found",sEmptyTable:"No data available in table",sInfo:"Showing _START_ to _END_ of _TOTAL_ entries",sInfoEmpty:"Showing 0 to 0 of 0 entries",sInfoFiltered:"(filtered from _MAX_ total entries)",sInfoPostFix:"",sSearch:"Search:",sUrl:"",oPaginate:{sFirst:"First",sPrevious:"Previous",sNext:"Next",sLast:"Last"},fnInfoCallback:null};this.aoData=[];this.aiDisplay=[];this.aiDisplayMaster=
[];this.aoColumns=[];this.iNextId=0;this.asDataSearch=[];this.oPreviousSearch={sSearch:"",bRegex:false,bSmart:true};this.aoPreSearchCols=[];this.aaSorting=[[0,"asc",0]];this.aaSortingFixed=null;this.asStripClasses=[];this.asDestoryStrips=[];this.sDestroyWidth=0;this.fnFooterCallback=this.fnHeaderCallback=this.fnRowCallback=null;this.aoDrawCallback=[];this.fnInitComplete=null;this.sTableId="";this.nTableWrapper=this.nTBody=this.nTFoot=this.nTHead=this.nTable=null;this.bInitialised=false;this.aoOpenRows=
[];this.sDom="lfrtip";this.sPaginationType="two_button";this.iCookieDuration=7200;this.sCookiePrefix="SpryMedia_DataTables_";this.fnCookieCallback=null;this.aoStateSave=[];this.aoStateLoad=[];this.sAjaxSource=this.oLoadedState=null;this.bAjaxDataGet=true;this.fnServerData=function(a,b,c){j.ajax({url:a,data:b,success:c,dataType:"json",cache:false,error:function(d,f){f=="parsererror"&&alert("DataTables warning: JSON data from server could not be parsed. This is caused by a JSON formatting error.")}})};
this.fnFormatNumber=function(a){if(a<1E3)return a;else{var b=a+"";a=b.split("");var c="";b=b.length;for(var d=0;d<b;d++){if(d%3===0&&d!==0)c=","+c;c=a[b-d-1]+c}}return c};this.aLengthMenu=[10,25,50,100];this.bDrawing=this.iDraw=0;this.iDrawError=-1;this._iDisplayLength=10;this._iDisplayStart=0;this._iDisplayEnd=10;this._iRecordsDisplay=this._iRecordsTotal=0;this.bJUI=false;this.oClasses=n.oStdClasses;this.bSorted=this.bFiltered=false;this.oInit=null}function r(a){return function(){var b=[A(this[n.iApiIndex])].concat(Array.prototype.slice.call(arguments));
return n.oApi[a].apply(this,b)}}function s(a){var b,c;if(a.bInitialised===false)setTimeout(function(){s(a)},200);else{sa(a);U(a);K(a,true);a.oFeatures.bAutoWidth&&$(a);b=0;for(c=a.aoColumns.length;b<c;b++)if(a.aoColumns[b].sWidth!==null)a.aoColumns[b].nTh.style.width=v(a.aoColumns[b].sWidth);if(a.oFeatures.bSort)O(a);else{a.aiDisplay=a.aiDisplayMaster.slice();E(a);C(a)}if(a.sAjaxSource!==null&&!a.oFeatures.bServerSide)a.fnServerData.call(a.oInstance,a.sAjaxSource,[],function(d){for(b=0;b<d.aaData.length;b++)u(a,
d.aaData[b]);a.iInitDisplayStart=a._iDisplayStart;if(a.oFeatures.bSort)O(a);else{a.aiDisplay=a.aiDisplayMaster.slice();E(a);C(a)}K(a,false);w(a,d)});else if(!a.oFeatures.bServerSide){K(a,false);w(a)}}}function w(a,b){a._bInitComplete=true;if(typeof a.fnInitComplete=="function")typeof b!="undefined"?a.fnInitComplete.call(a.oInstance,a,b):a.fnInitComplete.call(a.oInstance,a)}function y(a,b,c){o(a.oLanguage,b,"sProcessing");o(a.oLanguage,b,"sLengthMenu");o(a.oLanguage,b,"sEmptyTable");o(a.oLanguage,
b,"sZeroRecords");o(a.oLanguage,b,"sInfo");o(a.oLanguage,b,"sInfoEmpty");o(a.oLanguage,b,"sInfoFiltered");o(a.oLanguage,b,"sInfoPostFix");o(a.oLanguage,b,"sSearch");if(typeof b.oPaginate!="undefined"){o(a.oLanguage.oPaginate,b.oPaginate,"sFirst");o(a.oLanguage.oPaginate,b.oPaginate,"sPrevious");o(a.oLanguage.oPaginate,b.oPaginate,"sNext");o(a.oLanguage.oPaginate,b.oPaginate,"sLast")}typeof b.sEmptyTable=="undefined"&&typeof b.sZeroRecords!="undefined"&&o(a.oLanguage,b,"sZeroRecords","sEmptyTable");
c&&s(a)}function F(a,b){a.aoColumns[a.aoColumns.length++]={sType:null,_bAutoType:true,bVisible:true,bSearchable:true,bSortable:true,asSorting:["asc","desc"],sSortingClass:a.oClasses.sSortable,sSortingClassJUI:a.oClasses.sSortJUI,sTitle:b?b.innerHTML:"",sName:"",sWidth:null,sWidthOrig:null,sClass:null,fnRender:null,bUseRendered:true,iDataSort:a.aoColumns.length-1,sSortDataType:"std",nTh:b?b:p.createElement("th"),nTf:null,anThExtra:[],anTfExtra:[]};b=a.aoColumns.length-1;if(typeof a.aoPreSearchCols[b]==
"undefined"||a.aoPreSearchCols[b]===null)a.aoPreSearchCols[b]={sSearch:"",bRegex:false,bSmart:true};else{if(typeof a.aoPreSearchCols[b].bRegex=="undefined")a.aoPreSearchCols[b].bRegex=true;if(typeof a.aoPreSearchCols[b].bSmart=="undefined")a.aoPreSearchCols[b].bSmart=true}x(a,b,null)}function x(a,b,c){b=a.aoColumns[b];if(typeof c!="undefined"&&c!==null){if(typeof c.sType!="undefined"){b.sType=c.sType;b._bAutoType=false}o(b,c,"bVisible");o(b,c,"bSearchable");o(b,c,"bSortable");o(b,c,"sTitle");o(b,
c,"sName");o(b,c,"sWidth");o(b,c,"sWidth","sWidthOrig");o(b,c,"sClass");o(b,c,"fnRender");o(b,c,"bUseRendered");o(b,c,"iDataSort");o(b,c,"asSorting");o(b,c,"sSortDataType")}if(!a.oFeatures.bSort)b.bSortable=false;if(!b.bSortable||j.inArray("asc",b.asSorting)==-1&&j.inArray("desc",b.asSorting)==-1){b.sSortingClass=a.oClasses.sSortableNone;b.sSortingClassJUI=""}else if(j.inArray("asc",b.asSorting)!=-1&&j.inArray("desc",b.asSorting)==-1){b.sSortingClass=a.oClasses.sSortableAsc;b.sSortingClassJUI=a.oClasses.sSortJUIAscAllowed}else if(j.inArray("asc",
b.asSorting)==-1&&j.inArray("desc",b.asSorting)!=-1){b.sSortingClass=a.oClasses.sSortableDesc;b.sSortingClassJUI=a.oClasses.sSortJUIDescAllowed}}function u(a,b){if(b.length!=a.aoColumns.length&&a.iDrawError!=a.iDraw){H(a,0,"Added data (size "+b.length+") does not match known number of columns ("+a.aoColumns.length+")");a.iDrawError=a.iDraw;return-1}b=b.slice();var c=a.aoData.length;a.aoData.push({nTr:p.createElement("tr"),_iId:a.iNextId++,_aData:b,_anHidden:[],_sRowStripe:""});for(var d,f,e=0;e<b.length;e++){d=
p.createElement("td");if(b[e]===null)b[e]="";if(typeof a.aoColumns[e].fnRender=="function"){f=a.aoColumns[e].fnRender({iDataRow:c,iDataColumn:e,aData:b,oSettings:a});d.innerHTML=f;if(a.aoColumns[e].bUseRendered)a.aoData[c]._aData[e]=f}else d.innerHTML=b[e];if(typeof b[e]!="string")b[e]+="";b[e]=j.trim(b[e]);if(a.aoColumns[e].sClass!==null)d.className=a.aoColumns[e].sClass;if(a.aoColumns[e]._bAutoType&&a.aoColumns[e].sType!="string"){f=aa(a.aoData[c]._aData[e]);if(a.aoColumns[e].sType===null)a.aoColumns[e].sType=
f;else if(a.aoColumns[e].sType!=f)a.aoColumns[e].sType="string"}if(a.aoColumns[e].bVisible){a.aoData[c].nTr.appendChild(d);a.aoData[c]._anHidden[e]=null}else a.aoData[c]._anHidden[e]=d}a.aiDisplayMaster.push(c);return c}function z(a){var b,c,d,f,e,i,h,k;if(a.sAjaxSource===null){h=a.nTBody.childNodes;b=0;for(c=h.length;b<c;b++)if(h[b].nodeName.toUpperCase()=="TR"){i=a.aoData.length;a.aoData.push({nTr:h[b],_iId:a.iNextId++,_aData:[],_anHidden:[],_sRowStripe:""});a.aiDisplayMaster.push(i);k=a.aoData[i]._aData;
i=h[b].childNodes;d=e=0;for(f=i.length;d<f;d++)if(i[d].nodeName.toUpperCase()=="TD"){k[e]=j.trim(i[d].innerHTML);e++}}}h=R(a);i=[];b=0;for(c=h.length;b<c;b++){d=0;for(f=h[b].childNodes.length;d<f;d++){e=h[b].childNodes[d];e.nodeName.toUpperCase()=="TD"&&i.push(e)}}i.length!=h.length*a.aoColumns.length&&H(a,1,"Unexpected number of TD elements. Expected "+h.length*a.aoColumns.length+" and got "+i.length+". DataTables does not support rowspan / colspan in the table body, and there must be one cell for each row/column combination.");
h=0;for(d=a.aoColumns.length;h<d;h++){if(a.aoColumns[h].sTitle===null)a.aoColumns[h].sTitle=a.aoColumns[h].nTh.innerHTML;f=a.aoColumns[h]._bAutoType;e=typeof a.aoColumns[h].fnRender=="function";k=a.aoColumns[h].sClass!==null;var l=a.aoColumns[h].bVisible,q,t;if(f||e||k||!l){b=0;for(c=a.aoData.length;b<c;b++){q=i[b*d+h];if(f)if(a.aoColumns[h].sType!="string"){t=aa(a.aoData[b]._aData[h]);if(a.aoColumns[h].sType===null)a.aoColumns[h].sType=t;else if(a.aoColumns[h].sType!=t)a.aoColumns[h].sType="string"}if(e){t=
a.aoColumns[h].fnRender({iDataRow:b,iDataColumn:h,aData:a.aoData[b]._aData,oSettings:a});q.innerHTML=t;if(a.aoColumns[h].bUseRendered)a.aoData[b]._aData[h]=t}if(k)q.className+=" "+a.aoColumns[h].sClass;if(l)a.aoData[b]._anHidden[h]=null;else{a.aoData[b]._anHidden[h]=q;q.parentNode.removeChild(q)}}}}}function U(a){var b,c,d,f,e,i=a.nTHead.getElementsByTagName("tr"),h=0,k;if(a.nTHead.getElementsByTagName("th").length!==0){b=0;for(d=a.aoColumns.length;b<d;b++){c=a.aoColumns[b].nTh;a.aoColumns[b].sClass!==
null&&j(c).addClass(a.aoColumns[b].sClass);f=1;for(e=i.length;f<e;f++){k=j(i[f]).children();a.aoColumns[b].anThExtra.push(k[b-h]);a.aoColumns[b].bVisible||i[f].removeChild(k[b-h])}if(a.aoColumns[b].bVisible){if(a.aoColumns[b].sTitle!=c.innerHTML)c.innerHTML=a.aoColumns[b].sTitle}else{c.parentNode.removeChild(c);h++}}}else{f=p.createElement("tr");b=0;for(d=a.aoColumns.length;b<d;b++){c=a.aoColumns[b].nTh;c.innerHTML=a.aoColumns[b].sTitle;a.aoColumns[b].sClass!==null&&j(c).addClass(a.aoColumns[b].sClass);
a.aoColumns[b].bVisible&&f.appendChild(c)}j(a.nTHead).html("")[0].appendChild(f)}if(a.bJUI){b=0;for(d=a.aoColumns.length;b<d;b++){c=a.aoColumns[b].nTh;f=p.createElement("div");f.className=a.oClasses.sSortJUIWrapper;j(c).contents().appendTo(f);f.appendChild(p.createElement("span"));c.appendChild(f)}}d=function(){this.onselectstart=function(){return false};return false};if(a.oFeatures.bSort)for(b=0;b<a.aoColumns.length;b++)if(a.aoColumns[b].bSortable!==false){ba(a,a.aoColumns[b].nTh,b);j(a.aoColumns[b].nTh).bind("mousedown.DT",
d)}else j(a.aoColumns[b].nTh).addClass(a.oClasses.sSortableNone);if(a.nTFoot!==null){h=0;i=a.nTFoot.getElementsByTagName("tr");c=i[0].getElementsByTagName("th");b=0;for(d=c.length;b<d;b++)if(typeof a.aoColumns[b]!="undefined"){a.aoColumns[b].nTf=c[b-h];if(a.oClasses.sFooterTH!=="")a.aoColumns[b].nTf.className+=" "+a.oClasses.sFooterTH;f=1;for(e=i.length;f<e;f++){k=j(i[f]).children();a.aoColumns[b].anTfExtra.push(k[b-h]);a.aoColumns[b].bVisible||i[f].removeChild(k[b-h])}if(!a.aoColumns[b].bVisible){c[b-
h].parentNode.removeChild(c[b-h]);h++}}}}function C(a){var b,c,d=[],f=0,e=false;b=a.asStripClasses.length;c=a.aoOpenRows.length;a.bDrawing=true;if(typeof a.iInitDisplayStart!="undefined"&&a.iInitDisplayStart!=-1){a._iDisplayStart=a.oFeatures.bServerSide?a.iInitDisplayStart:a.iInitDisplayStart>=a.fnRecordsDisplay()?0:a.iInitDisplayStart;a.iInitDisplayStart=-1;E(a)}if(!(!a.bDestroying&&a.oFeatures.bServerSide&&!ta(a))){a.oFeatures.bServerSide||a.iDraw++;if(a.aiDisplay.length!==0){var i=a._iDisplayStart,
h=a._iDisplayEnd;if(a.oFeatures.bServerSide){i=0;h=a.aoData.length}for(i=i;i<h;i++){var k=a.aoData[a.aiDisplay[i]],l=k.nTr;if(b!==0){var q=a.asStripClasses[f%b];if(k._sRowStripe!=q){j(l).removeClass(k._sRowStripe).addClass(q);k._sRowStripe=q}}if(typeof a.fnRowCallback=="function"){l=a.fnRowCallback.call(a.oInstance,l,a.aoData[a.aiDisplay[i]]._aData,f,i);if(!l&&!e){H(a,0,"A node was not returned by fnRowCallback");e=true}}d.push(l);f++;if(c!==0)for(k=0;k<c;k++)l==a.aoOpenRows[k].nParent&&d.push(a.aoOpenRows[k].nTr)}}else{d[0]=
p.createElement("tr");if(typeof a.asStripClasses[0]!="undefined")d[0].className=a.asStripClasses[0];e=p.createElement("td");e.setAttribute("valign","top");e.colSpan=S(a);e.className=a.oClasses.sRowEmpty;e.innerHTML=typeof a.oLanguage.sEmptyTable!="undefined"&&a.fnRecordsTotal()===0?a.oLanguage.sEmptyTable:a.oLanguage.sZeroRecords.replace("_MAX_",a.fnFormatNumber(a.fnRecordsTotal()));d[f].appendChild(e)}typeof a.fnHeaderCallback=="function"&&a.fnHeaderCallback.call(a.oInstance,j(">tr",a.nTHead)[0],
V(a),a._iDisplayStart,a.fnDisplayEnd(),a.aiDisplay);typeof a.fnFooterCallback=="function"&&a.fnFooterCallback.call(a.oInstance,j(">tr",a.nTFoot)[0],V(a),a._iDisplayStart,a.fnDisplayEnd(),a.aiDisplay);f=p.createDocumentFragment();b=p.createDocumentFragment();if(a.nTBody){e=a.nTBody.parentNode;b.appendChild(a.nTBody);if(!a.oScroll.bInfinite||!a._bInitComplete||a.bSorted||a.bFiltered){c=a.nTBody.childNodes;for(b=c.length-1;b>=0;b--)c[b].parentNode.removeChild(c[b])}b=0;for(c=d.length;b<c;b++)f.appendChild(d[b]);
a.nTBody.appendChild(f);e!==null&&e.appendChild(a.nTBody)}for(b=a.aoDrawCallback.length-1;b>=0;b--)a.aoDrawCallback[b].fn.call(a.oInstance,a);a.bSorted=false;a.bFiltered=false;a.bDrawing=false;if(a.oFeatures.bServerSide){K(a,false);typeof a._bInitComplete=="undefined"&&w(a)}}}function W(a){if(a.oFeatures.bSort)O(a,a.oPreviousSearch);else if(a.oFeatures.bFilter)P(a,a.oPreviousSearch);else{E(a);C(a)}}function ta(a){if(a.bAjaxDataGet){K(a,true);var b=a.aoColumns.length,c=[],d;a.iDraw++;c.push({name:"sEcho",
value:a.iDraw});c.push({name:"iColumns",value:b});c.push({name:"sColumns",value:ca(a)});c.push({name:"iDisplayStart",value:a._iDisplayStart});c.push({name:"iDisplayLength",value:a.oFeatures.bPaginate!==false?a._iDisplayLength:-1});if(a.oFeatures.bFilter!==false){c.push({name:"sSearch",value:a.oPreviousSearch.sSearch});c.push({name:"bRegex",value:a.oPreviousSearch.bRegex});for(d=0;d<b;d++){c.push({name:"sSearch_"+d,value:a.aoPreSearchCols[d].sSearch});c.push({name:"bRegex_"+d,value:a.aoPreSearchCols[d].bRegex});
c.push({name:"bSearchable_"+d,value:a.aoColumns[d].bSearchable})}}if(a.oFeatures.bSort!==false){var f=a.aaSortingFixed!==null?a.aaSortingFixed.length:0,e=a.aaSorting.length;c.push({name:"iSortingCols",value:f+e});for(d=0;d<f;d++){c.push({name:"iSortCol_"+d,value:a.aaSortingFixed[d][0]});c.push({name:"sSortDir_"+d,value:a.aaSortingFixed[d][1]})}for(d=0;d<e;d++){c.push({name:"iSortCol_"+(d+f),value:a.aaSorting[d][0]});c.push({name:"sSortDir_"+(d+f),value:a.aaSorting[d][1]})}for(d=0;d<b;d++)c.push({name:"bSortable_"+
d,value:a.aoColumns[d].bSortable})}a.fnServerData.call(a.oInstance,a.sAjaxSource,c,function(i){ua(a,i)});return false}else return true}function ua(a,b){if(typeof b.sEcho!="undefined")if(b.sEcho*1<a.iDraw)return;else a.iDraw=b.sEcho*1;if(!a.oScroll.bInfinite||a.oScroll.bInfinite&&(a.bSorted||a.bFiltered))da(a);a._iRecordsTotal=b.iTotalRecords;a._iRecordsDisplay=b.iTotalDisplayRecords;var c=ca(a);if(c=typeof b.sColumns!="undefined"&&c!==""&&b.sColumns!=c)var d=va(a,b.sColumns);for(var f=0,e=b.aaData.length;f<
e;f++)if(c){for(var i=[],h=0,k=a.aoColumns.length;h<k;h++)i.push(b.aaData[f][d[h]]);u(a,i)}else u(a,b.aaData[f]);a.aiDisplay=a.aiDisplayMaster.slice();a.bAjaxDataGet=false;C(a);a.bAjaxDataGet=true;K(a,false)}function sa(a){var b=p.createElement("div");a.nTable.parentNode.insertBefore(b,a.nTable);a.nTableWrapper=p.createElement("div");a.nTableWrapper.className=a.oClasses.sWrapper;a.sTableId!==""&&a.nTableWrapper.setAttribute("id",a.sTableId+"_wrapper");for(var c=a.nTableWrapper,d=a.sDom.split(""),
f,e,i,h,k,l,q,t=0;t<d.length;t++){e=0;i=d[t];if(i=="<"){h=p.createElement("div");k=d[t+1];if(k=="'"||k=='"'){l="";for(q=2;d[t+q]!=k;){l+=d[t+q];q++}if(l=="H")l="fg-toolbar ui-toolbar ui-widget-header ui-corner-tl ui-corner-tr ui-helper-clearfix";else if(l=="F")l="fg-toolbar ui-toolbar ui-widget-header ui-corner-bl ui-corner-br ui-helper-clearfix";if(l.indexOf(".")!=-1){k=l.split(".");h.setAttribute("id",k[0].substr(1,k[0].length-1));h.className=k[1]}else if(l.charAt(0)=="#")h.setAttribute("id",l.substr(1,
l.length-1));else h.className=l;t+=q}c.appendChild(h);c=h}else if(i==">")c=c.parentNode;else if(i=="l"&&a.oFeatures.bPaginate&&a.oFeatures.bLengthChange){f=wa(a);e=1}else if(i=="f"&&a.oFeatures.bFilter){f=xa(a);e=1}else if(i=="r"&&a.oFeatures.bProcessing){f=ya(a);e=1}else if(i=="t"){f=za(a);e=1}else if(i=="i"&&a.oFeatures.bInfo){f=Aa(a);e=1}else if(i=="p"&&a.oFeatures.bPaginate){f=Ba(a);e=1}else if(n.aoFeatures.length!==0){h=n.aoFeatures;q=0;for(k=h.length;q<k;q++)if(i==h[q].cFeature){if(f=h[q].fnInit(a))e=
1;break}}if(e==1&&f!==null){if(typeof a.aanFeatures[i]!="object")a.aanFeatures[i]=[];a.aanFeatures[i].push(f);c.appendChild(f)}}b.parentNode.replaceChild(a.nTableWrapper,b)}function za(a){if(a.oScroll.sX===""&&a.oScroll.sY==="")return a.nTable;var b=p.createElement("div"),c=p.createElement("div"),d=p.createElement("div"),f=p.createElement("div"),e=p.createElement("div"),i=p.createElement("div"),h=a.nTable.cloneNode(false),k=a.nTable.cloneNode(false),l=a.nTable.getElementsByTagName("thead")[0],q=a.nTable.getElementsByTagName("tfoot").length===
0?null:a.nTable.getElementsByTagName("tfoot")[0],t=typeof g.bJQueryUI!="undefined"&&g.bJQueryUI?n.oJUIClasses:n.oStdClasses;c.appendChild(d);e.appendChild(i);f.appendChild(a.nTable);b.appendChild(c);b.appendChild(f);d.appendChild(h);h.appendChild(l);if(q!==null){b.appendChild(e);i.appendChild(k);k.appendChild(q)}b.className=t.sScrollWrapper;c.className=t.sScrollHead;d.className=t.sScrollHeadInner;f.className=t.sScrollBody;e.className=t.sScrollFoot;i.className=t.sScrollFootInner;if(a.oScroll.bAutoCss){c.style.overflow=
"hidden";c.style.position="relative";e.style.overflow="hidden";f.style.overflow="auto"}c.style.border="0";c.style.width="100%";e.style.border="0";d.style.width="150%";h.removeAttribute("id");h.style.marginLeft="0";a.nTable.style.marginLeft="0";if(q!==null){k.removeAttribute("id");k.style.marginLeft="0"}d=j(">caption",a.nTable);i=0;for(k=d.length;i<k;i++)h.appendChild(d[i]);if(a.oScroll.sX!==""){c.style.width=v(a.oScroll.sX);f.style.width=v(a.oScroll.sX);if(q!==null)e.style.width=v(a.oScroll.sX);j(f).scroll(function(){c.scrollLeft=
this.scrollLeft;if(q!==null)e.scrollLeft=this.scrollLeft})}if(a.oScroll.sY!=="")f.style.height=v(a.oScroll.sY);a.aoDrawCallback.push({fn:Ca,sName:"scrolling"});a.oScroll.bInfinite&&j(f).scroll(function(){if(!a.bDrawing)if(j(this).scrollTop()+j(this).height()>j(a.nTable).height()-a.oScroll.iLoadGap)if(a.fnDisplayEnd()<a.fnRecordsDisplay()){ea(a,"next");E(a);C(a)}});a.nScrollHead=c;a.nScrollFoot=e;return b}function Ca(a){var b=a.nScrollHead.getElementsByTagName("div")[0],c=b.getElementsByTagName("table")[0],
d=a.nTable.parentNode,f,e,i,h,k,l,q,t,G=[];i=a.nTable.getElementsByTagName("thead");i.length>0&&a.nTable.removeChild(i[0]);if(a.nTFoot!==null){k=a.nTable.getElementsByTagName("tfoot");k.length>0&&a.nTable.removeChild(k[0])}i=a.nTHead.cloneNode(true);a.nTable.insertBefore(i,a.nTable.childNodes[0]);if(a.nTFoot!==null){k=a.nTFoot.cloneNode(true);a.nTable.insertBefore(k,a.nTable.childNodes[1])}var J=fa(i);f=0;for(e=J.length;f<e;f++){q=ga(a,f);J[f].style.width=a.aoColumns[q].sWidth}a.nTFoot!==null&&L(function(B){B.style.width=
""},k.getElementsByTagName("tr"));f=j(a.nTable).outerWidth();if(a.oScroll.sX===""){a.nTable.style.width="100%";if(j.browser.msie&&j.browser.version<=7)a.nTable.style.width=v(j(a.nTable).outerWidth()-a.oScroll.iBarWidth)}else if(a.oScroll.sXInner!=="")a.nTable.style.width=v(a.oScroll.sXInner);else if(f==j(d).width()&&j(d).height()<j(a.nTable).height()){a.nTable.style.width=v(f-a.oScroll.iBarWidth);if(j(a.nTable).outerWidth()>f-a.oScroll.iBarWidth)a.nTable.style.width=v(f)}else a.nTable.style.width=
v(f);f=j(a.nTable).outerWidth();e=a.nTHead.getElementsByTagName("tr");i=i.getElementsByTagName("tr");L(function(B,I){l=B.style;l.paddingTop="0";l.paddingBottom="0";l.borderTopWidth="0";l.borderBottomWidth="0";l.height=0;t=j(B).width();I.style.width=v(t);G.push(t)},i,e);j(i).height(0);if(a.nTFoot!==null){h=k.getElementsByTagName("tr");k=a.nTFoot.getElementsByTagName("tr");L(function(B,I){l=B.style;l.paddingTop="0";l.paddingBottom="0";l.borderTopWidth="0";l.borderBottomWidth="0";l.height=0;t=j(B).width();
I.style.width=v(t);G.push(t)},h,k);j(h).height(0)}L(function(B){B.innerHTML="";B.style.width=v(G.shift())},i);a.nTFoot!==null&&L(function(B){B.innerHTML="";B.style.width=v(G.shift())},h);if(j(a.nTable).outerWidth()<f)if(a.oScroll.sX==="")H(a,1,"The table cannot fit into the current element which will cause column misalignment. It is suggested that you enable x-scrolling or increase the width the table has in which to be drawn");else a.oScroll.sXInner!==""&&H(a,1,"The table cannot fit into the current element which will cause column misalignment. It is suggested that you increase the sScrollXInner property to allow it to draw in a larger area, or simply remove that parameter to allow automatic calculation");
if(a.oScroll.sY==="")if(j.browser.msie&&j.browser.version<=7)d.style.height=v(a.nTable.offsetHeight+a.oScroll.iBarWidth);if(a.oScroll.sY!==""&&a.oScroll.bCollapse){d.style.height=v(a.oScroll.sY);h=a.oScroll.sX!==""&&a.nTable.offsetWidth>d.offsetWidth?a.oScroll.iBarWidth:0;if(a.nTable.offsetHeight<d.offsetHeight)d.style.height=v(j(a.nTable).height()+h)}h=j(a.nTable).outerWidth();c.style.width=v(h);b.style.width=v(h+a.oScroll.iBarWidth);if(a.nTFoot!==null){b=a.nScrollFoot.getElementsByTagName("div")[0];
c=b.getElementsByTagName("table")[0];b.style.width=v(a.nTable.offsetWidth+a.oScroll.iBarWidth);c.style.width=v(a.nTable.offsetWidth)}if(a.bSorted||a.bFiltered)d.scrollTop=0}function X(a){if(a.oFeatures.bAutoWidth===false)return false;$(a);for(var b=0,c=a.aoColumns.length;b<c;b++)a.aoColumns[b].nTh.style.width=a.aoColumns[b].sWidth}function xa(a){var b=p.createElement("div");a.sTableId!==""&&typeof a.aanFeatures.f=="undefined"&&b.setAttribute("id",a.sTableId+"_filter");b.className=a.oClasses.sFilter;
b.innerHTML=a.oLanguage.sSearch+(a.oLanguage.sSearch===""?"":" ")+'<input type="text" />';var c=j("input",b);c.val(a.oPreviousSearch.sSearch.replace('"',"&quot;"));c.bind("keyup.DT",function(){for(var d=a.aanFeatures.f,f=0,e=d.length;f<e;f++)d[f]!=this.parentNode&&j("input",d[f]).val(this.value);this.value!=a.oPreviousSearch.sSearch&&P(a,{sSearch:this.value,bRegex:a.oPreviousSearch.bRegex,bSmart:a.oPreviousSearch.bSmart})});c.bind("keypress.DT",function(d){if(d.keyCode==13)return false});return b}
function P(a,b,c){Da(a,b.sSearch,c,b.bRegex,b.bSmart);for(b=0;b<a.aoPreSearchCols.length;b++)Ea(a,a.aoPreSearchCols[b].sSearch,b,a.aoPreSearchCols[b].bRegex,a.aoPreSearchCols[b].bSmart);n.afnFiltering.length!==0&&Fa(a);a.bFiltered=true;a._iDisplayStart=0;E(a);C(a);ha(a,0)}function Fa(a){for(var b=n.afnFiltering,c=0,d=b.length;c<d;c++)for(var f=0,e=0,i=a.aiDisplay.length;e<i;e++){var h=a.aiDisplay[e-f];if(!b[c](a,a.aoData[h]._aData,h)){a.aiDisplay.splice(e-f,1);f++}}}function Ea(a,b,c,d,f){if(b!==
""){var e=0;b=ia(b,d,f);for(d=a.aiDisplay.length-1;d>=0;d--){f=ja(a.aoData[a.aiDisplay[d]]._aData[c],a.aoColumns[c].sType);if(!b.test(f)){a.aiDisplay.splice(d,1);e++}}}}function Da(a,b,c,d,f){var e=ia(b,d,f);if(typeof c=="undefined"||c===null)c=0;if(n.afnFiltering.length!==0)c=1;if(b.length<=0){a.aiDisplay.splice(0,a.aiDisplay.length);a.aiDisplay=a.aiDisplayMaster.slice()}else if(a.aiDisplay.length==a.aiDisplayMaster.length||a.oPreviousSearch.sSearch.length>b.length||c==1||b.indexOf(a.oPreviousSearch.sSearch)!==
0){a.aiDisplay.splice(0,a.aiDisplay.length);ha(a,1);for(c=0;c<a.aiDisplayMaster.length;c++)e.test(a.asDataSearch[c])&&a.aiDisplay.push(a.aiDisplayMaster[c])}else{var i=0;for(c=0;c<a.asDataSearch.length;c++)if(!e.test(a.asDataSearch[c])){a.aiDisplay.splice(c-i,1);i++}}a.oPreviousSearch.sSearch=b;a.oPreviousSearch.bRegex=d;a.oPreviousSearch.bSmart=f}function ha(a,b){a.asDataSearch.splice(0,a.asDataSearch.length);b=typeof b!="undefined"&&b==1?a.aiDisplayMaster:a.aiDisplay;for(var c=0,d=b.length;c<d;c++)a.asDataSearch[c]=
ka(a,a.aoData[b[c]]._aData)}function ka(a,b){for(var c="",d=p.createElement("div"),f=0,e=a.aoColumns.length;f<e;f++)if(a.aoColumns[f].bSearchable)c+=ja(b[f],a.aoColumns[f].sType)+"  ";if(c.indexOf("&")!==-1){d.innerHTML=c;c=d.textContent?d.textContent:d.innerText;c=c.replace(/\n/g," ").replace(/\r/g,"")}return c}function ia(a,b,c){if(c){a=b?a.split(" "):la(a).split(" ");a="^(?=.*?"+a.join(")(?=.*?")+").*$";return new RegExp(a,"i")}else{a=b?a:la(a);return new RegExp(a,"i")}}function ja(a,b){if(typeof n.ofnSearch[b]==
"function")return n.ofnSearch[b](a);else if(b=="html")return a.replace(/\n/g," ").replace(/<.*?>/g,"");else if(typeof a=="string")return a.replace(/\n/g," ");return a}function O(a,b){var c,d,f,e,i,h,k=[],l=[],q=n.oSort,t=a.aoData,G=a.aoColumns;if(!a.oFeatures.bServerSide&&(a.aaSorting.length!==0||a.aaSortingFixed!==null)){k=a.aaSortingFixed!==null?a.aaSortingFixed.concat(a.aaSorting):a.aaSorting.slice();for(f=0;f<k.length;f++){e=k[f][0];i=M(a,e);h=a.aoColumns[e].sSortDataType;if(typeof n.afnSortData[h]!=
"undefined"){var J=n.afnSortData[h](a,e,i);i=0;for(h=t.length;i<h;i++)t[i]._aData[e]=J[i]}}f=0;for(e=a.aiDisplayMaster.length;f<e;f++)l[a.aiDisplayMaster[f]]=f;var B=k.length;a.aiDisplayMaster.sort(function(I,Y){var N;for(f=0;f<B;f++){c=G[k[f][0]].iDataSort;d=G[c].sType;N=q[d+"-"+k[f][1]](t[I]._aData[c],t[Y]._aData[c]);if(N!==0)return N}return q["numeric-asc"](l[I],l[Y])})}if(typeof b=="undefined"||b)T(a);a.bSorted=true;if(a.oFeatures.bFilter)P(a,a.oPreviousSearch,1);else{a.aiDisplay=a.aiDisplayMaster.slice();
a._iDisplayStart=0;E(a);C(a)}}function ba(a,b,c,d){j(b).bind("click.DT",function(f){if(a.aoColumns[c].bSortable!==false){var e=function(){var i,h;if(f.shiftKey){for(var k=false,l=0;l<a.aaSorting.length;l++)if(a.aaSorting[l][0]==c){k=true;i=a.aaSorting[l][0];h=a.aaSorting[l][2]+1;if(typeof a.aoColumns[i].asSorting[h]=="undefined")a.aaSorting.splice(l,1);else{a.aaSorting[l][1]=a.aoColumns[i].asSorting[h];a.aaSorting[l][2]=h}break}k===false&&a.aaSorting.push([c,a.aoColumns[c].asSorting[0],0])}else if(a.aaSorting.length==
1&&a.aaSorting[0][0]==c){i=a.aaSorting[0][0];h=a.aaSorting[0][2]+1;if(typeof a.aoColumns[i].asSorting[h]=="undefined")h=0;a.aaSorting[0][1]=a.aoColumns[i].asSorting[h];a.aaSorting[0][2]=h}else{a.aaSorting.splice(0,a.aaSorting.length);a.aaSorting.push([c,a.aoColumns[c].asSorting[0],0])}O(a)};if(a.oFeatures.bProcessing){K(a,true);setTimeout(function(){e();a.oFeatures.bServerSide||K(a,false)},0)}else e();typeof d=="function"&&d(a)}})}function T(a){var b,c,d,f,e,i=a.aoColumns.length,h=a.oClasses;for(b=
0;b<i;b++)a.aoColumns[b].bSortable&&j(a.aoColumns[b].nTh).removeClass(h.sSortAsc+" "+h.sSortDesc+" "+a.aoColumns[b].sSortingClass);f=a.aaSortingFixed!==null?a.aaSortingFixed.concat(a.aaSorting):a.aaSorting.slice();for(b=0;b<a.aoColumns.length;b++)if(a.aoColumns[b].bSortable){e=a.aoColumns[b].sSortingClass;d=-1;for(c=0;c<f.length;c++)if(f[c][0]==b){e=f[c][1]=="asc"?h.sSortAsc:h.sSortDesc;d=c;break}j(a.aoColumns[b].nTh).addClass(e);if(a.bJUI){c=j("span",a.aoColumns[b].nTh);c.removeClass(h.sSortJUIAsc+
" "+h.sSortJUIDesc+" "+h.sSortJUI+" "+h.sSortJUIAscAllowed+" "+h.sSortJUIDescAllowed);c.addClass(d==-1?a.aoColumns[b].sSortingClassJUI:f[d][1]=="asc"?h.sSortJUIAsc:h.sSortJUIDesc)}}else j(a.aoColumns[b].nTh).addClass(a.aoColumns[b].sSortingClass);e=h.sSortColumn;if(a.oFeatures.bSort&&a.oFeatures.bSortClasses){d=Z(a);if(d.length>=i)for(b=0;b<i;b++)if(d[b].className.indexOf(e+"1")!=-1){c=0;for(a=d.length/i;c<a;c++)d[i*c+b].className=j.trim(d[i*c+b].className.replace(e+"1",""))}else if(d[b].className.indexOf(e+
"2")!=-1){c=0;for(a=d.length/i;c<a;c++)d[i*c+b].className=j.trim(d[i*c+b].className.replace(e+"2",""))}else if(d[b].className.indexOf(e+"3")!=-1){c=0;for(a=d.length/i;c<a;c++)d[i*c+b].className=j.trim(d[i*c+b].className.replace(" "+e+"3",""))}h=1;var k;for(b=0;b<f.length;b++){k=parseInt(f[b][0],10);c=0;for(a=d.length/i;c<a;c++)d[i*c+k].className+=" "+e+h;h<3&&h++}}}function Ba(a){if(a.oScroll.bInfinite)return null;var b=p.createElement("div");b.className=a.oClasses.sPaging+a.sPaginationType;n.oPagination[a.sPaginationType].fnInit(a,
b,function(c){E(c);C(c)});typeof a.aanFeatures.p=="undefined"&&a.aoDrawCallback.push({fn:function(c){n.oPagination[c.sPaginationType].fnUpdate(c,function(d){E(d);C(d)})},sName:"pagination"});return b}function ea(a,b){var c=a._iDisplayStart;if(b=="first")a._iDisplayStart=0;else if(b=="previous"){a._iDisplayStart=a._iDisplayLength>=0?a._iDisplayStart-a._iDisplayLength:0;if(a._iDisplayStart<0)a._iDisplayStart=0}else if(b=="next")if(a._iDisplayLength>=0){if(a._iDisplayStart+a._iDisplayLength<a.fnRecordsDisplay())a._iDisplayStart+=
a._iDisplayLength}else a._iDisplayStart=0;else if(b=="last")if(a._iDisplayLength>=0){b=parseInt((a.fnRecordsDisplay()-1)/a._iDisplayLength,10)+1;a._iDisplayStart=(b-1)*a._iDisplayLength}else a._iDisplayStart=0;else H(a,0,"Unknown paging action: "+b);return c!=a._iDisplayStart}function Aa(a){var b=p.createElement("div");b.className=a.oClasses.sInfo;if(typeof a.aanFeatures.i=="undefined"){a.aoDrawCallback.push({fn:Ga,sName:"information"});a.sTableId!==""&&b.setAttribute("id",a.sTableId+"_info")}return b}
function Ga(a){if(!(!a.oFeatures.bInfo||a.aanFeatures.i.length===0)){var b=a._iDisplayStart+1,c=a.fnDisplayEnd(),d=a.fnRecordsTotal(),f=a.fnRecordsDisplay(),e=a.fnFormatNumber(b),i=a.fnFormatNumber(c),h=a.fnFormatNumber(d),k=a.fnFormatNumber(f);if(a.oScroll.bInfinite)e=a.fnFormatNumber(1);e=a.fnRecordsDisplay()===0&&a.fnRecordsDisplay()==a.fnRecordsTotal()?a.oLanguage.sInfoEmpty+a.oLanguage.sInfoPostFix:a.fnRecordsDisplay()===0?a.oLanguage.sInfoEmpty+" "+a.oLanguage.sInfoFiltered.replace("_MAX_",
h)+a.oLanguage.sInfoPostFix:a.fnRecordsDisplay()==a.fnRecordsTotal()?a.oLanguage.sInfo.replace("_START_",e).replace("_END_",i).replace("_TOTAL_",k)+a.oLanguage.sInfoPostFix:a.oLanguage.sInfo.replace("_START_",e).replace("_END_",i).replace("_TOTAL_",k)+" "+a.oLanguage.sInfoFiltered.replace("_MAX_",a.fnFormatNumber(a.fnRecordsTotal()))+a.oLanguage.sInfoPostFix;if(a.oLanguage.fnInfoCallback!==null)e=a.oLanguage.fnInfoCallback(a,b,c,d,f,e);a=a.aanFeatures.i;b=0;for(c=a.length;b<c;b++)j(a[b]).html(e)}}
function wa(a){if(a.oScroll.bInfinite)return null;var b='<select size="1" '+(a.sTableId===""?"":'name="'+a.sTableId+'_length"')+">",c,d;if(a.aLengthMenu.length==2&&typeof a.aLengthMenu[0]=="object"&&typeof a.aLengthMenu[1]=="object"){c=0;for(d=a.aLengthMenu[0].length;c<d;c++)b+='<option value="'+a.aLengthMenu[0][c]+'">'+a.aLengthMenu[1][c]+"</option>"}else{c=0;for(d=a.aLengthMenu.length;c<d;c++)b+='<option value="'+a.aLengthMenu[c]+'">'+a.aLengthMenu[c]+"</option>"}b+="</select>";var f=p.createElement("div");
a.sTableId!==""&&typeof a.aanFeatures.l=="undefined"&&f.setAttribute("id",a.sTableId+"_length");f.className=a.oClasses.sLength;f.innerHTML=a.oLanguage.sLengthMenu.replace("_MENU_",b);j('select option[value="'+a._iDisplayLength+'"]',f).attr("selected",true);j("select",f).bind("change.DT",function(){var e=j(this).val(),i=a.aanFeatures.l;c=0;for(d=i.length;c<d;c++)i[c]!=this.parentNode&&j("select",i[c]).val(e);a._iDisplayLength=parseInt(e,10);E(a);if(a.fnDisplayEnd()==a.fnRecordsDisplay()){a._iDisplayStart=
a.fnDisplayEnd()-a._iDisplayLength;if(a._iDisplayStart<0)a._iDisplayStart=0}if(a._iDisplayLength==-1)a._iDisplayStart=0;C(a)});return f}function ya(a){var b=p.createElement("div");a.sTableId!==""&&typeof a.aanFeatures.r=="undefined"&&b.setAttribute("id",a.sTableId+"_processing");b.innerHTML=a.oLanguage.sProcessing;b.className=a.oClasses.sProcessing;a.nTable.parentNode.insertBefore(b,a.nTable);return b}function K(a,b){if(a.oFeatures.bProcessing){a=a.aanFeatures.r;for(var c=0,d=a.length;c<d;c++)a[c].style.visibility=
b?"visible":"hidden"}}function ga(a,b){for(var c=-1,d=0;d<a.aoColumns.length;d++){a.aoColumns[d].bVisible===true&&c++;if(c==b)return d}return null}function M(a,b){for(var c=-1,d=0;d<a.aoColumns.length;d++){a.aoColumns[d].bVisible===true&&c++;if(d==b)return a.aoColumns[d].bVisible===true?c:null}return null}function Q(a,b){var c,d;c=a._iDisplayStart;for(d=a._iDisplayEnd;c<d;c++)if(a.aoData[a.aiDisplay[c]].nTr==b)return a.aiDisplay[c];c=0;for(d=a.aoData.length;c<d;c++)if(a.aoData[c].nTr==b)return c;
return null}function S(a){for(var b=0,c=0;c<a.aoColumns.length;c++)a.aoColumns[c].bVisible===true&&b++;return b}function E(a){a._iDisplayEnd=a.oFeatures.bPaginate===false?a.aiDisplay.length:a._iDisplayStart+a._iDisplayLength>a.aiDisplay.length||a._iDisplayLength==-1?a.aiDisplay.length:a._iDisplayStart+a._iDisplayLength}function Ha(a,b){if(!a||a===null||a==="")return 0;if(typeof b=="undefined")b=p.getElementsByTagName("body")[0];var c=p.createElement("div");c.style.width=a;b.appendChild(c);a=c.offsetWidth;
b.removeChild(c);return a}function $(a){var b=0,c,d=0,f=a.aoColumns.length,e,i=j("th",a.nTHead);for(e=0;e<f;e++)if(a.aoColumns[e].bVisible){d++;if(a.aoColumns[e].sWidth!==null){c=Ha(a.aoColumns[e].sWidthOrig,a.nTable.parentNode);if(c!==null)a.aoColumns[e].sWidth=v(c);b++}}if(f==i.length&&b===0&&d==f&&a.oScroll.sX===""&&a.oScroll.sY==="")for(e=0;e<a.aoColumns.length;e++){c=j(i[e]).width();if(c!==null)a.aoColumns[e].sWidth=v(c)}else{b=a.nTable.cloneNode(false);e=p.createElement("tbody");c=p.createElement("tr");
b.removeAttribute("id");b.appendChild(a.nTHead.cloneNode(true));if(a.nTFoot!==null){b.appendChild(a.nTFoot.cloneNode(true));L(function(h){h.style.width=""},b.getElementsByTagName("tr"))}b.appendChild(e);e.appendChild(c);e=j("thead th",b);if(e.length===0)e=j("tbody tr:eq(0)>td",b);e.each(function(h){this.style.width="";h=ga(a,h);if(h!==null&&a.aoColumns[h].sWidthOrig!=="")this.style.width=a.aoColumns[h].sWidthOrig});for(e=0;e<f;e++)if(a.aoColumns[e].bVisible){d=Ia(a,e);if(d!==null){d=d.cloneNode(true);
c.appendChild(d)}}e=a.nTable.parentNode;e.appendChild(b);if(a.oScroll.sX!==""&&a.oScroll.sXInner!=="")b.style.width=v(a.oScroll.sXInner);else if(a.oScroll.sX!==""){b.style.width="";if(j(b).width()<e.offsetWidth)b.style.width=v(e.offsetWidth)}else if(a.oScroll.sY!=="")b.style.width=v(e.offsetWidth);b.style.visibility="hidden";Ja(a,b);f=j("tbody tr:eq(0)>td",b);if(f.length===0)f=j("thead tr:eq(0)>th",b);for(e=c=0;e<a.aoColumns.length;e++)if(a.aoColumns[e].bVisible){d=j(f[c]).outerWidth();if(d!==null&&
d>0)a.aoColumns[e].sWidth=v(d);c++}a.nTable.style.width=v(j(b).outerWidth());b.parentNode.removeChild(b)}}function Ja(a,b){if(a.oScroll.sX===""&&a.oScroll.sY!==""){j(b).width();b.style.width=v(j(b).outerWidth()-a.oScroll.iBarWidth)}else if(a.oScroll.sX!=="")b.style.width=v(j(b).outerWidth())}function Ia(a,b,c){if(typeof c=="undefined"||c){c=Ka(a,b);b=M(a,b);if(c<0)return null;return a.aoData[c].nTr.getElementsByTagName("td")[b]}var d=-1,f,e;c=-1;var i=p.createElement("div");i.style.visibility="hidden";
i.style.position="absolute";p.body.appendChild(i);f=0;for(e=a.aoData.length;f<e;f++){i.innerHTML=a.aoData[f]._aData[b];if(i.offsetWidth>d){d=i.offsetWidth;c=f}}p.body.removeChild(i);if(c>=0){b=M(a,b);if(a=a.aoData[c].nTr.getElementsByTagName("td")[b])return a}return null}function Ka(a,b){for(var c=-1,d=-1,f=0;f<a.aoData.length;f++){var e=a.aoData[f]._aData[b];if(e.length>c){c=e.length;d=f}}return d}function v(a){if(a===null)return"0px";if(typeof a=="number"){if(a<0)return"0px";return a+"px"}var b=
a.charCodeAt(a.length-1);if(b<48||b>57)return a;return a+"px"}function Oa(a,b){if(a.length!=b.length)return 1;for(var c=0;c<a.length;c++)if(a[c]!=b[c])return 2;return 0}function aa(a){for(var b=n.aTypes,c=b.length,d=0;d<c;d++){var f=b[d](a);if(f!==null)return f}return"string"}function A(a){for(var b=0;b<D.length;b++)if(D[b].nTable==a)return D[b];return null}function V(a){for(var b=[],c=a.aoData.length,d=0;d<c;d++)b.push(a.aoData[d]._aData);return b}function R(a){for(var b=[],c=a.aoData.length,d=0;d<
c;d++)b.push(a.aoData[d].nTr);return b}function Z(a){var b=R(a),c=[],d,f=[],e,i,h,k;e=0;for(i=b.length;e<i;e++){c=[];h=0;for(k=b[e].childNodes.length;h<k;h++){d=b[e].childNodes[h];d.nodeName.toUpperCase()=="TD"&&c.push(d)}h=d=0;for(k=a.aoColumns.length;h<k;h++)if(a.aoColumns[h].bVisible)f.push(c[h-d]);else{f.push(a.aoData[e]._anHidden[h]);d++}}return f}function la(a){return a.replace(new RegExp("(\\/|\\.|\\*|\\+|\\?|\\||\\(|\\)|\\[|\\]|\\{|\\}|\\\\|\\$|\\^)","g"),"\\$1")}function ma(a,b){for(var c=
-1,d=0,f=a.length;d<f;d++)if(a[d]==b)c=d;else a[d]>b&&a[d]--;c!=-1&&a.splice(c,1)}function va(a,b){b=b.split(",");for(var c=[],d=0,f=a.aoColumns.length;d<f;d++)for(var e=0;e<f;e++)if(a.aoColumns[d].sName==b[e]){c.push(e);break}return c}function ca(a){for(var b="",c=0,d=a.aoColumns.length;c<d;c++)b+=a.aoColumns[c].sName+",";if(b.length==d)return"";return b.slice(0,-1)}function H(a,b,c){a=a.sTableId===""?"DataTables warning: "+c:"DataTables warning (table id = '"+a.sTableId+"'): "+c;if(b===0)if(n.sErrMode==
"alert")alert(a);else throw a;else typeof console!="undefined"&&typeof console.log!="undefined"&&console.log(a)}function da(a){a.aoData.splice(0,a.aoData.length);a.aiDisplayMaster.splice(0,a.aiDisplayMaster.length);a.aiDisplay.splice(0,a.aiDisplay.length);E(a)}function na(a){if(!(!a.oFeatures.bStateSave||typeof a.bDestroying!="undefined")){var b,c,d,f="{";f+='"iCreate":'+(new Date).getTime()+",";f+='"iStart":'+a._iDisplayStart+",";f+='"iEnd":'+a._iDisplayEnd+",";f+='"iLength":'+a._iDisplayLength+
",";f+='"sFilter":"'+encodeURIComponent(a.oPreviousSearch.sSearch)+'",';f+='"sFilterEsc":'+!a.oPreviousSearch.bRegex+",";f+='"aaSorting":[ ';for(b=0;b<a.aaSorting.length;b++)f+="["+a.aaSorting[b][0]+',"'+a.aaSorting[b][1]+'"],';f=f.substring(0,f.length-1);f+="],";f+='"aaSearchCols":[ ';for(b=0;b<a.aoPreSearchCols.length;b++)f+='["'+encodeURIComponent(a.aoPreSearchCols[b].sSearch)+'",'+!a.aoPreSearchCols[b].bRegex+"],";f=f.substring(0,f.length-1);f+="],";f+='"abVisCols":[ ';for(b=0;b<a.aoColumns.length;b++)f+=
a.aoColumns[b].bVisible+",";f=f.substring(0,f.length-1);f+="]";b=0;for(c=a.aoStateSave.length;b<c;b++){d=a.aoStateSave[b].fn(a,f);if(d!=="")f=d}f+="}";La(a.sCookiePrefix+a.sInstance,f,a.iCookieDuration,a.sCookiePrefix,a.fnCookieCallback)}}function Ma(a,b){if(a.oFeatures.bStateSave){var c,d,f;d=oa(a.sCookiePrefix+a.sInstance);if(d!==null&&d!==""){try{c=typeof j.parseJSON=="function"?j.parseJSON(d.replace(/'/g,'"')):eval("("+d+")")}catch(e){return}d=0;for(f=a.aoStateLoad.length;d<f;d++)if(!a.aoStateLoad[d].fn(a,
c))return;a.oLoadedState=j.extend(true,{},c);a._iDisplayStart=c.iStart;a.iInitDisplayStart=c.iStart;a._iDisplayEnd=c.iEnd;a._iDisplayLength=c.iLength;a.oPreviousSearch.sSearch=decodeURIComponent(c.sFilter);a.aaSorting=c.aaSorting.slice();a.saved_aaSorting=c.aaSorting.slice();if(typeof c.sFilterEsc!="undefined")a.oPreviousSearch.bRegex=!c.sFilterEsc;if(typeof c.aaSearchCols!="undefined")for(d=0;d<c.aaSearchCols.length;d++)a.aoPreSearchCols[d]={sSearch:decodeURIComponent(c.aaSearchCols[d][0]),bRegex:!c.aaSearchCols[d][1]};
if(typeof c.abVisCols!="undefined"){b.saved_aoColumns=[];for(d=0;d<c.abVisCols.length;d++){b.saved_aoColumns[d]={};b.saved_aoColumns[d].bVisible=c.abVisCols[d]}}}}}function La(a,b,c,d,f){var e=new Date;e.setTime(e.getTime()+c*1E3);c=ra.location.pathname.split("/");a=a+"_"+c.pop().replace(/[\/:]/g,"").toLowerCase();var i;if(f!==null){i=typeof j.parseJSON=="function"?j.parseJSON(b):eval("("+b+")");b=f(a,i,e.toGMTString(),c.join("/")+"/")}else b=a+"="+encodeURIComponent(b)+"; expires="+e.toGMTString()+
"; path="+c.join("/")+"/";f="";e=9999999999999;if((oa(a)!==null?p.cookie.length:b.length+p.cookie.length)+10>4096){a=p.cookie.split(";");for(var h=0,k=a.length;h<k;h++)if(a[h].indexOf(d)!=-1){var l=a[h].split("=");try{i=eval("("+decodeURIComponent(l[1])+")")}catch(q){continue}if(typeof i.iCreate!="undefined"&&i.iCreate<e){f=l[0];e=i.iCreate}}if(f!=="")p.cookie=f+"=; expires=Thu, 01-Jan-1970 00:00:01 GMT; path="+c.join("/")+"/"}p.cookie=b}function oa(a){var b=ra.location.pathname.split("/");a=a+"_"+
b[b.length-1].replace(/[\/:]/g,"").toLowerCase()+"=";b=p.cookie.split(";");for(var c=0;c<b.length;c++){for(var d=b[c];d.charAt(0)==" ";)d=d.substring(1,d.length);if(d.indexOf(a)===0)return decodeURIComponent(d.substring(a.length,d.length))}return null}function fa(a){a=a.getElementsByTagName("tr");if(a.length==1)return a[0].getElementsByTagName("th");var b=[],c=[],d,f,e,i,h,k,l=function(I,Y,N){for(;typeof I[Y][N]!="undefined";)N++;return N},q=function(I){if(typeof b[I]=="undefined")b[I]=[]};d=0;for(i=
a.length;d<i;d++){q(d);var t=0,G=[];f=0;for(h=a[d].childNodes.length;f<h;f++)if(a[d].childNodes[f].nodeName.toUpperCase()=="TD"||a[d].childNodes[f].nodeName.toUpperCase()=="TH")G.push(a[d].childNodes[f]);f=0;for(h=G.length;f<h;f++){var J=G[f].getAttribute("colspan")*1,B=G[f].getAttribute("rowspan")*1;if(!J||J===0||J===1){k=l(b,d,t);b[d][k]=G[f].nodeName.toUpperCase()=="TD"?4:G[f];if(B||B===0||B===1)for(e=1;e<B;e++){q(d+e);b[d+e][k]=2}t++}else{k=l(b,d,t);for(e=0;e<J;e++)b[d][k+e]=3;t+=J}}}d=0;for(i=
b.length;d<i;d++){f=0;for(h=b[d].length;f<h;f++)if(typeof b[d][f]=="object"&&typeof c[f]=="undefined")c[f]=b[d][f]}return c}function Na(){var a=p.createElement("p"),b=a.style;b.width="100%";b.height="200px";var c=p.createElement("div");b=c.style;b.position="absolute";b.top="0px";b.left="0px";b.visibility="hidden";b.width="200px";b.height="150px";b.overflow="hidden";c.appendChild(a);p.body.appendChild(c);b=a.offsetWidth;c.style.overflow="scroll";a=a.offsetWidth;if(b==a)a=c.clientWidth;p.body.removeChild(c);
return b-a}function L(a,b,c){for(var d=0,f=b.length;d<f;d++)for(var e=0,i=b[d].childNodes.length;e<i;e++)if(b[d].childNodes[e].nodeType==1)typeof c!="undefined"?a(b[d].childNodes[e],c[d].childNodes[e]):a(b[d].childNodes[e])}function o(a,b,c,d){if(typeof d=="undefined")d=c;if(typeof b[c]!="undefined")a[d]=b[c]}this.oApi={};this.fnDraw=function(a){var b=A(this[n.iApiIndex]);if(typeof a!="undefined"&&a===false){E(b);C(b)}else W(b)};this.fnFilter=function(a,b,c,d,f){var e=A(this[n.iApiIndex]);if(e.oFeatures.bFilter){if(typeof c==
"undefined")c=false;if(typeof d=="undefined")d=true;if(typeof f=="undefined")f=true;if(typeof b=="undefined"||b===null){P(e,{sSearch:a,bRegex:c,bSmart:d},1);if(f&&typeof e.aanFeatures.f!="undefined"){b=e.aanFeatures.f;c=0;for(d=b.length;c<d;c++)j("input",b[c]).val(a)}}else{e.aoPreSearchCols[b].sSearch=a;e.aoPreSearchCols[b].bRegex=c;e.aoPreSearchCols[b].bSmart=d;P(e,e.oPreviousSearch,1)}}};this.fnSettings=function(){return A(this[n.iApiIndex])};this.fnVersionCheck=n.fnVersionCheck;this.fnSort=function(a){var b=
A(this[n.iApiIndex]);b.aaSorting=a;O(b)};this.fnSortListener=function(a,b,c){ba(A(this[n.iApiIndex]),a,b,c)};this.fnAddData=function(a,b){if(a.length===0)return[];var c=[],d,f=A(this[n.iApiIndex]);if(typeof a[0]=="object")for(var e=0;e<a.length;e++){d=u(f,a[e]);if(d==-1)return c;c.push(d)}else{d=u(f,a);if(d==-1)return c;c.push(d)}f.aiDisplay=f.aiDisplayMaster.slice();if(typeof b=="undefined"||b)W(f);return c};this.fnDeleteRow=function(a,b,c){var d=A(this[n.iApiIndex]);a=typeof a=="object"?Q(d,a):
a;var f=d.aoData.splice(a,1),e=j.inArray(a,d.aiDisplay);d.asDataSearch.splice(e,1);ma(d.aiDisplayMaster,a);ma(d.aiDisplay,a);typeof b=="function"&&b.call(this,d,f);if(d._iDisplayStart>=d.aiDisplay.length){d._iDisplayStart-=d._iDisplayLength;if(d._iDisplayStart<0)d._iDisplayStart=0}if(typeof c=="undefined"||c){E(d);C(d)}return f};this.fnClearTable=function(a){var b=A(this[n.iApiIndex]);da(b);if(typeof a=="undefined"||a)C(b)};this.fnOpen=function(a,b,c){var d=A(this[n.iApiIndex]);this.fnClose(a);var f=
p.createElement("tr"),e=p.createElement("td");f.appendChild(e);e.className=c;e.colSpan=S(d);e.innerHTML=b;b=j("tr",d.nTBody);j.inArray(a,b)!=-1&&j(f).insertAfter(a);d.aoOpenRows.push({nTr:f,nParent:a});return f};this.fnClose=function(a){for(var b=A(this[n.iApiIndex]),c=0;c<b.aoOpenRows.length;c++)if(b.aoOpenRows[c].nParent==a){(a=b.aoOpenRows[c].nTr.parentNode)&&a.removeChild(b.aoOpenRows[c].nTr);b.aoOpenRows.splice(c,1);return 0}return 1};this.fnGetData=function(a){var b=A(this[n.iApiIndex]);if(typeof a!=
"undefined"){a=typeof a=="object"?Q(b,a):a;return(aRowData=b.aoData[a])?aRowData._aData:null}return V(b)};this.fnGetNodes=function(a){var b=A(this[n.iApiIndex]);if(typeof a!="undefined")return(aRowData=b.aoData[a])?aRowData.nTr:null;return R(b)};this.fnGetPosition=function(a){var b=A(this[n.iApiIndex]);if(a.nodeName.toUpperCase()=="TR")return Q(b,a);else if(a.nodeName.toUpperCase()=="TD")for(var c=Q(b,a.parentNode),d=0,f=0;f<b.aoColumns.length;f++)if(b.aoColumns[f].bVisible){if(b.aoData[c].nTr.getElementsByTagName("td")[f-
d]==a)return[c,f-d,f]}else d++;return null};this.fnUpdate=function(a,b,c,d,f){var e=A(this[n.iApiIndex]),i,h;b=typeof b=="object"?Q(e,b):b;if(typeof a!="object"){h=a;e.aoData[b]._aData[c]=h;if(e.aoColumns[c].fnRender!==null){h=e.aoColumns[c].fnRender({iDataRow:b,iDataColumn:c,aData:e.aoData[b]._aData,oSettings:e});if(e.aoColumns[c].bUseRendered)e.aoData[b]._aData[c]=h}i=M(e,c);if(i!==null)e.aoData[b].nTr.getElementsByTagName("td")[i].innerHTML=h;else e.aoData[b]._anHidden[c].innerHTML=h}else{if(a.length!=
e.aoColumns.length){H(e,0,"An array passed to fnUpdate must have the same number of columns as the table in question - in this case "+e.aoColumns.length);return 1}for(c=0;c<a.length;c++){h=a[c];e.aoData[b]._aData[c]=h;if(e.aoColumns[c].fnRender!==null){h=e.aoColumns[c].fnRender({iDataRow:b,iDataColumn:c,aData:e.aoData[b]._aData,oSettings:e});if(e.aoColumns[c].bUseRendered)e.aoData[b]._aData[c]=h}i=M(e,c);if(i!==null)e.aoData[b].nTr.getElementsByTagName("td")[i].innerHTML=h;else e.aoData[b]._anHidden[c].innerHTML=
h}}a=j.inArray(b,e.aiDisplay);e.asDataSearch[a]=ka(e,e.aoData[b]._aData);if(typeof f=="undefined"||f)X(e);if(typeof d=="undefined"||d)W(e);return 0};this.fnSetColumnVis=function(a,b,c){var d=A(this[n.iApiIndex]),f,e;e=d.aoColumns.length;var i,h,k,l,q;if(d.aoColumns[a].bVisible!=b){l=j(">tr",d.nTHead)[0];i=j(">tr",d.nTFoot)[0];q=[];h=[];for(f=0;f<e;f++){q.push(d.aoColumns[f].nTh);h.push(d.aoColumns[f].nTf)}if(b){for(f=b=0;f<a;f++)d.aoColumns[f].bVisible&&b++;if(b>=S(d)){l.appendChild(q[a]);l=j(">tr",
d.nTHead);f=1;for(e=l.length;f<e;f++)l[f].appendChild(d.aoColumns[a].anThExtra[f-1]);if(i){i.appendChild(h[a]);l=j(">tr",d.nTFoot);f=1;for(e=l.length;f<e;f++)l[f].appendChild(d.aoColumns[a].anTfExtra[f-1])}f=0;for(e=d.aoData.length;f<e;f++){i=d.aoData[f]._anHidden[a];d.aoData[f].nTr.appendChild(i)}}else{for(f=a;f<e;f++){k=M(d,f);if(k!==null)break}l.insertBefore(q[a],l.getElementsByTagName("th")[k]);l=j(">tr",d.nTHead);f=1;for(e=l.length;f<e;f++){q=j(l[f]).children();l[f].insertBefore(d.aoColumns[a].anThExtra[f-
1],q[k])}if(i){i.insertBefore(h[a],i.getElementsByTagName("th")[k]);l=j(">tr",d.nTFoot);f=1;for(e=l.length;f<e;f++){q=j(l[f]).children();l[f].insertBefore(d.aoColumns[a].anTfExtra[f-1],q[k])}}Z(d);f=0;for(e=d.aoData.length;f<e;f++){i=d.aoData[f]._anHidden[a];d.aoData[f].nTr.insertBefore(i,j(">td:eq("+k+")",d.aoData[f].nTr)[0])}}d.aoColumns[a].bVisible=true}else{l.removeChild(q[a]);f=0;for(e=d.aoColumns[a].anThExtra.length;f<e;f++){k=d.aoColumns[a].anThExtra[f];k.parentNode.removeChild(k)}if(i){i.removeChild(h[a]);
f=0;for(e=d.aoColumns[a].anTfExtra.length;f<e;f++){k=d.aoColumns[a].anTfExtra[f];k.parentNode.removeChild(k)}}h=Z(d);f=0;for(e=d.aoData.length;f<e;f++){i=h[f*d.aoColumns.length+a*1];d.aoData[f]._anHidden[a]=i;i.parentNode.removeChild(i)}d.aoColumns[a].bVisible=false}f=0;for(e=d.aoOpenRows.length;f<e;f++)d.aoOpenRows[f].nTr.colSpan=S(d);if(typeof c=="undefined"||c){X(d);C(d)}na(d)}};this.fnPageChange=function(a,b){var c=A(this[n.iApiIndex]);ea(c,a);E(c);if(typeof b=="undefined"||b)C(c)};this.fnDestroy=
function(){var a=A(this[n.iApiIndex]),b=a.nTableWrapper.parentNode,c=a.nTBody,d,f;a.bDestroying=true;j(a.nTableWrapper).find("*").andSelf().unbind(".DT");d=0;for(f=a.aoColumns.length;d<f;d++)a.aoColumns[d].bVisible===false&&this.fnSetColumnVis(d,true);j("tbody>tr>td."+a.oClasses.sRowEmpty,a.nTable).parent().remove();if(a.nTable!=a.nTHead.parentNode){j(">thead",a.nTable).remove();a.nTable.appendChild(a.nTHead)}if(a.nTFoot&&a.nTable!=a.nTFoot.parentNode){j(">tfoot",a.nTable).remove();a.nTable.appendChild(a.nTFoot)}a.nTable.parentNode.removeChild(a.nTable);
j(a.nTableWrapper).remove();a.aaSorting=[];a.aaSortingFixed=[];T(a);j(R(a)).removeClass(a.asStripClasses.join(" "));if(a.bJUI){j("th",a.nTHead).removeClass([n.oStdClasses.sSortable,n.oJUIClasses.sSortableAsc,n.oJUIClasses.sSortableDesc,n.oJUIClasses.sSortableNone].join(" "));j("th span",a.nTHead).remove()}else j("th",a.nTHead).removeClass([n.oStdClasses.sSortable,n.oStdClasses.sSortableAsc,n.oStdClasses.sSortableDesc,n.oStdClasses.sSortableNone].join(" "));b.appendChild(a.nTable);d=0;for(f=a.aoData.length;d<
f;d++)c.appendChild(a.aoData[d].nTr);a.nTable.style.width=v(a.sDestroyWidth);j(">tr:even",c).addClass(a.asDestoryStrips[0]);j(">tr:odd",c).addClass(a.asDestoryStrips[1]);d=0;for(f=D.length;d<f;d++)D[d]==a&&D.splice(d,1)};this.fnAdjustColumnSizing=function(a){var b=A(this[n.iApiIndex]);X(b);if(typeof a=="undefined"||a)this.fnDraw(false);else if(b.oScroll.sX!==""||b.oScroll.sY!=="")this.oApi._fnScrollDraw(b)};for(var pa in n.oApi)if(pa)this[pa]=r(pa);this.oApi._fnExternApiFunc=r;this.oApi._fnInitalise=
s;this.oApi._fnLanguageProcess=y;this.oApi._fnAddColumn=F;this.oApi._fnColumnOptions=x;this.oApi._fnAddData=u;this.oApi._fnGatherData=z;this.oApi._fnDrawHead=U;this.oApi._fnDraw=C;this.oApi._fnReDraw=W;this.oApi._fnAjaxUpdate=ta;this.oApi._fnAjaxUpdateDraw=ua;this.oApi._fnAddOptionsHtml=sa;this.oApi._fnFeatureHtmlTable=za;this.oApi._fnScrollDraw=Ca;this.oApi._fnAjustColumnSizing=X;this.oApi._fnFeatureHtmlFilter=xa;this.oApi._fnFilterComplete=P;this.oApi._fnFilterCustom=Fa;this.oApi._fnFilterColumn=
Ea;this.oApi._fnFilter=Da;this.oApi._fnBuildSearchArray=ha;this.oApi._fnBuildSearchRow=ka;this.oApi._fnFilterCreateSearch=ia;this.oApi._fnDataToSearch=ja;this.oApi._fnSort=O;this.oApi._fnSortAttachListener=ba;this.oApi._fnSortingClasses=T;this.oApi._fnFeatureHtmlPaginate=Ba;this.oApi._fnPageChange=ea;this.oApi._fnFeatureHtmlInfo=Aa;this.oApi._fnUpdateInfo=Ga;this.oApi._fnFeatureHtmlLength=wa;this.oApi._fnFeatureHtmlProcessing=ya;this.oApi._fnProcessingDisplay=K;this.oApi._fnVisibleToColumnIndex=ga;
this.oApi._fnColumnIndexToVisible=M;this.oApi._fnNodeToDataIndex=Q;this.oApi._fnVisbleColumns=S;this.oApi._fnCalculateEnd=E;this.oApi._fnConvertToWidth=Ha;this.oApi._fnCalculateColumnWidths=$;this.oApi._fnScrollingWidthAdjust=Ja;this.oApi._fnGetWidestNode=Ia;this.oApi._fnGetMaxLenString=Ka;this.oApi._fnStringToCss=v;this.oApi._fnArrayCmp=Oa;this.oApi._fnDetectType=aa;this.oApi._fnSettingsFromNode=A;this.oApi._fnGetDataMaster=V;this.oApi._fnGetTrNodes=R;this.oApi._fnGetTdNodes=Z;this.oApi._fnEscapeRegex=
la;this.oApi._fnDeleteIndex=ma;this.oApi._fnReOrderIndex=va;this.oApi._fnColumnOrdering=ca;this.oApi._fnLog=H;this.oApi._fnClearTable=da;this.oApi._fnSaveState=na;this.oApi._fnLoadState=Ma;this.oApi._fnCreateCookie=La;this.oApi._fnReadCookie=oa;this.oApi._fnGetUniqueThs=fa;this.oApi._fnScrollBarWidth=Na;this.oApi._fnApplyToChildren=L;this.oApi._fnMap=o;var qa=this;return this.each(function(){var a=0,b,c,d,f;a=0;for(b=D.length;a<b;a++){if(D[a].nTable==this)if(typeof g=="undefined"||typeof g.bRetrieve!=
"undefined"&&g.bRetrieve===true)return D[a].oInstance;else if(typeof g.bDestroy!="undefined"&&g.bDestroy===true){D[a].oInstance.fnDestroy();break}else{H(D[a],0,"Cannot reinitialise DataTable.\n\nTo retrieve the DataTables object for this table, please pass either no arguments to the dataTable() function, or set bRetrieve to true. Alternatively, to destory the old table and create a new one, set bDestroy to true (note that a lot of changes to the configuration can be made through the API which is usually much faster).");
return}if(D[a].sTableId!==""&&D[a].sTableId==this.getAttribute("id")){D.splice(a,1);break}}var e=new m;D.push(e);var i=false,h=false;a=this.getAttribute("id");if(a!==null){e.sTableId=a;e.sInstance=a}else e.sInstance=n._oExternConfig.iNextUnique++;if(this.nodeName.toLowerCase()!="table")H(e,0,"Attempted to initialise DataTables on a node which is not a table: "+this.nodeName);else{e.nTable=this;e.oInstance=qa.length==1?qa:j(this).dataTable();e.oApi=qa.oApi;e.sDestroyWidth=j(this).width();if(typeof g!=
"undefined"&&g!==null){e.oInit=g;o(e.oFeatures,g,"bPaginate");o(e.oFeatures,g,"bLengthChange");o(e.oFeatures,g,"bFilter");o(e.oFeatures,g,"bSort");o(e.oFeatures,g,"bInfo");o(e.oFeatures,g,"bProcessing");o(e.oFeatures,g,"bAutoWidth");o(e.oFeatures,g,"bSortClasses");o(e.oFeatures,g,"bServerSide");o(e.oScroll,g,"sScrollX","sX");o(e.oScroll,g,"sScrollXInner","sXInner");o(e.oScroll,g,"sScrollY","sY");o(e.oScroll,g,"bScrollCollapse","bCollapse");o(e.oScroll,g,"bScrollInfinite","bInfinite");o(e.oScroll,
g,"iScrollLoadGap","iLoadGap");o(e.oScroll,g,"bScrollAutoCss","bAutoCss");o(e,g,"asStripClasses");o(e,g,"fnRowCallback");o(e,g,"fnHeaderCallback");o(e,g,"fnFooterCallback");o(e,g,"fnCookieCallback");o(e,g,"fnInitComplete");o(e,g,"fnServerData");o(e,g,"fnFormatNumber");o(e,g,"aaSorting");o(e,g,"aaSortingFixed");o(e,g,"aLengthMenu");o(e,g,"sPaginationType");o(e,g,"sAjaxSource");o(e,g,"iCookieDuration");o(e,g,"sCookiePrefix");o(e,g,"sDom");o(e,g,"oSearch","oPreviousSearch");o(e,g,"aoSearchCols","aoPreSearchCols");
o(e,g,"iDisplayLength","_iDisplayLength");o(e,g,"bJQueryUI","bJUI");o(e.oLanguage,g,"fnInfoCallback");typeof g.fnDrawCallback=="function"&&e.aoDrawCallback.push({fn:g.fnDrawCallback,sName:"user"});typeof g.fnStateSaveCallback=="function"&&e.aoStateSave.push({fn:g.fnStateSaveCallback,sName:"user"});typeof g.fnStateLoadCallback=="function"&&e.aoStateLoad.push({fn:g.fnStateLoadCallback,sName:"user"});e.oFeatures.bServerSide&&e.oFeatures.bSort&&e.oFeatures.bSortClasses&&e.aoDrawCallback.push({fn:T,sName:"server_side_sort_classes"});
if(typeof g.bJQueryUI!="undefined"&&g.bJQueryUI){e.oClasses=n.oJUIClasses;if(typeof g.sDom=="undefined")e.sDom='<"H"lfr>t<"F"ip>'}if(e.oScroll.sX!==""||e.oScroll.sY!=="")e.oScroll.iBarWidth=Na();if(typeof g.iDisplayStart!="undefined"&&typeof e.iInitDisplayStart=="undefined"){e.iInitDisplayStart=g.iDisplayStart;e._iDisplayStart=g.iDisplayStart}if(typeof g.bStateSave!="undefined"){e.oFeatures.bStateSave=g.bStateSave;Ma(e,g);e.aoDrawCallback.push({fn:na,sName:"state_save"})}if(typeof g.aaData!="undefined")h=
true;if(typeof g!="undefined"&&typeof g.aoData!="undefined")g.aoColumns=g.aoData;if(typeof g.oLanguage!="undefined")if(typeof g.oLanguage.sUrl!="undefined"&&g.oLanguage.sUrl!==""){e.oLanguage.sUrl=g.oLanguage.sUrl;j.getJSON(e.oLanguage.sUrl,null,function(q){y(e,q,true)});i=true}else y(e,g.oLanguage,false)}else g={};if(typeof g.asStripClasses=="undefined"){e.asStripClasses.push(e.oClasses.sStripOdd);e.asStripClasses.push(e.oClasses.sStripEven)}c=false;d=j(">tbody>tr",this);a=0;for(b=e.asStripClasses.length;a<
b;a++)if(d.filter(":lt(2)").hasClass(e.asStripClasses[a])){c=true;break}if(c){e.asDestoryStrips=["",""];if(j(d[0]).hasClass(e.oClasses.sStripOdd))e.asDestoryStrips[0]+=e.oClasses.sStripOdd+" ";if(j(d[0]).hasClass(e.oClasses.sStripEven))e.asDestoryStrips[0]+=e.oClasses.sStripEven;if(j(d[1]).hasClass(e.oClasses.sStripOdd))e.asDestoryStrips[1]+=e.oClasses.sStripOdd+" ";if(j(d[1]).hasClass(e.oClasses.sStripEven))e.asDestoryStrips[1]+=e.oClasses.sStripEven;d.removeClass(e.asStripClasses.join(" "))}a=this.getElementsByTagName("thead");
c=a.length===0?[]:fa(a[0]);var k;if(typeof g.aoColumns=="undefined"){k=[];a=0;for(b=c.length;a<b;a++)k.push(null)}else k=g.aoColumns;a=0;for(b=k.length;a<b;a++){if(typeof g.saved_aoColumns!="undefined"&&g.saved_aoColumns.length==b){if(k[a]===null)k[a]={};k[a].bVisible=g.saved_aoColumns[a].bVisible}F(e,c?c[a]:null)}if(typeof g.aoColumnDefs!="undefined")for(a=g.aoColumnDefs.length-1;a>=0;a--){var l=g.aoColumnDefs[a].aTargets;j.isArray(l)||H(e,1,"aTargets must be an array of targets, not a "+typeof l);
c=0;for(d=l.length;c<d;c++)if(typeof l[c]=="number"&&l[c]>=0){for(;e.aoColumns.length<=l[c];)F(e);x(e,l[c],g.aoColumnDefs[a])}else if(typeof l[c]=="number"&&l[c]<0)x(e,e.aoColumns.length+l[c],g.aoColumnDefs[a]);else if(typeof l[c]=="string"){b=0;for(f=e.aoColumns.length;b<f;b++)if(l[c]=="_all"||e.aoColumns[b].nTh.className.indexOf(l[c])!=-1)x(e,b,g.aoColumnDefs[a])}}if(typeof k!="undefined"){a=0;for(b=k.length;a<b;a++)x(e,a,k[a])}a=0;for(b=e.aaSorting.length;a<b;a++){if(e.aaSorting[a][0]>=e.aoColumns.length)e.aaSorting[a][0]=
0;k=e.aoColumns[e.aaSorting[a][0]];if(typeof e.aaSorting[a][2]=="undefined")e.aaSorting[a][2]=0;if(typeof g.aaSorting=="undefined"&&typeof e.saved_aaSorting=="undefined")e.aaSorting[a][1]=k.asSorting[0];c=0;for(d=k.asSorting.length;c<d;c++)if(e.aaSorting[a][1]==k.asSorting[c]){e.aaSorting[a][2]=c;break}}T(e);this.getElementsByTagName("thead").length===0&&this.appendChild(p.createElement("thead"));this.getElementsByTagName("tbody").length===0&&this.appendChild(p.createElement("tbody"));e.nTHead=this.getElementsByTagName("thead")[0];
e.nTBody=this.getElementsByTagName("tbody")[0];if(this.getElementsByTagName("tfoot").length>0)e.nTFoot=this.getElementsByTagName("tfoot")[0];if(h)for(a=0;a<g.aaData.length;a++)u(e,g.aaData[a]);else z(e);e.aiDisplay=e.aiDisplayMaster.slice();e.bInitialised=true;i===false&&s(e)}})}})(jQuery,window,document);

/**
 *  A custom plugin that overrides the default animate() function from
 *  jQuery, so that we can notify the floatbox every time that a 
 *  animation was completed.
 * 
 *  We need this because after an animation the size of the page may
 *  be different, and the floatbox may have to be resized because of
 *  that.
 */
(function($) {
    
    // store a reference to the original animate method
    var originalMethod = $.fn.animate;
    
    // define overriding method
    jQuery.fn.animate = function(prop, speed, easing, callback) {
        
        // construct the options (copied this from the jQuery source, 
        // this may become different in future jQuery releases)
        var optall = jQuery.speed(speed, easing, callback);
        
        // the original callback function
        var originalCallback = optall.complete;
        
        // install our own callback function
        optall.complete = function() {
            
            // fire the event to resize the floatbox
            wl.events.fire('resize');
            
            // call original callback
            originalCallback.apply(this, arguments);
        }
        
        // call the original method
        return originalMethod.call(this, prop, optall);
    }
    
})(jQuery);



/**
 *  We need highcharts
 */
// ==ClosureCompiler==
// @compilation_level SIMPLE_OPTIMIZATIONS

/**
 * @license Highcharts JS v2.1.9 (2011-11-11)
 *
 * (c) 2009-2011 Torstein Hønsi
 *
 * License: www.highcharts.com/license
 */

// JSLint options:
/*global document, window, navigator, setInterval, clearInterval, clearTimeout, setTimeout, location, jQuery, $ */

(function () {
// encapsulated variables
var doc = document,
   win = window,
   math = Math,
   mathRound = math.round,
   mathFloor = math.floor,
   mathCeil = math.ceil,
   mathMax = math.max,
   mathMin = math.min,
   mathAbs = math.abs,
   mathCos = math.cos,
   mathSin = math.sin,
   mathPI = math.PI,
   deg2rad = mathPI * 2 / 360,


   // some variables
   userAgent = navigator.userAgent,
   isIE = /msie/i.test(userAgent) && !win.opera,
   docMode8 = doc.documentMode === 8,
   isWebKit = /AppleWebKit/.test(userAgent),
   isFirefox = /Firefox/.test(userAgent),
   SVG_NS = 'http://www.w3.org/2000/svg',
   hasSVG = !!doc.createElementNS && !!doc.createElementNS(SVG_NS, 'svg').createSVGRect,
   hasRtlBug = isFirefox && parseInt(userAgent.split('Firefox/')[1], 10) < 4, // issue #38
   Renderer,
   hasTouch = doc.documentElement.ontouchstart !== undefined,
   symbolSizes = {},
   idCounter = 0,
   timeFactor = 1, // 1 = JavaScript time, 1000 = Unix time
   garbageBin,
   defaultOptions,
   dateFormat, // function
   globalAnimation,
   pathAnim,


   // some constants for frequently used strings
   UNDEFINED,
   DIV = 'div',
   ABSOLUTE = 'absolute',
   RELATIVE = 'relative',
   HIDDEN = 'hidden',
   PREFIX = 'highcharts-',
   VISIBLE = 'visible',
   PX = 'px',
   NONE = 'none',
   M = 'M',
   L = 'L',
   /*
    * Empirical lowest possible opacities for TRACKER_FILL
    * IE6: 0.002
    * IE7: 0.002
    * IE8: 0.002
    * IE9: 0.00000000001 (unlimited)
    * FF: 0.00000000001 (unlimited)
    * Chrome: 0.000001
    * Safari: 0.000001
    * Opera: 0.00000000001 (unlimited)
    */
   TRACKER_FILL = 'rgba(192,192,192,' + (hasSVG ? 0.000001 : 0.002) + ')', // invisible but clickable
   NORMAL_STATE = '',
   HOVER_STATE = 'hover',
   SELECT_STATE = 'select',

   // time methods, changed based on whether or not UTC is used
   makeTime,
   getMinutes,
   getHours,
   getDay,
   getDate,
   getMonth,
   getFullYear,
   setMinutes,
   setHours,
   setDate,
   setMonth,
   setFullYear,

   // check for a custom HighchartsAdapter defined prior to this file
   globalAdapter = win.HighchartsAdapter,
   adapter = globalAdapter || {},

   // Utility functions. If the HighchartsAdapter is not defined, adapter is an empty object
   // and all the utility functions will be null. In that case they are populated by the
   // default adapters below.
   each = adapter.each,
   grep = adapter.grep,
   map = adapter.map,
   merge = adapter.merge,
   addEvent = adapter.addEvent,
   removeEvent = adapter.removeEvent,
   fireEvent = adapter.fireEvent,
   animate = adapter.animate,
   stop = adapter.stop,

   // lookup over the types and the associated classes
   seriesTypes = {};

/**
 * Extend an object with the members of another
 * @param {Object} a The object to be extended
 * @param {Object} b The object to add to the first one
 */
function extend(a, b) {
   var n;
   if (!a) {
      a = {};
   }
   for (n in b) {
      a[n] = b[n];
   }
   return a;
}

/**
 * Shortcut for parseInt
 * @param {Object} s
 */
function pInt(s, mag) {
   return parseInt(s, mag || 10);
}

/**
 * Check for string
 * @param {Object} s
 */
function isString(s) {
   return typeof s === 'string';
}

/**
 * Check for object
 * @param {Object} obj
 */
function isObject(obj) {
   return typeof obj === 'object';
}

/**
 * Check for array
 * @param {Object} obj
 */
function isArray(obj) {
   return Object.prototype.toString.call(obj) === '[object Array]';
}

/**
 * Check for number
 * @param {Object} n
 */
function isNumber(n) {
   return typeof n === 'number';
}

function log2lin(num) {
   return math.log(num) / math.LN10;
}
function lin2log(num) {
   return math.pow(10, num);
}

/**
 * Remove last occurence of an item from an array
 * @param {Array} arr
 * @param {Mixed} item
 */
function erase(arr, item) {
   var i = arr.length;
   while (i--) {
      if (arr[i] === item) {
         arr.splice(i, 1);
         break;
      }
   }
   //return arr;
}

/**
 * Returns true if the object is not null or undefined. Like MooTools' $.defined.
 * @param {Object} obj
 */
function defined(obj) {
   return obj !== UNDEFINED && obj !== null;
}

/**
 * Set or get an attribute or an object of attributes. Can't use jQuery attr because
 * it attempts to set expando properties on the SVG element, which is not allowed.
 *
 * @param {Object} elem The DOM element to receive the attribute(s)
 * @param {String|Object} prop The property or an abject of key-value pairs
 * @param {String} value The value if a single property is set
 */
function attr(elem, prop, value) {
   var key,
      setAttribute = 'setAttribute',
      ret;

   // if the prop is a string
   if (isString(prop)) {
      // set the value
      if (defined(value)) {

         elem[setAttribute](prop, value);

      // get the value
      } else if (elem && elem.getAttribute) { // elem not defined when printing pie demo...
         ret = elem.getAttribute(prop);
      }

   // else if prop is defined, it is a hash of key/value pairs
   } else if (defined(prop) && isObject(prop)) {
      for (key in prop) {
         elem[setAttribute](key, prop[key]);
      }
   }
   return ret;
}
/**
 * Check if an element is an array, and if not, make it into an array. Like
 * MooTools' $.splat.
 */
function splat(obj) {
   return isArray(obj) ? obj : [obj];
}


/**
 * Return the first value that is defined. Like MooTools' $.pick.
 */
function pick() {
   var args = arguments,
      i,
      arg,
      length = args.length;
   for (i = 0; i < length; i++) {
      arg = args[i];
      if (typeof arg !== 'undefined' && arg !== null) {
         return arg;
      }
   }
}

/**
 * Set CSS on a given element
 * @param {Object} el
 * @param {Object} styles Style object with camel case property names
 */
function css(el, styles) {
   if (isIE) {
      if (styles && styles.opacity !== UNDEFINED) {
         styles.filter = 'alpha(opacity=' + (styles.opacity * 100) + ')';
      }
   }
   extend(el.style, styles);
}

/**
 * Utility function to create element with attributes and styles
 * @param {Object} tag
 * @param {Object} attribs
 * @param {Object} styles
 * @param {Object} parent
 * @param {Object} nopad
 */
function createElement(tag, attribs, styles, parent, nopad) {
   var el = doc.createElement(tag);
   if (attribs) {
      extend(el, attribs);
   }
   if (nopad) {
      css(el, {padding: 0, border: NONE, margin: 0});
   }
   if (styles) {
      css(el, styles);
   }
   if (parent) {
      parent.appendChild(el);
   }
   return el;
}

/**
 * Extend a prototyped class by new members
 * @param {Object} parent
 * @param {Object} members
 */
function extendClass(parent, members) {
   var object = function () {};
   object.prototype = new parent();
   extend(object.prototype, members);
   return object;
}

/**
 * Format a number and return a string based on input settings
 * @param {Number} number The input number to format
 * @param {Number} decimals The amount of decimals
 * @param {String} decPoint The decimal point, defaults to the one given in the lang options
 * @param {String} thousandsSep The thousands separator, defaults to the one given in the lang options
 */
function numberFormat(number, decimals, decPoint, thousandsSep) {
   var lang = defaultOptions.lang,
      // http://kevin.vanzonneveld.net/techblog/article/javascript_equivalent_for_phps_number_format/
      n = number,
      c = isNaN(decimals = mathAbs(decimals)) ? 2 : decimals,
      d = decPoint === undefined ? lang.decimalPoint : decPoint,
      t = thousandsSep === undefined ? lang.thousandsSep : thousandsSep,
      s = n < 0 ? "-" : "",
      i = String(pInt(n = mathAbs(+n || 0).toFixed(c))),
      j = i.length > 3 ? i.length % 3 : 0;

   return s + (j ? i.substr(0, j) + t : "") + i.substr(j).replace(/(\d{3})(?=\d)/g, "$1" + t) +
      (c ? d + mathAbs(n - i).toFixed(c).slice(2) : "");
}

/**
 * Based on http://www.php.net/manual/en/function.strftime.php
 * @param {String} format
 * @param {Number} timestamp
 * @param {Boolean} capitalize
 */
dateFormat = function (format, timestamp, capitalize) {
   function pad(number) {
      return number.toString().replace(/^([0-9])$/, '0$1');
   }

   if (!defined(timestamp) || isNaN(timestamp)) {
      return 'Invalid date';
   }
   format = pick(format, '%Y-%m-%d %H:%M:%S');

   var date = new Date(timestamp * timeFactor),
      key, // used in for constuct below
      // get the basic time values
      hours = date[getHours](),
      day = date[getDay](),
      dayOfMonth = date[getDate](),
      month = date[getMonth](),
      fullYear = date[getFullYear](),
      lang = defaultOptions.lang,
      langWeekdays = lang.weekdays,
      /* // uncomment this and the 'W' format key below to enable week numbers
      weekNumber = function() {
         var clone = new Date(date.valueOf()),
            day = clone[getDay]() == 0 ? 7 : clone[getDay](),
            dayNumber;
         clone.setDate(clone[getDate]() + 4 - day);
         dayNumber = mathFloor((clone.getTime() - new Date(clone[getFullYear](), 0, 1, -6)) / 86400000);
         return 1 + mathFloor(dayNumber / 7);
      },
      */

      // list all format keys
      replacements = {

         // Day
         'a': langWeekdays[day].substr(0, 3), // Short weekday, like 'Mon'
         'A': langWeekdays[day], // Long weekday, like 'Monday'
         'd': pad(dayOfMonth), // Two digit day of the month, 01 to 31
         'e': dayOfMonth, // Day of the month, 1 through 31

         // Week (none implemented)
         //'W': weekNumber(),

         // Month
         'b': lang.shortMonths[month], // Short month, like 'Jan'
         'B': lang.months[month], // Long month, like 'January'
         'm': pad(month + 1), // Two digit month number, 01 through 12

         // Year
         'y': fullYear.toString().substr(2, 2), // Two digits year, like 09 for 2009
         'Y': fullYear, // Four digits year, like 2009

         // Time
         'H': pad(hours), // Two digits hours in 24h format, 00 through 23
         'I': pad((hours % 12) || 12), // Two digits hours in 12h format, 00 through 11
         'l': (hours % 12) || 12, // Hours in 12h format, 1 through 12
         'M': pad(date[getMinutes]()), // Two digits minutes, 00 through 59
         'p': hours < 12 ? 'AM' : 'PM', // Upper case AM or PM
         'P': hours < 12 ? 'am' : 'pm', // Lower case AM or PM
         'S': pad(date.getSeconds()) // Two digits seconds, 00 through  59

      };


   // do the replaces
   for (key in replacements) {
      format = format.replace('%' + key, replacements[key]);
   }

   // Optionally capitalize the string and return
   return capitalize ? format.substr(0, 1).toUpperCase() + format.substr(1) : format;
};

/**
 * Loop up the node tree and add offsetWidth and offsetHeight to get the
 * total page offset for a given element. Used by Opera and iOS on hover and
 * all browsers on point click.
 *
 * @param {Object} el
 *
 */
function getPosition(el) {
   var p = { left: el.offsetLeft, top: el.offsetTop };
   el = el.offsetParent;
   while (el) {
      p.left += el.offsetLeft;
      p.top += el.offsetTop;
      if (el !== doc.body && el !== doc.documentElement) {
         p.left -= el.scrollLeft;
         p.top -= el.scrollTop;
      }
      el = el.offsetParent;
   }
   return p;
}

/**
 * Helper class that contains variuos counters that are local to the chart.
 */
function ChartCounters() {
   this.color = 0;
   this.symbol = 0;
}

ChartCounters.prototype = {
   /**
    * Wraps the color counter if it reaches the specified length.
    */
   wrapColor: function (length) {
      if (this.color >= length) {
         this.color = 0;
      }
   },

   /**
    * Wraps the symbol counter if it reaches the specified length.
    */
   wrapSymbol: function (length) {
      if (this.symbol >= length) {
         this.symbol = 0;
      }
   }
};

/**
 * Utility method extracted from Tooltip code that places a tooltip in a chart without spilling over
 * and not covering the point it self.
 */
function placeBox(boxWidth, boxHeight, outerLeft, outerTop, outerWidth, outerHeight, point) {
   // keep the box within the chart area
   var pointX = point.x,
      pointY = point.y,
      x = pointX - boxWidth + outerLeft - 25,
      y = pointY - boxHeight + outerTop + 10,
      alignedRight;

   // it is too far to the left, adjust it
   if (x < 7) {
      x = outerLeft + pointX + 15;
   }

   // Test to see if the tooltip is to far to the right,
   // if it is, move it back to be inside and then up to not cover the point.
   if ((x + boxWidth) > (outerLeft + outerWidth)) {
      x -= (x + boxWidth) - (outerLeft + outerWidth);
      y -= boxHeight;
      alignedRight = true;
   }

   if (y < 5) {
      y = 5; // above

      // If the tooltip is still covering the point, move it below instead
      if (alignedRight && pointY >= y && pointY <= (y + boxHeight)) {
         y = pointY + boxHeight - 5; // below
      }
   } else if (y + boxHeight > outerTop + outerHeight) {
      y = outerTop + outerHeight - boxHeight - 5; // below
   }

   return {x: x, y: y};
}

/**
 * Utility method that sorts an object array and keeping the order of equal items.
 * ECMA script standard does not specify the behaviour when items are equal.
 */
function stableSort(arr, sortFunction) {
   var length = arr.length,
      i;

   // Add index to each item
   for (i = 0; i < length; i++) {
      arr[i].ss_i = i; // stable sort index
   }

   arr.sort(function (a, b) {
      var sortValue = sortFunction(a, b);
      return sortValue === 0 ? a.ss_i - b.ss_i : sortValue;
   });

   // Remove index from items
   for (i = 0; i < length; i++) {
      delete arr[i].ss_i; // stable sort index
   }
}

/**
 * Utility method that destroys any SVGElement or VMLElement that are properties on the given object.
 * It loops all properties and invokes destroy if there is a destroy method. The property is
 * then delete'ed.
 */
function destroyObjectProperties(obj) {
   var n;
   for (n in obj) {
      // If the object is non-null and destroy is defined
      if (obj[n] && obj[n].destroy) {
         // Invoke the destroy
         obj[n].destroy();
      }

      // Delete the property from the object.
      delete obj[n];
   }
}

/**
 * Path interpolation algorithm used across adapters
 */
pathAnim = {
   /**
    * Prepare start and end values so that the path can be animated one to one
    */
   init: function (elem, fromD, toD) {
      fromD = fromD || '';
      var shift = elem.shift,
         bezier = fromD.indexOf('C') > -1,
         numParams = bezier ? 7 : 3,
         endLength,
         slice,
         i,
         start = fromD.split(' '),
         end = [].concat(toD), // copy
         startBaseLine,
         endBaseLine,
         sixify = function (arr) { // in splines make move points have six parameters like bezier curves
            i = arr.length;
            while (i--) {
               if (arr[i] === M) {
                  arr.splice(i + 1, 0, arr[i + 1], arr[i + 2], arr[i + 1], arr[i + 2]);
               }
            }
         };

      if (bezier) {
         sixify(start);
         sixify(end);
      }

      // pull out the base lines before padding
      if (elem.isArea) {
         startBaseLine = start.splice(start.length - 6, 6);
         endBaseLine = end.splice(end.length - 6, 6);
      }

      // if shifting points, prepend a dummy point to the end path
      if (shift) {

         end = [].concat(end).splice(0, numParams).concat(end);
         elem.shift = false; // reset for following animations
      }

      // copy and append last point until the length matches the end length
      if (start.length) {
         endLength = end.length;
         while (start.length < endLength) {

            //bezier && sixify(start);
            slice = [].concat(start).splice(start.length - numParams, numParams);
            if (bezier) { // disable first control point
               slice[numParams - 6] = slice[numParams - 2];
               slice[numParams - 5] = slice[numParams - 1];
            }
            start = start.concat(slice);
         }
      }

      if (startBaseLine) { // append the base lines for areas
         start = start.concat(startBaseLine);
         end = end.concat(endBaseLine);
      }
      return [start, end];
   },

   /**
    * Interpolate each value of the path and return the array
    */
   step: function (start, end, pos, complete) {
      var ret = [],
         i = start.length,
         startVal;

      if (pos === 1) { // land on the final path without adjustment points appended in the ends
         ret = complete;

      } else if (i === end.length && pos < 1) {
         while (i--) {
            startVal = parseFloat(start[i]);
            ret[i] =
               isNaN(startVal) ? // a letter instruction like M or L
                  start[i] :
                  pos * (parseFloat(end[i] - startVal)) + startVal;

         }
      } else { // if animation is finished or length not matching, land on right value
         ret = end;
      }
      return ret;
   }
};


/**
 * Set the global animation to either a given value, or fall back to the
 * given chart's animation option
 * @param {Object} animation
 * @param {Object} chart
 */
function setAnimation(animation, chart) {
   globalAnimation = pick(animation, chart.animation);
}

/*
 * Define the adapter for frameworks. If an external adapter is not defined,
 * Highcharts reverts to the built-in jQuery adapter.
 */
if (globalAdapter && globalAdapter.init) {
   // Initialize the adapter with the pathAnim object that takes care
   // of path animations.
   globalAdapter.init(pathAnim);
}
if (!globalAdapter && win.jQuery) {
   var jQ = jQuery;

   /**
    * Utility for iterating over an array. Parameters are reversed compared to jQuery.
    * @param {Array} arr
    * @param {Function} fn
    */
   each = function (arr, fn) {
      var i = 0,
         len = arr.length;
      for (; i < len; i++) {
         if (fn.call(arr[i], arr[i], i, arr) === false) {
            return i;
         }
      }
   };

   /**
    * Filter an array
    */
   grep = jQ.grep;

   /**
    * Map an array
    * @param {Array} arr
    * @param {Function} fn
    */
   map = function (arr, fn) {
      //return jQuery.map(arr, fn);
      var results = [],
         i = 0,
         len = arr.length;
      for (; i < len; i++) {
         results[i] = fn.call(arr[i], arr[i], i, arr);
      }
      return results;

   };

   /**
    * Deep merge two objects and return a third object
    */
   merge = function () {
      var args = arguments;
      return jQ.extend(true, null, args[0], args[1], args[2], args[3]);
   };

   /**
    * Add an event listener
    * @param {Object} el A HTML element or custom object
    * @param {String} event The event type
    * @param {Function} fn The event handler
    */
   addEvent = function (el, event, fn) {
      jQ(el).bind(event, fn);
   };

   /**
    * Remove event added with addEvent
    * @param {Object} el The object
    * @param {String} eventType The event type. Leave blank to remove all events.
    * @param {Function} handler The function to remove
    */
   removeEvent = function (el, eventType, handler) {
      // workaround for jQuery issue with unbinding custom events:
      // http://forum.jquery.com/topic/javascript-error-when-unbinding-a-custom-event-using-jquery-1-4-2
      var func = doc.removeEventListener ? 'removeEventListener' : 'detachEvent';
      if (doc[func] && !el[func]) {
         el[func] = function () {};
      }

      jQ(el).unbind(eventType, handler);
   };

   /**
    * Fire an event on a custom object
    * @param {Object} el
    * @param {String} type
    * @param {Object} eventArguments
    * @param {Function} defaultFunction
    */
   fireEvent = function (el, type, eventArguments, defaultFunction) {
      var event = jQ.Event(type),
         detachedType = 'detached' + type;
      extend(event, eventArguments);

      // Prevent jQuery from triggering the object method that is named the
      // same as the event. For example, if the event is 'select', jQuery
      // attempts calling el.select and it goes into a loop.
      if (el[type]) {
         el[detachedType] = el[type];
         el[type] = null;
      }

      // trigger it
      jQ(el).trigger(event);

      // attach the method
      if (el[detachedType]) {
         el[type] = el[detachedType];
         el[detachedType] = null;
      }

      if (defaultFunction && !event.isDefaultPrevented()) {
         defaultFunction(event);
      }
   };

   /**
    * Animate a HTML element or SVG element wrapper
    * @param {Object} el
    * @param {Object} params
    * @param {Object} options jQuery-like animation options: duration, easing, callback
    */
   animate = function (el, params, options) {
      var $el = jQ(el);
      if (params.d) {
         el.toD = params.d; // keep the array form for paths, used in jQ.fx.step.d
         params.d = 1; // because in jQuery, animating to an array has a different meaning
      }

      $el.stop();
      $el.animate(params, options);

   };
   /**
    * Stop running animation
    */
   stop = function (el) {
      jQ(el).stop();
   };


   //=== Extend jQuery on init
   
   /*jslint unparam: true*//* allow unused param x in this function */
   jQ.extend(jQ.easing, {
      easeOutQuad: function (x, t, b, c, d) {
         return -c * (t /= d) * (t - 2) + b;
      }
   });
   /*jslint unparam: false*/

   // extend the animate function to allow SVG animations
   var jFx = jQuery.fx,
      jStep = jFx.step;
      
   // extend some methods to check for elem.attr, which means it is a Highcharts SVG object
   each(['cur', '_default', 'width', 'height'], function (fn, i) {
      var obj = i ? jStep : jFx.prototype, // 'cur', the getter' relates to jFx.prototype
         base = obj[fn],
         elem;
      
      if (base) { // step.width and step.height don't exist in jQuery < 1.7
      
         // create the extended function replacement
         obj[fn] = function (fx) {
            
            // jFx.prototype.cur does not use fx argument
            fx = i ? fx : this;
            
            // shortcut
            elem = fx.elem;
            
            // jFX.prototype.cur returns the current value. The other ones are setters 
            // and returning a value has no effect.
            return elem.attr ? // is SVG element wrapper
               elem.attr(fx.prop, fx.now) : // apply the SVG wrapper's method
               base.apply(this, arguments); // use jQuery's built-in method
         };
      }
   });
   
   // animate paths
   jStep.d = function (fx) {
      var elem = fx.elem;


      // Normally start and end should be set in state == 0, but sometimes,
      // for reasons unknown, this doesn't happen. Perhaps state == 0 is skipped
      // in these cases
      if (!fx.started) {
         var ends = pathAnim.init(elem, elem.d, elem.toD);
         fx.start = ends[0];
         fx.end = ends[1];
         fx.started = true;
      }


      // interpolate each value of the path
      elem.attr('d', pathAnim.step(fx.start, fx.end, fx.pos, elem.toD));

   };
}


/**
 * Add a global listener for mousemove events
 */
/*addEvent(doc, 'mousemove', function(e) {
   if (globalMouseMove) {
      globalMouseMove(e);
   }
});*/
/**
 * Set the time methods globally based on the useUTC option. Time method can be either
 * local time or UTC (default).
 */
function setTimeMethods() {
   var useUTC = defaultOptions.global.useUTC;

   makeTime = useUTC ? Date.UTC : function (year, month, date, hours, minutes, seconds) {
      return new Date(
         year,
         month,
         pick(date, 1),
         pick(hours, 0),
         pick(minutes, 0),
         pick(seconds, 0)
      ).getTime();
   };
   getMinutes = useUTC ? 'getUTCMinutes' : 'getMinutes';
   getHours = useUTC ? 'getUTCHours' : 'getHours';
   getDay = useUTC ? 'getUTCDay' : 'getDay';
   getDate = useUTC ? 'getUTCDate' : 'getDate';
   getMonth = useUTC ? 'getUTCMonth' : 'getMonth';
   getFullYear = useUTC ? 'getUTCFullYear' : 'getFullYear';
   setMinutes = useUTC ? 'setUTCMinutes' : 'setMinutes';
   setHours = useUTC ? 'setUTCHours' : 'setHours';
   setDate = useUTC ? 'setUTCDate' : 'setDate';
   setMonth = useUTC ? 'setUTCMonth' : 'setMonth';
   setFullYear = useUTC ? 'setUTCFullYear' : 'setFullYear';

}

/**
 * Merge the default options with custom options and return the new options structure
 * @param {Object} options The new custom options
 */
function setOptions(options) {
   defaultOptions = merge(defaultOptions, options);

   // apply UTC
   setTimeMethods();

   return defaultOptions;
}

/**
 * Get the updated default options. Merely exposing defaultOptions for outside modules
 * isn't enough because the setOptions method creates a new object.
 */
function getOptions() {
   return defaultOptions;
}

/**
 * Discard an element by moving it to the bin and delete
 * @param {Object} The HTML node to discard
 */
function discardElement(element) {
   // create a garbage bin element, not part of the DOM
   if (!garbageBin) {
      garbageBin = createElement(DIV);
   }

   // move the node and empty bin
   if (element) {
      garbageBin.appendChild(element);
   }
   garbageBin.innerHTML = '';
}

/* ****************************************************************************
 * Handle the options                                                         *
 *****************************************************************************/
var

defaultLabelOptions = {
   enabled: true,
   // rotation: 0,
   align: 'center',
   x: 0,
   y: 15,
   /*formatter: function() {
      return this.value;
   },*/
   style: {
      color: '#666',
      fontSize: '11px',
      lineHeight: '14px'
   }
};

defaultOptions = {
   colors: ['#4572A7', '#AA4643', '#89A54E', '#80699B', '#3D96AE',
      '#DB843D', '#92A8CD', '#A47D7C', '#B5CA92'],
   symbols: ['circle', 'diamond', 'square', 'triangle', 'triangle-down'],
   lang: {
      loading: 'Loading...',
      months: ['January', 'February', 'March', 'April', 'May', 'June', 'July',
            'August', 'September', 'October', 'November', 'December'],
      shortMonths: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'June', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
      weekdays: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
      decimalPoint: '.',
      resetZoom: 'Reset zoom',
      resetZoomTitle: 'Reset zoom level 1:1',
      thousandsSep: ','
   },
   global: {
      useUTC: true
   },
   chart: {
      //animation: true,
      //alignTicks: false,
      //reflow: true,
      //className: null,
      //events: { load, selection },
      //margin: [null],
      //marginTop: null,
      //marginRight: null,
      //marginBottom: null,
      //marginLeft: null,
      borderColor: '#4572A7',
      //borderWidth: 0,
      borderRadius: 5,
      defaultSeriesType: 'line',
      ignoreHiddenSeries: true,
      //inverted: false,
      //shadow: false,
      spacingTop: 10,
      spacingRight: 10,
      spacingBottom: 15,
      spacingLeft: 10,
      style: {
         fontFamily: '"Lucida Grande", "Lucida Sans Unicode", Verdana, Arial, Helvetica, sans-serif', // default font
         fontSize: '12px'
      },
      backgroundColor: '#FFFFFF',
      //plotBackgroundColor: null,
      plotBorderColor: '#C0C0C0'
      //plotBorderWidth: 0,
      //plotShadow: false,
      //zoomType: ''
   },
   title: {
      text: 'Chart title',
      align: 'center',
      // floating: false,
      // margin: 15,
      // x: 0,
      // verticalAlign: 'top',
      y: 15,
      style: {
         color: '#3E576F',
         fontSize: '16px'
      }

   },
   subtitle: {
      text: '',
      align: 'center',
      // floating: false
      // x: 0,
      // verticalAlign: 'top',
      y: 30,
      style: {
         color: '#6D869F'
      }
   },

   plotOptions: {
      line: { // base series options
         allowPointSelect: false,
         showCheckbox: false,
         animation: {
            duration: 1000
         },
         //connectNulls: false,
         //cursor: 'default',
         //dashStyle: null,
         //enableMouseTracking: true,
         events: {},
         //legendIndex: 0,
         lineWidth: 2,
         shadow: true,
         // stacking: null,
         marker: {
            enabled: true,
            //symbol: null,
            lineWidth: 0,
            radius: 4,
            lineColor: '#FFFFFF',
            //fillColor: null,
            states: { // states for a single point
               hover: {
                  //radius: base + 2
               },
               select: {
                  fillColor: '#FFFFFF',
                  lineColor: '#000000',
                  lineWidth: 2
               }
            }
         },
         point: {
            events: {}
         },
         dataLabels: merge(defaultLabelOptions, {
            enabled: false,
            y: -6,
            formatter: function () {
               return this.y;
            }
         }),

         //pointStart: 0,
         //pointInterval: 1,
         showInLegend: true,
         states: { // states for the entire series
            hover: {
               //enabled: false,
               //lineWidth: base + 1,
               marker: {
                  // lineWidth: base + 1,
                  // radius: base + 1
               }
            },
            select: {
               marker: {}
            }
         },
         stickyTracking: true
         //zIndex: null
      }
   },
   labels: {
      //items: [],
      style: {
         //font: defaultFont,
         position: ABSOLUTE,
         color: '#3E576F'
      }
   },
   legend: {
      enabled: true,
      align: 'center',
      //floating: false,
      layout: 'horizontal',
      labelFormatter: function () {
         return this.name;
      },
      borderWidth: 1,
      borderColor: '#909090',
      borderRadius: 5,
      // margin: 10,
      // reversed: false,
      shadow: false,
      // backgroundColor: null,
      style: {
         padding: '5px'
      },
      itemStyle: {
         cursor: 'pointer',
         color: '#3E576F'
      },
      itemHoverStyle: {
         cursor: 'pointer',
         color: '#000000'
      },
      itemHiddenStyle: {
         color: '#C0C0C0'
      },
      itemCheckboxStyle: {
         position: ABSOLUTE,
         width: '13px', // for IE precision
         height: '13px'
      },
      // itemWidth: undefined,
      symbolWidth: 16,
      symbolPadding: 5,
      verticalAlign: 'bottom',
      // width: undefined,
      x: 0,
      y: 0
   },

   loading: {
      hideDuration: 100,
      labelStyle: {
         fontWeight: 'bold',
         position: RELATIVE,
         top: '1em'
      },
      showDuration: 100,
      style: {
         position: ABSOLUTE,
         backgroundColor: 'white',
         opacity: 0.5,
         textAlign: 'center'
      }
   },

   tooltip: {
      enabled: true,
      //crosshairs: null,
      backgroundColor: 'rgba(255, 255, 255, .85)',
      borderWidth: 2,
      borderRadius: 5,
      //formatter: defaultFormatter,
      shadow: true,
      //shared: false,
      snap: hasTouch ? 25 : 10,
      style: {
         color: '#333333',
         fontSize: '12px',
         padding: '5px',
         whiteSpace: 'nowrap'
      }
   },

   toolbar: {
      itemStyle: {
         color: '#4572A7',
         cursor: 'pointer'
      }
   },

   credits: {
      enabled: true,
      text: 'Highcharts.com',
      href: 'http://www.highcharts.com',
      position: {
         align: 'right',
         x: -10,
         verticalAlign: 'bottom',
         y: -5
      },
      style: {
         cursor: 'pointer',
         color: '#909090',
         fontSize: '10px'
      }
   }
};

// Axis defaults
var defaultXAxisOptions =  {
   // allowDecimals: null,
   // alternateGridColor: null,
   // categories: [],
   dateTimeLabelFormats: {
      second: '%H:%M:%S',
      minute: '%H:%M',
      hour: '%H:%M',
      day: '%e. %b',
      week: '%e. %b',
      month: '%b \'%y',
      year: '%Y'
   },
   endOnTick: false,
   gridLineColor: '#C0C0C0',
   // gridLineDashStyle: 'solid', // docs
   // gridLineWidth: 0,
   // reversed: false,

   labels: defaultLabelOptions,
      // { step: null },
   lineColor: '#C0D0E0',
   lineWidth: 1,
   //linkedTo: null,
   max: null,
   min: null,
   minPadding: 0.01,
   maxPadding: 0.01,
   //maxZoom: null,
   minorGridLineColor: '#E0E0E0',
   // minorGridLineDashStyle: null,
   minorGridLineWidth: 1,
   minorTickColor: '#A0A0A0',
   //minorTickInterval: null,
   minorTickLength: 2,
   minorTickPosition: 'outside', // inside or outside
   //minorTickWidth: 0,
   //opposite: false,
   //offset: 0,
   //plotBands: [{
   //   events: {},
   //   zIndex: 1,
   //   labels: { align, x, verticalAlign, y, style, rotation, textAlign }
   //}],
   //plotLines: [{
   //   events: {}
   //  dashStyle: {}
   //   zIndex:
   //   labels: { align, x, verticalAlign, y, style, rotation, textAlign }
   //}],
   //reversed: false,
   // showFirstLabel: true,
   // showLastLabel: false,
   startOfWeek: 1,
   startOnTick: false,
   tickColor: '#C0D0E0',
   //tickInterval: null,
   tickLength: 5,
   tickmarkPlacement: 'between', // on or between
   tickPixelInterval: 100,
   tickPosition: 'outside',
   tickWidth: 1,
   title: {
      //text: null,
      align: 'middle', // low, middle or high
      //margin: 0 for horizontal, 10 for vertical axes,
      //rotation: 0,
      //side: 'outside',
      style: {
         color: '#6D869F',
         //font: defaultFont.replace('normal', 'bold')
         fontWeight: 'bold'
      }
      //x: 0,
      //y: 0
   },
   type: 'linear' // linear, logarithmic or datetime
},

defaultYAxisOptions = merge(defaultXAxisOptions, {
   endOnTick: true,
   gridLineWidth: 1,
   tickPixelInterval: 72,
   showLastLabel: true,
   labels: {
      align: 'right',
      x: -8,
      y: 3
   },
   lineWidth: 0,
   maxPadding: 0.05,
   minPadding: 0.05,
   startOnTick: true,
   tickWidth: 0,
   title: {
      rotation: 270,
      text: 'Y-values'
   },
   stackLabels: {
      enabled: false,
      //align: dynamic,
      //y: dynamic,
      //x: dynamic,
      //verticalAlign: dynamic,
      //textAlign: dynamic,
      //rotation: 0,
      formatter: function () {
         return this.total;
      },
      style: defaultLabelOptions.style
   }
}),

defaultLeftAxisOptions = {
   labels: {
      align: 'right',
      x: -8,
      y: null
   },
   title: {
      rotation: 270
   }
},
defaultRightAxisOptions = {
   labels: {
      align: 'left',
      x: 8,
      y: null
   },
   title: {
      rotation: 90
   }
},
defaultBottomAxisOptions = { // horizontal axis
   labels: {
      align: 'center',
      x: 0,
      y: 14
      // staggerLines: null
   },
   title: {
      rotation: 0
   }
},
defaultTopAxisOptions = merge(defaultBottomAxisOptions, {
   labels: {
      y: -5
      // staggerLines: null
   }
});




// Series defaults
var defaultPlotOptions = defaultOptions.plotOptions,
   defaultSeriesOptions = defaultPlotOptions.line;
//defaultPlotOptions.line = merge(defaultSeriesOptions);
defaultPlotOptions.spline = merge(defaultSeriesOptions);
defaultPlotOptions.scatter = merge(defaultSeriesOptions, {
   lineWidth: 0,
   states: {
      hover: {
         lineWidth: 0
      }
   }
});
defaultPlotOptions.area = merge(defaultSeriesOptions, {
   // threshold: 0,
   // lineColor: null, // overrides color, but lets fillColor be unaltered
   // fillOpacity: 0.75,
   // fillColor: null

});
defaultPlotOptions.areaspline = merge(defaultPlotOptions.area);
defaultPlotOptions.column = merge(defaultSeriesOptions, {
   borderColor: '#FFFFFF',
   borderWidth: 1,
   borderRadius: 0,
   //colorByPoint: undefined,
   groupPadding: 0.2,
   marker: null, // point options are specified in the base options
   pointPadding: 0.1,
   //pointWidth: null,
   minPointLength: 0,
   states: {
      hover: {
         brightness: 0.1,
         shadow: false
      },
      select: {
         color: '#C0C0C0',
         borderColor: '#000000',
         shadow: false
      }
   },
   dataLabels: {
      y: null,
      verticalAlign: null
   }
});
defaultPlotOptions.bar = merge(defaultPlotOptions.column, {
   dataLabels: {
      align: 'left',
      x: 5,
      y: 0
   }
});
defaultPlotOptions.pie = merge(defaultSeriesOptions, {
   //dragType: '', // n/a
   borderColor: '#FFFFFF',
   borderWidth: 1,
   center: ['50%', '50%'],
   colorByPoint: true, // always true for pies
   dataLabels: {
      // align: null,
      // connectorWidth: 1,
      // connectorColor: point.color,
      // connectorPadding: 5,
      distance: 30,
      enabled: true,
      formatter: function () {
         return this.point.name;
      },
      // softConnector: true,
      y: 5
   },
   //innerSize: 0,
   legendType: 'point',
   marker: null, // point options are specified in the base options
   size: '75%',
   showInLegend: false,
   slicedOffset: 10,
   states: {
      hover: {
         brightness: 0.1,
         shadow: false
      }
   }

});

// set the default time methods
setTimeMethods();


/**
 * Handle color operations. The object methods are chainable.
 * @param {String} input The input color in either rbga or hex format
 */
var Color = function (input) {
   // declare variables
   var rgba = [], result;

   /**
    * Parse the input color to rgba array
    * @param {String} input
    */
   function init(input) {

      // rgba
      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);
      if (result) {
         rgba = [pInt(result[1]), pInt(result[2]), pInt(result[3]), parseFloat(result[4], 10)];
      } else { // hex
         result = /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(input);
         if (result) {
            rgba = [pInt(result[1], 16), pInt(result[2], 16), pInt(result[3], 16), 1];
         }
      }

   }
   /**
    * Return the color a specified format
    * @param {String} format
    */
   function get(format) {
      var ret;

      // it's NaN if gradient colors on a column chart
      if (rgba && !isNaN(rgba[0])) {
         if (format === 'rgb') {
            ret = 'rgb(' + rgba[0] + ',' + rgba[1] + ',' + rgba[2] + ')';
         } else if (format === 'a') {
            ret = rgba[3];
         } else {
            ret = 'rgba(' + rgba.join(',') + ')';
         }
      } else {
         ret = input;
      }
      return ret;
   }

   /**
    * Brighten the color
    * @param {Number} alpha
    */
   function brighten(alpha) {
      if (isNumber(alpha) && alpha !== 0) {
         var i;
         for (i = 0; i < 3; i++) {
            rgba[i] += pInt(alpha * 255);

            if (rgba[i] < 0) {
               rgba[i] = 0;
            }
            if (rgba[i] > 255) {
               rgba[i] = 255;
            }
         }
      }
      return this;
   }
   /**
    * Set the color's opacity to a given alpha value
    * @param {Number} alpha
    */
   function setOpacity(alpha) {
      rgba[3] = alpha;
      return this;
   }

   // initialize: parse the input
   init(input);

   // public methods
   return {
      get: get,
      brighten: brighten,
      setOpacity: setOpacity
   };
};

/**
 * A wrapper object for SVG elements
 */
function SVGElement() {}

SVGElement.prototype = {
   /**
    * Initialize the SVG renderer
    * @param {Object} renderer
    * @param {String} nodeName
    */
   init: function (renderer, nodeName) {
      this.element = doc.createElementNS(SVG_NS, nodeName);
      this.renderer = renderer;
   },
   /**
    * Animate a given attribute
    * @param {Object} params
    * @param {Number} options The same options as in jQuery animation
    * @param {Function} complete Function to perform at the end of animation
    */
   animate: function (params, options, complete) {
      var animOptions = pick(options, globalAnimation, true);
      if (animOptions) {
         animOptions = merge(animOptions);
         if (complete) { // allows using a callback with the global animation without overwriting it
            animOptions.complete = complete;
         }
         animate(this, params, animOptions);
      } else {
         this.attr(params);
         if (complete) {
            complete();
         }
      }
   },
   /**
    * Set or get a given attribute
    * @param {Object|String} hash
    * @param {Mixed|Undefined} val
    */
   attr: function (hash, val) {
      var key,
         value,
         i,
         child,
         element = this.element,
         nodeName = element.nodeName,
         renderer = this.renderer,
         skipAttr,
         shadows = this.shadows,
         htmlNode = this.htmlNode,
         hasSetSymbolSize,
         ret = this;

      // single key-value pair
      if (isString(hash) && defined(val)) {
         key = hash;
         hash = {};
         hash[key] = val;
      }

      // used as a getter: first argument is a string, second is undefined
      if (isString(hash)) {
         key = hash;
         if (nodeName === 'circle') {
            key = { x: 'cx', y: 'cy' }[key] || key;
         } else if (key === 'strokeWidth') {
            key = 'stroke-width';
         }
         ret = attr(element, key) || this[key] || 0;

         if (key !== 'd' && key !== 'visibility') { // 'd' is string in animation step
            ret = parseFloat(ret);
         }

      // setter
      } else {

         for (key in hash) {
            skipAttr = false; // reset
            value = hash[key];

            // paths
            if (key === 'd') {
               if (value && value.join) { // join path
                  value = value.join(' ');
               }
               if (/(NaN| {2}|^$)/.test(value)) {
                  value = 'M 0 0';
               }
               this.d = value; // shortcut for animations

            // update child tspans x values
            } else if (key === 'x' && nodeName === 'text') {
               for (i = 0; i < element.childNodes.length; i++) {
                  child = element.childNodes[i];
                  // if the x values are equal, the tspan represents a linebreak
                  if (attr(child, 'x') === attr(element, 'x')) {
                     //child.setAttribute('x', value);
                     attr(child, 'x', value);
                  }
               }

               if (this.rotation) {
                  attr(element, 'transform', 'rotate(' + this.rotation + ' ' + value + ' ' +
                     pInt(hash.y || attr(element, 'y')) + ')');
               }

            // apply gradients
            } else if (key === 'fill') {
               value = renderer.color(value, element, key);

            // circle x and y
            } else if (nodeName === 'circle' && (key === 'x' || key === 'y')) {
               key = { x: 'cx', y: 'cy' }[key] || key;

            // translation and text rotation
            } else if (key === 'translateX' || key === 'translateY' || key === 'rotation' || key === 'verticalAlign') {
               this[key] = value;
               this.updateTransform();
               skipAttr = true;

            // apply opacity as subnode (required by legacy WebKit and Batik)
            } else if (key === 'stroke') {
               value = renderer.color(value, element, key);

            // emulate VML's dashstyle implementation
            } else if (key === 'dashstyle') {
               key = 'stroke-dasharray';
               value = value && value.toLowerCase();
               if (value === 'solid') {
                  value = NONE;
               } else if (value) {
                  value = value
                     .replace('shortdashdotdot', '3,1,1,1,1,1,')
                     .replace('shortdashdot', '3,1,1,1')
                     .replace('shortdot', '1,1,')
                     .replace('shortdash', '3,1,')
                     .replace('longdash', '8,3,')
                     .replace(/dot/g, '1,3,')
                     .replace('dash', '4,3,')
                     .replace(/,$/, '')
                     .split(','); // ending comma

                  i = value.length;
                  while (i--) {
                     value[i] = pInt(value[i]) * hash['stroke-width'];
                  }

                  value = value.join(',');
               }

            // special
            } else if (key === 'isTracker') {
               this[key] = value;

            // IE9/MooTools combo: MooTools returns objects instead of numbers and IE9 Beta 2
            // is unable to cast them. Test again with final IE9.
            } else if (key === 'width') {
               value = pInt(value);

            // Text alignment
            } else if (key === 'align') {
               key = 'text-anchor';
               value = { left: 'start', center: 'middle', right: 'end' }[value];


            // Title requires a subnode, #431
            } else if (key === 'title') {
               var title = doc.createElementNS(SVG_NS, 'title');
               title.appendChild(doc.createTextNode(value));
               element.appendChild(title);
            }



            // jQuery animate changes case
            if (key === 'strokeWidth') {
               key = 'stroke-width';
            }

            // Chrome/Win < 6 bug (http://code.google.com/p/chromium/issues/detail?id=15461)
            if (isWebKit && key === 'stroke-width' && value === 0) {
               value = 0.000001;
            }

            // symbols
            if (this.symbolName && /^(x|y|r|start|end|innerR)/.test(key)) {


               if (!hasSetSymbolSize) {
                  this.symbolAttr(hash);
                  hasSetSymbolSize = true;
               }
               skipAttr = true;
            }

            // let the shadow follow the main element
            if (shadows && /^(width|height|visibility|x|y|d)$/.test(key)) {
               i = shadows.length;
               while (i--) {
                  attr(shadows[i], key, value);
               }
            }

            // validate heights
            if ((key === 'width' || key === 'height') && nodeName === 'rect' && value < 0) {
               value = 0;
            }

            if (key === 'text') {
               // only one node allowed
               this.textStr = value;
               if (this.added) {
                  renderer.buildText(this);
               }
            } else if (!skipAttr) {
               //element.setAttribute(key, value);
               attr(element, key, value);
            }

            // Issue #38
            if (htmlNode && (key === 'x' || key === 'y' ||
                  key === 'translateX' || key === 'translateY' || key === 'visibility')) {
               var wrapper = this,
                  bBox,
                  arr = htmlNode.length ? htmlNode : [this],
                  length = arr.length,
                  itemWrapper,
                  j;

               for (j = 0; j < length; j++) {
                  itemWrapper = arr[j];
                  bBox = itemWrapper.getBBox();
                  htmlNode = itemWrapper.htmlNode; // reassign to child item
                  css(htmlNode, extend(wrapper.styles, {
                     left: (bBox.x + (wrapper.translateX || 0)) + PX,
                     top: (bBox.y + (wrapper.translateY || 0)) + PX
                  }));

                  if (key === 'visibility') {
                     css(htmlNode, {
                        visibility: value
                     });
                  }
               }
            }

         }

      }
      return ret;
   },

   /**
    * If one of the symbol size affecting parameters are changed,
    * check all the others only once for each call to an element's
    * .attr() method
    * @param {Object} hash
    */
   symbolAttr: function (hash) {
      var wrapper = this;

      each(['x', 'y', 'r', 'start', 'end', 'width', 'height', 'innerR'], function (key) {
         wrapper[key] = pick(hash[key], wrapper[key]);
      });

      wrapper.attr({
         d: wrapper.renderer.symbols[wrapper.symbolName](
               mathRound(wrapper.x * 2) / 2, // Round to halves. Issue #274.
               mathRound(wrapper.y * 2) / 2,
               wrapper.r,
               {
                  start: wrapper.start,
                  end: wrapper.end,
                  width: wrapper.width,
                  height: wrapper.height,
                  innerR: wrapper.innerR
               }
         )
      });
   },

   /**
    * Apply a clipping path to this object
    * @param {String} id
    */
   clip: function (clipRect) {
      return this.attr('clip-path', 'url(' + this.renderer.url + '#' + clipRect.id + ')');
   },

   /**
    * Calculate the coordinates needed for drawing a rectangle crisply and return the
    * calculated attributes
    * @param {Number} strokeWidth
    * @param {Number} x
    * @param {Number} y
    * @param {Number} width
    * @param {Number} height
    */
   crisp: function (strokeWidth, x, y, width, height) {

      var wrapper = this,
         key,
         attr = {},
         values = {},
         normalizer;

      strokeWidth = strokeWidth || wrapper.strokeWidth || 0;
      normalizer = strokeWidth % 2 / 2;

      // normalize for crisp edges
      values.x = mathFloor(x || wrapper.x || 0) + normalizer;
      values.y = mathFloor(y || wrapper.y || 0) + normalizer;
      values.width = mathFloor((width || wrapper.width || 0) - 2 * normalizer);
      values.height = mathFloor((height || wrapper.height || 0) - 2 * normalizer);
      values.strokeWidth = strokeWidth;

      for (key in values) {
         if (wrapper[key] !== values[key]) { // only set attribute if changed
            wrapper[key] = attr[key] = values[key];
         }
      }

      return attr;
   },

   /**
    * Set styles for the element
    * @param {Object} styles
    */
   css: function (styles) {
      /*jslint unparam: true*//* allow unused param a in the regexp function below */
      var elemWrapper = this,
         elem = elemWrapper.element,
         textWidth = styles && styles.width && elem.nodeName === 'text',
         n,
         serializedCss = '',
         hyphenate = function (a, b) { return '-' + b.toLowerCase(); };
      /*jslint unparam: false*/

      // convert legacy
      if (styles && styles.color) {
         styles.fill = styles.color;
      }

      // Merge the new styles with the old ones
      styles = extend(
         elemWrapper.styles,
         styles
      );


      // store object
      elemWrapper.styles = styles;


      // serialize and set style attribute
      if (isIE && !hasSVG) { // legacy IE doesn't support setting style attribute
         if (textWidth) {
            delete styles.width;
         }
         css(elemWrapper.element, styles);
      } else {
         for (n in styles) {
            serializedCss += n.replace(/([A-Z])/g, hyphenate) + ':' + styles[n] + ';';
         }
         elemWrapper.attr({
            style: serializedCss
         });
      }


      // re-build text
      if (textWidth && elemWrapper.added) {
         elemWrapper.renderer.buildText(elemWrapper);
      }

      return elemWrapper;
   },

   /**
    * Add an event listener
    * @param {String} eventType
    * @param {Function} handler
    */
   on: function (eventType, handler) {
      var fn = handler;
      // touch
      if (hasTouch && eventType === 'click') {
         eventType = 'touchstart';
         fn = function (e) {
            e.preventDefault();
            handler();
         };
      }
      // simplest possible event model for internal use
      this.element['on' + eventType] = fn;
      return this;
   },


   /**
    * Move an object and its children by x and y values
    * @param {Number} x
    * @param {Number} y
    */
   translate: function (x, y) {
      return this.attr({
         translateX: x,
         translateY: y
      });
   },

   /**
    * Invert a group, rotate and flip
    */
   invert: function () {
      var wrapper = this;
      wrapper.inverted = true;
      wrapper.updateTransform();
      return wrapper;
   },

   /**
    * Private method to update the transform attribute based on internal
    * properties
    */
   updateTransform: function () {
      var wrapper = this,
         translateX = wrapper.translateX || 0,
         translateY = wrapper.translateY || 0,
         inverted = wrapper.inverted,
         rotation = wrapper.rotation,
         transform = [];

      // flipping affects translate as adjustment for flipping around the group's axis
      if (inverted) {
         translateX += wrapper.attr('width');
         translateY += wrapper.attr('height');
      }

      // apply translate
      if (translateX || translateY) {
         transform.push('translate(' + translateX + ',' + translateY + ')');
      }

      // apply rotation
      if (inverted) {
         transform.push('rotate(90) scale(-1,1)');
      } else if (rotation) { // text rotation
         transform.push('rotate(' + rotation + ' ' + wrapper.x + ' ' + wrapper.y + ')');
      }

      if (transform.length) {
         attr(wrapper.element, 'transform', transform.join(' '));
      }
   },
   /**
    * Bring the element to the front
    */
   toFront: function () {
      var element = this.element;
      element.parentNode.appendChild(element);
      return this;
   },


   /**
    * Break down alignment options like align, verticalAlign, x and y
    * to x and y relative to the chart.
    *
    * @param {Object} alignOptions
    * @param {Boolean} alignByTranslate
    * @param {Object} box The box to align to, needs a width and height
    *
    */
   align: function (alignOptions, alignByTranslate, box) {
      var elemWrapper = this;

      if (!alignOptions) { // called on resize
         alignOptions = elemWrapper.alignOptions;
         alignByTranslate = elemWrapper.alignByTranslate;
      } else { // first call on instanciate
         elemWrapper.alignOptions = alignOptions;
         elemWrapper.alignByTranslate = alignByTranslate;
         if (!box) { // boxes other than renderer handle this internally
            elemWrapper.renderer.alignedObjects.push(elemWrapper);
         }
      }

      box = pick(box, elemWrapper.renderer);

      var align = alignOptions.align,
         vAlign = alignOptions.verticalAlign,
         x = (box.x || 0) + (alignOptions.x || 0), // default: left align
         y = (box.y || 0) + (alignOptions.y || 0), // default: top align
         attribs = {};


      // align
      if (/^(right|center)$/.test(align)) {
         x += (box.width - (alignOptions.width || 0)) /
               { right: 1, center: 2 }[align];
      }
      attribs[alignByTranslate ? 'translateX' : 'x'] = mathRound(x);


      // vertical align
      if (/^(bottom|middle)$/.test(vAlign)) {
         y += (box.height - (alignOptions.height || 0)) /
               ({ bottom: 1, middle: 2 }[vAlign] || 1);

      }
      attribs[alignByTranslate ? 'translateY' : 'y'] = mathRound(y);

      // animate only if already placed
      elemWrapper[elemWrapper.placed ? 'animate' : 'attr'](attribs);
      elemWrapper.placed = true;
      elemWrapper.alignAttr = attribs;

      return elemWrapper;
   },

   /**
    * Get the bounding box (width, height, x and y) for the element
    */
   getBBox: function () {
      var bBox,
         width,
         height,
         rotation = this.rotation,
         rad = rotation * deg2rad;

      try { // fails in Firefox if the container has display: none
         // use extend because IE9 is not allowed to change width and height in case
         // of rotation (below)
         bBox = extend({}, this.element.getBBox());
      } catch (e) {
         bBox = { width: 0, height: 0 };
      }
      width = bBox.width;
      height = bBox.height;

      // adjust for rotated text
      if (rotation) {
         bBox.width = mathAbs(height * mathSin(rad)) + mathAbs(width * mathCos(rad));
         bBox.height = mathAbs(height * mathCos(rad)) + mathAbs(width * mathSin(rad));
      }

      return bBox;
   },

   /* *
    * Manually compute width and height of rotated text from non-rotated. Shared by SVG and VML
    * @param {Object} bBox
    * @param {number} rotation
    * /
   rotateBBox: function(bBox, rotation) {
      var rad = rotation * math.PI * 2 / 360, // radians
         width = bBox.width,
         height = bBox.height;


   },*/

   /**
    * Show the element
    */
   show: function () {
      return this.attr({ visibility: VISIBLE });
   },

   /**
    * Hide the element
    */
   hide: function () {
      return this.attr({ visibility: HIDDEN });
   },

   /**
    * Add the element
    * @param {Object|Undefined} parent Can be an element, an element wrapper or undefined
    *    to append the element to the renderer.box.
    */
   add: function (parent) {

      var renderer = this.renderer,
         parentWrapper = parent || renderer,
         parentNode = parentWrapper.element || renderer.box,
         childNodes = parentNode.childNodes,
         element = this.element,
         zIndex = attr(element, 'zIndex'),
         otherElement,
         otherZIndex,
         i;

      // mark as inverted
      this.parentInverted = parent && parent.inverted;

      // build formatted text
      if (this.textStr !== undefined) {
         renderer.buildText(this);
      }

      // register html spans in groups
      if (parent && this.htmlNode) {
         if (!parent.htmlNode) {
            parent.htmlNode = [];
         }
         parent.htmlNode.push(this);
      }

      // mark the container as having z indexed children
      if (zIndex) {
         parentWrapper.handleZ = true;
         zIndex = pInt(zIndex);
      }

      // insert according to this and other elements' zIndex
      if (parentWrapper.handleZ) { // this element or any of its siblings has a z index
         for (i = 0; i < childNodes.length; i++) {
            otherElement = childNodes[i];
            otherZIndex = attr(otherElement, 'zIndex');
            if (otherElement !== element && (
                  // insert before the first element with a higher zIndex
                  pInt(otherZIndex) > zIndex ||
                  // if no zIndex given, insert before the first element with a zIndex
                  (!defined(zIndex) && defined(otherZIndex))

                  )) {
               parentNode.insertBefore(element, otherElement);
               return this;
            }
         }
      }

      // default: append at the end
      parentNode.appendChild(element);

      this.added = true;

      return this;
   },

   /**
    * Removes a child either by removeChild or move to garbageBin.
    * Issue 490; in VML removeChild results in Orphaned nodes according to sIEve, discardElement does not.
    */
   safeRemoveChild: function (element) {
      var parentNode = element.parentNode;
      if (parentNode) {
         parentNode.removeChild(element);
      }
   },

   /**
    * Destroy the element and element wrapper
    */
   destroy: function () {
      var wrapper = this,
         element = wrapper.element || {},
         shadows = wrapper.shadows,
         key,
         i;

      // remove events
      element.onclick = element.onmouseout = element.onmouseover = element.onmousemove = null;
      stop(wrapper); // stop running animations

      if (wrapper.clipPath) {
         wrapper.clipPath = wrapper.clipPath.destroy();
      }

      // Destroy stops in case this is a gradient object
      if (wrapper.stops) {
         for (i = 0; i < wrapper.stops.length; i++) {
            wrapper.stops[i] = wrapper.stops[i].destroy();
         }
         wrapper.stops = null;
      }

      // remove element
      wrapper.safeRemoveChild(element);

      // destroy shadows
      if (shadows) {
         each(shadows, function (shadow) {
            wrapper.safeRemoveChild(shadow);
         });
      }

      // remove from alignObjects
      erase(wrapper.renderer.alignedObjects, wrapper);

      for (key in wrapper) {
         delete wrapper[key];
      }

      return null;
   },

   /**
    * Empty a group element
    */
   empty: function () {
      var element = this.element,
         childNodes = element.childNodes,
         i = childNodes.length;

      while (i--) {
         element.removeChild(childNodes[i]);
      }
   },

   /**
    * Add a shadow to the element. Must be done after the element is added to the DOM
    * @param {Boolean} apply
    */
   shadow: function (apply, group) {
      var shadows = [],
         i,
         shadow,
         element = this.element,

         // compensate for inverted plot area
         transform = this.parentInverted ? '(-1,-1)' : '(1,1)';


      if (apply) {
         for (i = 1; i <= 3; i++) {
            shadow = element.cloneNode(0);
            attr(shadow, {
               'isShadow': 'true',
               'stroke': 'rgb(0, 0, 0)',
               'stroke-opacity': 0.05 * i,
               'stroke-width': 7 - 2 * i,
               'transform': 'translate' + transform,
               'fill': NONE
            });

            if (group) {
               group.element.appendChild(shadow);
            } else {
               element.parentNode.insertBefore(shadow, element);
            }

            shadows.push(shadow);
         }

         this.shadows = shadows;
      }
      return this;

   }
};

/**
 * The default SVG renderer
 */
var SVGRenderer = function () {
   this.init.apply(this, arguments);
};
SVGRenderer.prototype = {

   Element: SVGElement,

   /**
    * Initialize the SVGRenderer
    * @param {Object} container
    * @param {Number} width
    * @param {Number} height
    * @param {Boolean} forExport
    */
   init: function (container, width, height, forExport) {
      var renderer = this,
         loc = location,
         boxWrapper;

      boxWrapper = renderer.createElement('svg')
         .attr({
            xmlns: SVG_NS,
            version: '1.1'
         });
      container.appendChild(boxWrapper.element);

      // object properties
      renderer.box = boxWrapper.element;
      renderer.boxWrapper = boxWrapper;
      renderer.alignedObjects = [];
      renderer.url = isIE ? '' : loc.href.replace(/#.*?$/, ''); // page url used for internal references
      renderer.defs = this.createElement('defs').add();
      renderer.forExport = forExport;
      renderer.gradients = []; // Array where gradient SvgElements are stored

      renderer.setSize(width, height, false);

   },

   /**
    * Destroys the renderer and its allocated members.
    */
   destroy: function () {
      var renderer = this,
         i,
         rendererGradients = renderer.gradients,
         rendererDefs = renderer.defs;
      renderer.box = null;
      renderer.boxWrapper = renderer.boxWrapper.destroy();

      // Call destroy on all gradient elements
      if (rendererGradients) { // gradients are null in VMLRenderer
         for (i = 0; i < rendererGradients.length; i++) {
            renderer.gradients[i] = rendererGradients[i].destroy();
         }
         renderer.gradients = null;
      }

      // Defs are null in VMLRenderer
      // Otherwise, destroy them here.
      if (rendererDefs) {
         renderer.defs = rendererDefs.destroy();
      }

      renderer.alignedObjects = null;

      return null;
   },

   /**
    * Create a wrapper for an SVG element
    * @param {Object} nodeName
    */
   createElement: function (nodeName) {
      var wrapper = new this.Element();
      wrapper.init(this, nodeName);
      return wrapper;
   },


   /**
    * Parse a simple HTML string into SVG tspans
    *
    * @param {Object} textNode The parent text SVG node
    */
   buildText: function (wrapper) {
      var textNode = wrapper.element,
         lines = pick(wrapper.textStr, '').toString()
            .replace(/<(b|strong)>/g, '<span style="font-weight:bold">')
            .replace(/<(i|em)>/g, '<span style="font-style:italic">')
            .replace(/<a/g, '<span')
            .replace(/<\/(b|strong|i|em|a)>/g, '</span>')
            .split(/<br.*?>/g),
         childNodes = textNode.childNodes,
         styleRegex = /style="([^"]+)"/,
         hrefRegex = /href="([^"]+)"/,
         parentX = attr(textNode, 'x'),
         textStyles = wrapper.styles,
         renderAsHtml = textStyles && wrapper.useHTML && !this.forExport,
         htmlNode = wrapper.htmlNode,
         //arr, issue #38 workaround
         width = textStyles && pInt(textStyles.width),
         textLineHeight = textStyles && textStyles.lineHeight,
         lastLine,
         GET_COMPUTED_STYLE = 'getComputedStyle',
         i = childNodes.length;

      // remove old text
      while (i--) {
         textNode.removeChild(childNodes[i]);
      }

      if (width && !wrapper.added) {
         this.box.appendChild(textNode); // attach it to the DOM to read offset width
      }

      each(lines, function (line, lineNo) {
         var spans, spanNo = 0, lineHeight;

         line = line.replace(/<span/g, '|||<span').replace(/<\/span>/g, '</span>|||');
         spans = line.split('|||');

         each(spans, function (span) {
            if (span !== '' || spans.length === 1) {
               var attributes = {},
                  tspan = doc.createElementNS(SVG_NS, 'tspan');
               if (styleRegex.test(span)) {
                  attr(
                     tspan,
                     'style',
                     span.match(styleRegex)[1].replace(/(;| |^)color([ :])/, '$1fill$2')
                  );
               }
               if (hrefRegex.test(span)) {
                  attr(tspan, 'onclick', 'location.href=\"' + span.match(hrefRegex)[1] + '\"');
                  css(tspan, { cursor: 'pointer' });
               }

               span = (span.replace(/<(.|\n)*?>/g, '') || ' ')
                  .replace(/&lt;/g, '<')
                  .replace(/&gt;/g, '>');

               // issue #38 workaround.
               /*if (reverse) {
                  arr = [];
                  i = span.length;
                  while (i--) {
                     arr.push(span.charAt(i));
                  }
                  span = arr.join('');
               }*/

               // add the text node
               tspan.appendChild(doc.createTextNode(span));

               if (!spanNo) { // first span in a line, align it to the left
                  attributes.x = parentX;
               } else {
                  // Firefox ignores spaces at the front or end of the tspan
                  attributes.dx = 3; // space
               }

               // first span on subsequent line, add the line height
               if (!spanNo) {
                  if (lineNo) {

                     // allow getting the right offset height in exporting in IE
                     if (!hasSVG && wrapper.renderer.forExport) {
                        css(tspan, { display: 'block' });
                     }

                     // Webkit and opera sometimes return 'normal' as the line height. In that
                     // case, webkit uses offsetHeight, while Opera falls back to 18
                     lineHeight = win[GET_COMPUTED_STYLE] &&
                        pInt(win[GET_COMPUTED_STYLE](lastLine, null).getPropertyValue('line-height'));

                     if (!lineHeight || isNaN(lineHeight)) {
                        lineHeight = textLineHeight || lastLine.offsetHeight || 18;
                     }
                     attr(tspan, 'dy', lineHeight);
                  }
                  lastLine = tspan; // record for use in next line
               }

               // add attributes
               attr(tspan, attributes);

               // append it
               textNode.appendChild(tspan);

               spanNo++;

               // check width and apply soft breaks
               if (width) {
                  var words = span.replace(/-/g, '- ').split(' '),
                     tooLong,
                     actualWidth,
                     rest = [];

                  while (words.length || rest.length) {
                     actualWidth = textNode.getBBox().width;
                     tooLong = actualWidth > width;
                     if (!tooLong || words.length === 1) { // new line needed
                        words = rest;
                        rest = [];
                        if (words.length) {
                           tspan = doc.createElementNS(SVG_NS, 'tspan');
                           attr(tspan, {
                              dy: textLineHeight || 16,
                              x: parentX
                           });
                           textNode.appendChild(tspan);

                           if (actualWidth > width) { // a single word is pressing it out
                              width = actualWidth;
                           }
                        }
                     } else { // append to existing line tspan
                        tspan.removeChild(tspan.firstChild);
                        rest.unshift(words.pop());
                     }
                     if (words.length) {
                        tspan.appendChild(doc.createTextNode(words.join(' ').replace(/- /g, '-')));
                     }
                  }
               }
            }
         });
      });

      // Fix issue #38 and allow HTML in tooltips and other labels
      if (renderAsHtml) {
         if (!htmlNode) {
            htmlNode = wrapper.htmlNode = createElement('span', null, extend(textStyles, {
               position: ABSOLUTE,
               top: 0,
               left: 0
            }), this.box.parentNode);
         }
         htmlNode.innerHTML = wrapper.textStr;

         i = childNodes.length;
         while (i--) {
            childNodes[i].style.visibility = HIDDEN;
         }
      }
   },

   /**
    * Make a straight line crisper by not spilling out to neighbour pixels
    * @param {Array} points
    * @param {Number} width
    */
   crispLine: function (points, width) {
      // points format: [M, 0, 0, L, 100, 0]
      // normalize to a crisp line
      if (points[1] === points[4]) {
         points[1] = points[4] = mathRound(points[1]) + (width % 2 / 2);
      }
      if (points[2] === points[5]) {
         points[2] = points[5] = mathRound(points[2]) + (width % 2 / 2);
      }
      return points;
   },


   /**
    * Draw a path
    * @param {Array} path An SVG path in array form
    */
   path: function (path) {
      return this.createElement('path').attr({
         d: path,
         fill: NONE
      });
   },

   /**
    * Draw and return an SVG circle
    * @param {Number} x The x position
    * @param {Number} y The y position
    * @param {Number} r The radius
    */
   circle: function (x, y, r) {
      var attr = isObject(x) ?
         x :
         {
            x: x,
            y: y,
            r: r
         };

      return this.createElement('circle').attr(attr);
   },

   /**
    * Draw and return an arc
    * @param {Number} x X position
    * @param {Number} y Y position
    * @param {Number} r Radius
    * @param {Number} innerR Inner radius like used in donut charts
    * @param {Number} start Starting angle
    * @param {Number} end Ending angle
    */
   arc: function (x, y, r, innerR, start, end) {
      // arcs are defined as symbols for the ability to set
      // attributes in attr and animate

      if (isObject(x)) {
         y = x.y;
         r = x.r;
         innerR = x.innerR;
         start = x.start;
         end = x.end;
         x = x.x;
      }

      return this.symbol('arc', x || 0, y || 0, r || 0, {
         innerR: innerR || 0,
         start: start || 0,
         end: end || 0
      });
   },

   /**
    * Draw and return a rectangle
    * @param {Number} x Left position
    * @param {Number} y Top position
    * @param {Number} width
    * @param {Number} height
    * @param {Number} r Border corner radius
    * @param {Number} strokeWidth A stroke width can be supplied to allow crisp drawing
    */
   rect: function (x, y, width, height, r, strokeWidth) {
      if (isObject(x)) {
         y = x.y;
         width = x.width;
         height = x.height;
         r = x.r;
         strokeWidth = x.strokeWidth;
         x = x.x;
      }
      var wrapper = this.createElement('rect').attr({
         rx: r,
         ry: r,
         fill: NONE
      });

      return wrapper.attr(wrapper.crisp(strokeWidth, x, y, mathMax(width, 0), mathMax(height, 0)));
   },

   /**
    * Resize the box and re-align all aligned elements
    * @param {Object} width
    * @param {Object} height
    * @param {Boolean} animate
    *
    */
   setSize: function (width, height, animate) {
      var renderer = this,
         alignedObjects = renderer.alignedObjects,
         i = alignedObjects.length;

      renderer.width = width;
      renderer.height = height;

      renderer.boxWrapper[pick(animate, true) ? 'animate' : 'attr']({
         width: width,
         height: height
      });

      while (i--) {
         alignedObjects[i].align();
      }
   },

   /**
    * Create a group
    * @param {String} name The group will be given a class name of 'highcharts-{name}'.
    *     This can be used for styling and scripting.
    */
   g: function (name) {
      var elem = this.createElement('g');
      return defined(name) ? elem.attr({ 'class': PREFIX + name }) : elem;
   },

   /**
    * Display an image
    * @param {String} src
    * @param {Number} x
    * @param {Number} y
    * @param {Number} width
    * @param {Number} height
    */
   image: function (src, x, y, width, height) {
      var attribs = {
            preserveAspectRatio: NONE
         },
         elemWrapper;

      // optional properties
      if (arguments.length > 1) {
         extend(attribs, {
            x: x,
            y: y,
            width: width,
            height: height
         });
      }

      elemWrapper = this.createElement('image').attr(attribs);

      // set the href in the xlink namespace
      if (elemWrapper.element.setAttributeNS) {
         elemWrapper.element.setAttributeNS('http://www.w3.org/1999/xlink',
            'href', src);
      } else {
         // could be exporting in IE
         // using href throws "not supported" in ie7 and under, requries regex shim to fix later
         elemWrapper.element.setAttribute('hc-svg-href', src);
      }

      return elemWrapper;
   },

   /**
    * Draw a symbol out of pre-defined shape paths from the namespace 'symbol' object.
    *
    * @param {Object} symbol
    * @param {Object} x
    * @param {Object} y
    * @param {Object} radius
    * @param {Object} options
    */
   symbol: function (symbol, x, y, radius, options) {

      var obj,

         // get the symbol definition function
         symbolFn = this.symbols[symbol],

         // check if there's a path defined for this symbol
         path = symbolFn && symbolFn(
            mathRound(x),
            mathRound(y),
            radius,
            options
         ),

         imageRegex = /^url\((.*?)\)$/,
         imageSrc,
         imageSize;

      if (path) {

         obj = this.path(path);
         // expando properties for use in animate and attr
         extend(obj, {
            symbolName: symbol,
            x: x,
            y: y,
            r: radius
         });
         if (options) {
            extend(obj, options);
         }


      // image symbols
      } else if (imageRegex.test(symbol)) {

         var centerImage = function (img, size) {
            img.attr({
               width: size[0],
               height: size[1]
            }).translate(
               -mathRound(size[0] / 2),
               -mathRound(size[1] / 2)
            );
         };

         imageSrc = symbol.match(imageRegex)[1];
         imageSize = symbolSizes[imageSrc];

         // create the image synchronously, add attribs async
         obj = this.image(imageSrc)
            .attr({
               x: x,
               y: y
            });

         if (imageSize) {
            centerImage(obj, imageSize);
         } else {
            // initialize image to be 0 size so export will still function if there's no cached sizes
            obj.attr({ width: 0, height: 0 });

            // create a dummy JavaScript image to get the width and height
            createElement('img', {
               onload: function () {
                  var img = this;
                  centerImage(obj, symbolSizes[imageSrc] = [img.width, img.height]);
               },
               src: imageSrc
            });
         }

      // default circles
      } else {
         obj = this.circle(x, y, radius);
      }

      return obj;
   },

   /**
    * An extendable collection of functions for defining symbol paths.
    */
   symbols: {
      'square': function (x, y, radius) {
         var len = 0.707 * radius;
         return [
            M, x - len, y - len,
            L, x + len, y - len,
            x + len, y + len,
            x - len, y + len,
            'Z'
         ];
      },

      'triangle': function (x, y, radius) {
         return [
            M, x, y - 1.33 * radius,
            L, x + radius, y + 0.67 * radius,
            x - radius, y + 0.67 * radius,
            'Z'
         ];
      },

      'triangle-down': function (x, y, radius) {
         return [
            M, x, y + 1.33 * radius,
            L, x - radius, y - 0.67 * radius,
            x + radius, y - 0.67 * radius,
            'Z'
         ];
      },
      'diamond': function (x, y, radius) {
         return [
            M, x, y - radius,
            L, x + radius, y,
            x, y + radius,
            x - radius, y,
            'Z'
         ];
      },
      'arc': function (x, y, radius, options) {
         var start = options.start,
            end = options.end - 0.000001, // to prevent cos and sin of start and end from becoming equal on 360 arcs
            innerRadius = options.innerR,
            cosStart = mathCos(start),
            sinStart = mathSin(start),
            cosEnd = mathCos(end),
            sinEnd = mathSin(end),
            longArc = options.end - start < mathPI ? 0 : 1;

         return [
            M,
            x + radius * cosStart,
            y + radius * sinStart,
            'A', // arcTo
            radius, // x radius
            radius, // y radius
            0, // slanting
            longArc, // long or short arc
            1, // clockwise
            x + radius * cosEnd,
            y + radius * sinEnd,
            L,
            x + innerRadius * cosEnd,
            y + innerRadius * sinEnd,
            'A', // arcTo
            innerRadius, // x radius
            innerRadius, // y radius
            0, // slanting
            longArc, // long or short arc
            0, // clockwise
            x + innerRadius * cosStart,
            y + innerRadius * sinStart,

            'Z' // close
         ];
      }
   },

   /**
    * Define a clipping rectangle
    * @param {String} id
    * @param {Number} x
    * @param {Number} y
    * @param {Number} width
    * @param {Number} height
    */
   clipRect: function (x, y, width, height) {
      var wrapper,
         id = PREFIX + idCounter++,

         clipPath = this.createElement('clipPath').attr({
            id: id
         }).add(this.defs);

      wrapper = this.rect(x, y, width, height, 0).add(clipPath);
      wrapper.id = id;
      wrapper.clipPath = clipPath;

      return wrapper;
   },


   /**
    * Take a color and return it if it's a string, make it a gradient if it's a
    * gradient configuration object
    *
    * @param {Object} color The color or config object
    */
   color: function (color, elem, prop) {
      var colorObject,
         regexRgba = /^rgba/;
      if (color && color.linearGradient) {
         var renderer = this,
            strLinearGradient = 'linearGradient',
            linearGradient = color[strLinearGradient],
            id = PREFIX + idCounter++,
            gradientObject,
            stopColor,
            stopOpacity;
         gradientObject = renderer.createElement(strLinearGradient).attr({
            id: id,
            gradientUnits: 'userSpaceOnUse',
            x1: linearGradient[0],
            y1: linearGradient[1],
            x2: linearGradient[2],
            y2: linearGradient[3]
         }).add(renderer.defs);

         // Keep a reference to the gradient object so it is possible to destroy it later
         renderer.gradients.push(gradientObject);

         // The gradient needs to keep a list of stops to be able to destroy them
         gradientObject.stops = [];
         each(color.stops, function (stop) {
            var stopObject;
            if (regexRgba.test(stop[1])) {
               colorObject = Color(stop[1]);
               stopColor = colorObject.get('rgb');
               stopOpacity = colorObject.get('a');
            } else {
               stopColor = stop[1];
               stopOpacity = 1;
            }
            stopObject = renderer.createElement('stop').attr({
               offset: stop[0],
               'stop-color': stopColor,
               'stop-opacity': stopOpacity
            }).add(gradientObject);

            // Add the stop element to the gradient
            gradientObject.stops.push(stopObject);
         });

         return 'url(' + this.url + '#' + id + ')';

      // Webkit and Batik can't show rgba.
      } else if (regexRgba.test(color)) {
         colorObject = Color(color);
         attr(elem, prop + '-opacity', colorObject.get('a'));

         return colorObject.get('rgb');


      } else {
         // Remove the opacity attribute added above. Does not throw if the attribute is not there.
         elem.removeAttribute(prop + '-opacity');

         return color;
      }

   },


   /**
    * Add text to the SVG object
    * @param {String} str
    * @param {Number} x Left position
    * @param {Number} y Top position
    * @param {Boolean} useHTML Use HTML to render the text
    */
   text: function (str, x, y, useHTML) {

      // declare variables
      var defaultChartStyle = defaultOptions.chart.style,
         wrapper;

      x = mathRound(pick(x, 0));
      y = mathRound(pick(y, 0));

      wrapper = this.createElement('text')
         .attr({
            x: x,
            y: y,
            text: str
         })
         .css({
            fontFamily: defaultChartStyle.fontFamily,
            fontSize: defaultChartStyle.fontSize
         });

      wrapper.x = x;
      wrapper.y = y;
      wrapper.useHTML = useHTML;
      return wrapper;
   }
}; // end SVGRenderer

// general renderer
Renderer = SVGRenderer;



/* ****************************************************************************
 *                                                                            *
 * START OF INTERNET EXPLORER <= 8 SPECIFIC CODE                              *
 *                                                                            *
 * For applications and websites that don't need IE support, like platform    *
 * targeted mobile apps and web apps, this code can be removed.               *
 *                                                                            *
 *****************************************************************************/
var VMLRenderer;
if (!hasSVG) {

/**
 * The VML element wrapper.
 */
var VMLElement = extendClass(SVGElement, {

   /**
    * Initialize a new VML element wrapper. It builds the markup as a string
    * to minimize DOM traffic.
    * @param {Object} renderer
    * @param {Object} nodeName
    */
   init: function (renderer, nodeName) {
      var markup =  ['<', nodeName, ' filled="f" stroked="f"'],
         style = ['position: ', ABSOLUTE, ';'];

      // divs and shapes need size
      if (nodeName === 'shape' || nodeName === DIV) {
         style.push('left:0;top:0;width:10px;height:10px;');
      }
      if (docMode8) {
         style.push('visibility: ', nodeName === DIV ? HIDDEN : VISIBLE);
      }

      markup.push(' style="', style.join(''), '"/>');

      // create element with default attributes and style
      if (nodeName) {
         markup = nodeName === DIV || nodeName === 'span' || nodeName === 'img' ?
            markup.join('')
            : renderer.prepVML(markup);
         this.element = createElement(markup);
      }

      this.renderer = renderer;
   },

   /**
    * Add the node to the given parent
    * @param {Object} parent
    */
   add: function (parent) {
      var wrapper = this,
         renderer = wrapper.renderer,
         element = wrapper.element,
         box = renderer.box,
         inverted = parent && parent.inverted,

         // get the parent node
         parentNode = parent ?
            parent.element || parent :
            box;


      // if the parent group is inverted, apply inversion on all children
      if (inverted) { // only on groups
         renderer.invertChild(element, parentNode);
      }

      // issue #140 workaround - related to #61 and #74
      if (docMode8 && parentNode.gVis === HIDDEN) {
         css(element, { visibility: HIDDEN });
      }

      // append it
      parentNode.appendChild(element);

      // align text after adding to be able to read offset
      wrapper.added = true;
      if (wrapper.alignOnAdd) {
         wrapper.updateTransform();
      }

      return wrapper;
   },

   /**
    * Get or set attributes
    */
   attr: function (hash, val) {
      var key,
         value,
         i,
         element = this.element || {},
         elemStyle = element.style,
         nodeName = element.nodeName,
         renderer = this.renderer,
         symbolName = this.symbolName,
         childNodes,
         hasSetSymbolSize,
         shadows = this.shadows,
         skipAttr,
         ret = this;

      // single key-value pair
      if (isString(hash) && defined(val)) {
         key = hash;
         hash = {};
         hash[key] = val;
      }

      // used as a getter, val is undefined
      if (isString(hash)) {
         key = hash;
         if (key === 'strokeWidth' || key === 'stroke-width') {
            ret = this.strokeweight;
         } else {
            ret = this[key];
         }

      // setter
      } else {
         for (key in hash) {
            value = hash[key];
            skipAttr = false;

            // prepare paths
            // symbols
            if (symbolName && /^(x|y|r|start|end|width|height|innerR)/.test(key)) {
               // if one of the symbol size affecting parameters are changed,
               // check all the others only once for each call to an element's
               // .attr() method
               if (!hasSetSymbolSize) {
                  this.symbolAttr(hash);

                  hasSetSymbolSize = true;
               }

               skipAttr = true;

            } else if (key === 'd') {
               value = value || [];
               this.d = value.join(' '); // used in getter for animation

               // convert paths
               i = value.length;
               var convertedPath = [];
               while (i--) {

                  // Multiply by 10 to allow subpixel precision.
                  // Substracting half a pixel seems to make the coordinates
                  // align with SVG, but this hasn't been tested thoroughly
                  if (isNumber(value[i])) {
                     convertedPath[i] = mathRound(value[i] * 10) - 5;
                  } else if (value[i] === 'Z') { // close the path
                     convertedPath[i] = 'x';
                  } else {
                     convertedPath[i] = value[i];
                  }

               }
               value = convertedPath.join(' ') || 'x';
               element.path = value;

               // update shadows
               if (shadows) {
                  i = shadows.length;
                  while (i--) {
                     shadows[i].path = value;
                  }
               }
               skipAttr = true;

            // directly mapped to css
            } else if (key === 'zIndex' || key === 'visibility') {

               // issue 61 workaround
               if (docMode8 && key === 'visibility' && nodeName === 'DIV') {
                  element.gVis = value;
                  childNodes = element.childNodes;
                  i = childNodes.length;
                  while (i--) {
                     css(childNodes[i], { visibility: value });
                  }
                  if (value === VISIBLE) { // issue 74
                     value = null;
                  }
               }

               if (value) {
                  elemStyle[key] = value;
               }



               skipAttr = true;

            // width and height
            } else if (/^(width|height)$/.test(key)) {

               this[key] = value; // used in getter

               // clipping rectangle special
               if (this.updateClipping) {
                  this[key] = value;
                  this.updateClipping();

               } else {
                  // normal
                  elemStyle[key] = value;
               }

               skipAttr = true;

            // x and y
            } else if (/^(x|y)$/.test(key)) {

               this[key] = value; // used in getter

               if (element.tagName === 'SPAN') {
                  this.updateTransform();

               } else {
                  elemStyle[{ x: 'left', y: 'top' }[key]] = value;
               }

            // class name
            } else if (key === 'class') {
               // IE8 Standards mode has problems retrieving the className
               element.className = value;

            // stroke
            } else if (key === 'stroke') {

               value = renderer.color(value, element, key);

               key = 'strokecolor';

            // stroke width
            } else if (key === 'stroke-width' || key === 'strokeWidth') {
               element.stroked = value ? true : false;
               key = 'strokeweight';
               this[key] = value; // used in getter, issue #113
               if (isNumber(value)) {
                  value += PX;
               }

            // dashStyle
            } else if (key === 'dashstyle') {
               var strokeElem = element.getElementsByTagName('stroke')[0] ||
                  createElement(renderer.prepVML(['<stroke/>']), null, null, element);
               strokeElem[key] = value || 'solid';
               this.dashstyle = value; /* because changing stroke-width will change the dash length
                  and cause an epileptic effect */
               skipAttr = true;

            // fill
            } else if (key === 'fill') {

               if (nodeName === 'SPAN') { // text color
                  elemStyle.color = value;
               } else {
                  element.filled = value !== NONE ? true : false;

                  value = renderer.color(value, element, key);

                  key = 'fillcolor';
               }

            // translation for animation
            } else if (key === 'translateX' || key === 'translateY' || key === 'rotation' || key === 'align') {
               if (key === 'align') {
                  key = 'textAlign';
               }
               this[key] = value;
               this.updateTransform();

               skipAttr = true;
            } else if (key === 'text') { // text for rotated and non-rotated elements
               this.bBox = null;
               element.innerHTML = value;
               skipAttr = true;
            }


            // let the shadow follow the main element
            if (shadows && key === 'visibility') {
               i = shadows.length;
               while (i--) {
                  shadows[i].style[key] = value;
               }
            }



            if (!skipAttr) {
               if (docMode8) { // IE8 setAttribute bug
                  element[key] = value;
               } else {
                  attr(element, key, value);
               }
            }
         }
      }
      return ret;
   },

   /**
    * Set the element's clipping to a predefined rectangle
    *
    * @param {String} id The id of the clip rectangle
    */
   clip: function (clipRect) {
      var wrapper = this,
         clipMembers = clipRect.members;

      clipMembers.push(wrapper);
      wrapper.destroyClip = function () {
         erase(clipMembers, wrapper);
      };
      return wrapper.css(clipRect.getCSS(wrapper.inverted));
   },

   /**
    * Set styles for the element
    * @param {Object} styles
    */
   css: function (styles) {
      var wrapper = this,
         element = wrapper.element,
         textWidth = styles && element.tagName === 'SPAN' && styles.width;

      /*if (textWidth) {
         extend(styles, {
            display: 'block',
            whiteSpace: 'normal'
         });
      }*/
      if (textWidth) {
         delete styles.width;
         wrapper.textWidth = textWidth;
         wrapper.updateTransform();
      }

      wrapper.styles = extend(wrapper.styles, styles);
      css(wrapper.element, styles);

      return wrapper;
   },

   /**
    * Removes a child either by removeChild or move to garbageBin.
    * Issue 490; in VML removeChild results in Orphaned nodes according to sIEve, discardElement does not.
    */
   safeRemoveChild: function (element) {
      // discardElement will detach the node from its parent before attaching it
      // to the garbage bin. Therefore it is important that the node is attached and have parent.
      var parentNode = element.parentNode;
      if (parentNode) {
         discardElement(element);
      }
   },

   /**
    * Extend element.destroy by removing it from the clip members array
    */
   destroy: function () {
      var wrapper = this;

      if (wrapper.destroyClip) {
         wrapper.destroyClip();
      }

      return SVGElement.prototype.destroy.apply(wrapper);
   },

   /**
    * Remove all child nodes of a group, except the v:group element
    */
   empty: function () {
      var element = this.element,
         childNodes = element.childNodes,
         i = childNodes.length,
         node;

      while (i--) {
         node = childNodes[i];
         node.parentNode.removeChild(node);
      }
   },

   /**
    * VML override for calculating the bounding box based on offsets
    *
    * @return {Object} A hash containing values for x, y, width and height
    */

   getBBox: function () {
      var wrapper = this,
         element = wrapper.element,
         bBox = wrapper.bBox;

      if (!bBox) {
         // faking getBBox in exported SVG in legacy IE
         if (element.nodeName === 'text') {
            element.style.position = ABSOLUTE;
         }

         bBox = wrapper.bBox = {
            x: element.offsetLeft,
            y: element.offsetTop,
            width: element.offsetWidth,
            height: element.offsetHeight
         };
      }
      return bBox;

   },

   /**
    * Add an event listener. VML override for normalizing event parameters.
    * @param {String} eventType
    * @param {Function} handler
    */
   on: function (eventType, handler) {
      // simplest possible event model for internal use
      this.element['on' + eventType] = function () {
         var evt = win.event;
         evt.target = evt.srcElement;
         handler(evt);
      };
      return this;
   },


   /**
    * VML override private method to update elements based on internal
    * properties based on SVG transform
    */
   updateTransform: function () {
      // aligning non added elements is expensive
      if (!this.added) {
         this.alignOnAdd = true;
         return;
      }

      var wrapper = this,
         elem = wrapper.element,
         translateX = wrapper.translateX || 0,
         translateY = wrapper.translateY || 0,
         x = wrapper.x || 0,
         y = wrapper.y || 0,
         align = wrapper.textAlign || 'left',
         alignCorrection = { left: 0, center: 0.5, right: 1 }[align],
         nonLeft = align && align !== 'left';

      // apply translate
      if (translateX || translateY) {
         wrapper.css({
            marginLeft: translateX,
            marginTop: translateY
         });
      }

      // apply inversion
      if (wrapper.inverted) { // wrapper is a group
         each(elem.childNodes, function (child) {
            wrapper.renderer.invertChild(child, elem);
         });
      }

      if (elem.tagName === 'SPAN') {

         var width, height,
            rotation = wrapper.rotation,
            lineHeight,
            radians = 0,
            costheta = 1,
            sintheta = 0,
            quad,
            textWidth = pInt(wrapper.textWidth),
            xCorr = wrapper.xCorr || 0,
            yCorr = wrapper.yCorr || 0,
            currentTextTransform = [rotation, align, elem.innerHTML, wrapper.textWidth].join(',');

         if (currentTextTransform !== wrapper.cTT) { // do the calculations and DOM access only if properties changed

            if (defined(rotation)) {
               radians = rotation * deg2rad; // deg to rad
               costheta = mathCos(radians);
               sintheta = mathSin(radians);

               // Adjust for alignment and rotation.
               // Test case: http://highcharts.com/tests/?file=text-rotation
               css(elem, {
                  filter: rotation ? ['progid:DXImageTransform.Microsoft.Matrix(M11=', costheta,
                     ', M12=', -sintheta, ', M21=', sintheta, ', M22=', costheta,
                     ', sizingMethod=\'auto expand\')'].join('') : NONE
               });
            }

            width = elem.offsetWidth;
            height = elem.offsetHeight;

            // update textWidth
            if (width > textWidth) {
               css(elem, {
                  width: textWidth + PX,
                  display: 'block',
                  whiteSpace: 'normal'
               });
               width = textWidth;
            }

            // correct x and y
            lineHeight = mathRound((pInt(elem.style.fontSize) || 12) * 1.2);
            xCorr = costheta < 0 && -width;
            yCorr = sintheta < 0 && -height;

            // correct for lineHeight and corners spilling out after rotation
            quad = costheta * sintheta < 0;
            xCorr += sintheta * lineHeight * (quad ? 1 - alignCorrection : alignCorrection);
            yCorr -= costheta * lineHeight * (rotation ? (quad ? alignCorrection : 1 - alignCorrection) : 1);

            // correct for the length/height of the text
            if (nonLeft) {
               xCorr -= width * alignCorrection * (costheta < 0 ? -1 : 1);
               if (rotation) {
                  yCorr -= height * alignCorrection * (sintheta < 0 ? -1 : 1);
               }
               css(elem, {
                  textAlign: align
               });
            }

            // record correction
            wrapper.xCorr = xCorr;
            wrapper.yCorr = yCorr;
         }

         // apply position with correction
         css(elem, {
            left: x + xCorr,
            top: y + yCorr
         });

         // record current text transform
         wrapper.cTT = currentTextTransform;
      }
   },

   /**
    * Apply a drop shadow by copying elements and giving them different strokes
    * @param {Boolean} apply
    */
   shadow: function (apply, group) {
      var shadows = [],
         i,
         element = this.element,
         renderer = this.renderer,
         shadow,
         elemStyle = element.style,
         markup,
         path = element.path;

      // some times empty paths are not strings
      if (path && typeof path.value !== 'string') {
         path = 'x';
      }

      if (apply) {
         for (i = 1; i <= 3; i++) {
            markup = ['<shape isShadow="true" strokeweight="', (7 - 2 * i),
               '" filled="false" path="', path,
               '" coordsize="100,100" style="', element.style.cssText, '" />'];
            shadow = createElement(renderer.prepVML(markup),
               null, {
                  left: pInt(elemStyle.left) + 1,
                  top: pInt(elemStyle.top) + 1
               }
            );

            // apply the opacity
            markup = ['<stroke color="black" opacity="', (0.05 * i), '"/>'];
            createElement(renderer.prepVML(markup), null, null, shadow);


            // insert it
            if (group) {
               group.element.appendChild(shadow);
            } else {
               element.parentNode.insertBefore(shadow, element);
            }

            // record it
            shadows.push(shadow);

         }

         this.shadows = shadows;
      }
      return this;

   }
});

/**
 * The VML renderer
 */
VMLRenderer = function () {
   this.init.apply(this, arguments);
};
VMLRenderer.prototype = merge(SVGRenderer.prototype, { // inherit SVGRenderer

   Element: VMLElement,
   isIE8: userAgent.indexOf('MSIE 8.0') > -1,


   /**
    * Initialize the VMLRenderer
    * @param {Object} container
    * @param {Number} width
    * @param {Number} height
    */
   init: function (container, width, height) {
      var renderer = this,
         boxWrapper;

      renderer.alignedObjects = [];

      boxWrapper = renderer.createElement(DIV);
      container.appendChild(boxWrapper.element);


      // generate the containing box
      renderer.box = boxWrapper.element;
      renderer.boxWrapper = boxWrapper;


      renderer.setSize(width, height, false);

      // The only way to make IE6 and IE7 print is to use a global namespace. However,
      // with IE8 the only way to make the dynamic shapes visible in screen and print mode
      // seems to be to add the xmlns attribute and the behaviour style inline.
      if (!doc.namespaces.hcv) {

         doc.namespaces.add('hcv', 'urn:schemas-microsoft-com:vml');

         // setup default css
         doc.createStyleSheet().cssText =
            'hcv\\:fill, hcv\\:path, hcv\\:shape, hcv\\:stroke' +
            '{ behavior:url(#default#VML); display: inline-block; } ';

      }
   },

   /**
    * Define a clipping rectangle. In VML it is accomplished by storing the values
    * for setting the CSS style to all associated members.
    *
    * @param {Number} x
    * @param {Number} y
    * @param {Number} width
    * @param {Number} height
    */
   clipRect: function (x, y, width, height) {

      // create a dummy element
      var clipRect = this.createElement();

      // mimic a rectangle with its style object for automatic updating in attr
      return extend(clipRect, {
         members: [],
         left: x,
         top: y,
         width: width,
         height: height,
         getCSS: function (inverted) {
            var rect = this,//clipRect.element.style,
               top = rect.top,
               left = rect.left,
               right = left + rect.width,
               bottom = top + rect.height,
               ret = {
                  clip: 'rect(' +
                     mathRound(inverted ? left : top) + 'px,' +
                     mathRound(inverted ? bottom : right) + 'px,' +
                     mathRound(inverted ? right : bottom) + 'px,' +
                     mathRound(inverted ? top : left) + 'px)'
               };

            // issue 74 workaround
            if (!inverted && docMode8) {
               extend(ret, {
                  width: right + PX,
                  height: bottom + PX
               });
            }
            return ret;
         },

         // used in attr and animation to update the clipping of all members
         updateClipping: function () {
            each(clipRect.members, function (member) {
               member.css(clipRect.getCSS(member.inverted));
            });
         }
      });

   },


   /**
    * Take a color and return it if it's a string, make it a gradient if it's a
    * gradient configuration object, and apply opacity.
    *
    * @param {Object} color The color or config object
    */
   color: function (color, elem, prop) {
      var colorObject,
         regexRgba = /^rgba/,
         markup;

      if (color && color.linearGradient) {

         var stopColor,
            stopOpacity,
            linearGradient = color.linearGradient,
            angle,
            color1,
            opacity1,
            color2,
            opacity2;

         each(color.stops, function (stop, i) {
            if (regexRgba.test(stop[1])) {
               colorObject = Color(stop[1]);
               stopColor = colorObject.get('rgb');
               stopOpacity = colorObject.get('a');
            } else {
               stopColor = stop[1];
               stopOpacity = 1;
            }

            if (!i) { // first
               color1 = stopColor;
               opacity1 = stopOpacity;
            } else {
               color2 = stopColor;
               opacity2 = stopOpacity;
            }
         });



         // calculate the angle based on the linear vector
         angle = 90  - math.atan(
            (linearGradient[3] - linearGradient[1]) / // y vector
            (linearGradient[2] - linearGradient[0]) // x vector
            ) * 180 / mathPI;

         // when colors attribute is used, the meanings of opacity and o:opacity2
         // are reversed.
         markup = ['<', prop, ' colors="0% ', color1, ',100% ', color2, '" angle="', angle,
            '" opacity="', opacity2, '" o:opacity2="', opacity1,
            '" type="gradient" focus="100%" />'];
         createElement(this.prepVML(markup), null, null, elem);



      // if the color is an rgba color, split it and add a fill node
      // to hold the opacity component
      } else if (regexRgba.test(color) && elem.tagName !== 'IMG') {

         colorObject = Color(color);

         markup = ['<', prop, ' opacity="', colorObject.get('a'), '"/>'];
         createElement(this.prepVML(markup), null, null, elem);

         return colorObject.get('rgb');


      } else {
         var strokeNodes = elem.getElementsByTagName(prop);
         if (strokeNodes.length) {
            strokeNodes[0].opacity = 1;
         }
         return color;
      }

   },

   /**
    * Take a VML string and prepare it for either IE8 or IE6/IE7.
    * @param {Array} markup A string array of the VML markup to prepare
    */
   prepVML: function (markup) {
      var vmlStyle = 'display:inline-block;behavior:url(#default#VML);',
         isIE8 = this.isIE8;

      markup = markup.join('');

      if (isIE8) { // add xmlns and style inline
         markup = markup.replace('/>', ' xmlns="urn:schemas-microsoft-com:vml" />');
         if (markup.indexOf('style="') === -1) {
            markup = markup.replace('/>', ' style="' + vmlStyle + '" />');
         } else {
            markup = markup.replace('style="', 'style="' + vmlStyle);
         }

      } else { // add namespace
         markup = markup.replace('<', '<hcv:');
      }

      return markup;
   },

   /**
    * Create rotated and aligned text
    * @param {String} str
    * @param {Number} x
    * @param {Number} y
    */
   text: function (str, x, y) {

      var defaultChartStyle = defaultOptions.chart.style;

      return this.createElement('span')
         .attr({
            text: str,
            x: mathRound(x),
            y: mathRound(y)
         })
         .css({
            whiteSpace: 'nowrap',
            fontFamily: defaultChartStyle.fontFamily,
            fontSize: defaultChartStyle.fontSize
         });
   },

   /**
    * Create and return a path element
    * @param {Array} path
    */
   path: function (path) {
      // create the shape
      return this.createElement('shape').attr({
         // subpixel precision down to 0.1 (width and height = 10px)
         coordsize: '100 100',
         d: path
      });
   },

   /**
    * Create and return a circle element. In VML circles are implemented as
    * shapes, which is faster than v:oval
    * @param {Number} x
    * @param {Number} y
    * @param {Number} r
    */
   circle: function (x, y, r) {
      return this.symbol('circle').attr({ x: x, y: y, r: r});
   },

   /**
    * Create a group using an outer div and an inner v:group to allow rotating
    * and flipping. A simple v:group would have problems with positioning
    * child HTML elements and CSS clip.
    *
    * @param {String} name The name of the group
    */
   g: function (name) {
      var wrapper,
         attribs;

      // set the class name
      if (name) {
         attribs = { 'className': PREFIX + name, 'class': PREFIX + name };
      }

      // the div to hold HTML and clipping
      wrapper = this.createElement(DIV).attr(attribs);

      return wrapper;
   },

   /**
    * VML override to create a regular HTML image
    * @param {String} src
    * @param {Number} x
    * @param {Number} y
    * @param {Number} width
    * @param {Number} height
    */
   image: function (src, x, y, width, height) {
      var obj = this.createElement('img')
         .attr({ src: src });

      if (arguments.length > 1) {
         obj.css({
            left: x,
            top: y,
            width: width,
            height: height
         });
      }
      return obj;
   },

   /**
    * VML uses a shape for rect to overcome bugs and rotation problems
    */
   rect: function (x, y, width, height, r, strokeWidth) {

      if (isObject(x)) {
         y = x.y;
         width = x.width;
         height = x.height;
         r = x.r;
         strokeWidth = x.strokeWidth;
         x = x.x;
      }
      var wrapper = this.symbol('rect');
      wrapper.r = r;

      return wrapper.attr(wrapper.crisp(strokeWidth, x, y, mathMax(width, 0), mathMax(height, 0)));
   },

   /**
    * In the VML renderer, each child of an inverted div (group) is inverted
    * @param {Object} element
    * @param {Object} parentNode
    */
   invertChild: function (element, parentNode) {
      var parentStyle = parentNode.style;

      css(element, {
         flip: 'x',
         left: pInt(parentStyle.width) - 10,
         top: pInt(parentStyle.height) - 10,
         rotation: -90
      });
   },

   /**
    * Symbol definitions that override the parent SVG renderer's symbols
    *
    */
   symbols: {
      // VML specific arc function
      arc: function (x, y, radius, options) {
         var start = options.start,
            end = options.end,
            cosStart = mathCos(start),
            sinStart = mathSin(start),
            cosEnd = mathCos(end),
            sinEnd = mathSin(end),
            innerRadius = options.innerR,
            circleCorrection = 0.07 / radius,
            innerCorrection = (innerRadius && 0.1 / innerRadius) || 0;

         if (end - start === 0) { // no angle, don't show it.
            return ['x'];

         //} else if (end - start == 2 * mathPI) { // full circle
         } else if (2 * mathPI - end + start < circleCorrection) { // full circle
            // empirical correction found by trying out the limits for different radii
            cosEnd = -circleCorrection;
         } else if (end - start < innerCorrection) { // issue #186, another mysterious VML arc problem
            cosEnd = mathCos(start + innerCorrection);
         }

         return [
            'wa', // clockwise arc to
            x - radius, // left
            y - radius, // top
            x + radius, // right
            y + radius, // bottom
            x + radius * cosStart, // start x
            y + radius * sinStart, // start y
            x + radius * cosEnd, // end x
            y + radius * sinEnd, // end y


            'at', // anti clockwise arc to
            x - innerRadius, // left
            y - innerRadius, // top
            x + innerRadius, // right
            y + innerRadius, // bottom
            x + innerRadius * cosEnd, // start x
            y + innerRadius * sinEnd, // start y
            x + innerRadius * cosStart, // end x
            y + innerRadius * sinStart, // end y

            'x', // finish path
            'e' // close
         ];

      },
      // Add circle symbol path. This performs significantly faster than v:oval.
      circle: function (x, y, r) {
         return [
            'wa', // clockwisearcto
            x - r, // left
            y - r, // top
            x + r, // right
            y + r, // bottom
            x + r, // start x
            y,     // start y
            x + r, // end x
            y,     // end y
            //'x', // finish path
            'e' // close
         ];
      },
      /**
       * Add rectangle symbol path which eases rotation and omits arcsize problems
       * compared to the built-in VML roundrect shape
       *
       * @param {Number} left Left position
       * @param {Number} top Top position
       * @param {Number} r Border radius
       * @param {Object} options Width and height
       */

      rect: function (left, top, r, options) {
         if (!defined(options)) {
            return [];
         }
         var width = options.width,
            height = options.height,
            right = left + width,
            bottom = top + height;

         r = mathMin(r, width, height);

         return [
            M,
            left + r, top,

            L,
            right - r, top,
            'wa',
            right - 2 * r, top,
            right, top + 2 * r,
            right - r, top,
            right, top + r,

            L,
            right, bottom - r,
            'wa',
            right - 2 * r, bottom - 2 * r,
            right, bottom,
            right, bottom - r,
            right - r, bottom,

            L,
            left + r, bottom,
            'wa',
            left, bottom - 2 * r,
            left + 2 * r, bottom,
            left + r, bottom,
            left, bottom - r,

            L,
            left, top + r,
            'wa',
            left, top,
            left + 2 * r, top + 2 * r,
            left, top + r,
            left + r, top,


            'x',
            'e'
         ];

      }
   }
});

// general renderer
Renderer = VMLRenderer;
}
/* ****************************************************************************
 *                                                                            *
 * END OF INTERNET EXPLORER <= 8 SPECIFIC CODE                                *
 *                                                                            *
 *****************************************************************************/


/**
 * The chart class
 * @param {Object} options
 * @param {Function} callback Function to run when the chart has loaded
 */
function Chart(options, callback) {

   defaultXAxisOptions = merge(defaultXAxisOptions, defaultOptions.xAxis);
   defaultYAxisOptions = merge(defaultYAxisOptions, defaultOptions.yAxis);
   defaultOptions.xAxis = defaultOptions.yAxis = null;

   // Handle regular options
   options = merge(defaultOptions, options);

   // Define chart variables
   var optionsChart = options.chart,
      optionsMargin = optionsChart.margin,
      margin = isObject(optionsMargin) ?
         optionsMargin :
         [optionsMargin, optionsMargin, optionsMargin, optionsMargin],
      optionsMarginTop = pick(optionsChart.marginTop, margin[0]),
      optionsMarginRight = pick(optionsChart.marginRight, margin[1]),
      optionsMarginBottom = pick(optionsChart.marginBottom, margin[2]),
      optionsMarginLeft = pick(optionsChart.marginLeft, margin[3]),
      spacingTop = optionsChart.spacingTop,
      spacingRight = optionsChart.spacingRight,
      spacingBottom = optionsChart.spacingBottom,
      spacingLeft = optionsChart.spacingLeft,
      spacingBox,
      chartTitleOptions,
      chartSubtitleOptions,
      plotTop,
      marginRight,
      marginBottom,
      plotLeft,
      axisOffset,
      renderTo,
      renderToClone,
      container,
      containerId,
      containerWidth,
      containerHeight,
      chartWidth,
      chartHeight,
      oldChartWidth,
      oldChartHeight,
      chartBackground,
      plotBackground,
      plotBGImage,
      plotBorder,
      chart = this,
      chartEvents = optionsChart.events,
      runChartClick = chartEvents && !!chartEvents.click,
      eventType,
      isInsidePlot, // function
      tooltip,
      mouseIsDown,
      loadingDiv,
      loadingSpan,
      loadingShown,
      plotHeight,
      plotWidth,
      tracker,
      trackerGroup,
      placeTrackerGroup,
      legend,
      legendWidth,
      legendHeight,
      chartPosition,// = getPosition(container),
      hasCartesianSeries = optionsChart.showAxes,
      isResizing = 0,
      axes = [],
      maxTicks, // handle the greatest amount of ticks on grouped axes
      series = [],
      inverted,
      renderer,
      tooltipTick,
      tooltipInterval,
      hoverX,
      drawChartBox, // function
      getMargins, // function
      resetMargins, // function
      setChartSize, // function
      resize,
      zoom, // function
      zoomOut; // function


   /**
    * Create a new axis object
    * @param {Object} options
    */
   function Axis(userOptions) {

      // Define variables
      var isXAxis = userOptions.isX,
         opposite = userOptions.opposite, // needed in setOptions
         horiz = inverted ? !isXAxis : isXAxis,
         side = horiz ?
            (opposite ? 0 : 2) : // top : bottom
            (opposite ? 1 : 3),  // right : left
         stacks = {},

         options = merge(
            isXAxis ? defaultXAxisOptions : defaultYAxisOptions,
            [defaultTopAxisOptions, defaultRightAxisOptions,
               defaultBottomAxisOptions, defaultLeftAxisOptions][side],
            userOptions
         ),

         axis = this,
         axisTitle,
         type = options.type,
         isDatetimeAxis = type === 'datetime',
         isLog = type === 'logarithmic',
         offset = options.offset || 0,
         xOrY = isXAxis ? 'x' : 'y',
         axisLength,
         transA, // translation factor
         oldTransA, // used for prerendering
         transB = horiz ? plotLeft : marginBottom, // translation addend
         translate, // fn
         getPlotLinePath, // fn
         axisGroup,
         gridGroup,
         axisLine,
         dataMin,
         dataMax,
         associatedSeries,
         userMin,
         userMax,
         max = null,
         min = null,
         oldMin,
         oldMax,
         minPadding = options.minPadding,
         maxPadding = options.maxPadding,
         isLinked = defined(options.linkedTo),
         ignoreMinPadding, // can be set to true by a column or bar series
         ignoreMaxPadding,
         usePercentage,
         events = options.events,
         eventType,
         plotLinesAndBands = [],
         tickInterval,
         minorTickInterval,
         magnitude,
         tickPositions, // array containing predefined positions
         ticks = {},
         minorTicks = {},
         alternateBands = {},
         tickAmount,
         labelOffset,
         axisTitleMargin,// = options.title.margin,
         dateTimeLabelFormat,
         categories = options.categories,
         labelFormatter = options.labels.formatter ||  // can be overwritten by dynamic format
            function () {
               var value = this.value,
                  ret;

               if (dateTimeLabelFormat) { // datetime axis
                  ret = dateFormat(dateTimeLabelFormat, value);

               } else if (tickInterval % 1000000 === 0) { // use M abbreviation
                  ret = (value / 1000000) + 'M';

               } else if (tickInterval % 1000 === 0) { // use k abbreviation
                  ret = (value / 1000) + 'k';

               } else if (!categories && value >= 1000) { // add thousands separators
                  ret = numberFormat(value, 0);

               } else { // strings (categories) and small numbers
                  ret = value;
               }
               return ret;
            },

         staggerLines = horiz && options.labels.staggerLines,
         reversed = options.reversed,
         tickmarkOffset = (categories && options.tickmarkPlacement === 'between') ? 0.5 : 0;

      /**
       * The Tick class
       */
      function Tick(pos, minor) {
         var tick = this;
         tick.pos = pos;
         tick.minor = minor;
         tick.isNew = true;

         if (!minor) {
            tick.addLabel();
         }
      }
      Tick.prototype = {
         /**
          * Write the tick label
          */
         addLabel: function () {
            var pos = this.pos,
               labelOptions = options.labels,
               str,
               withLabel = !((pos === min && !pick(options.showFirstLabel, 1)) ||
                  (pos === max && !pick(options.showLastLabel, 0))),
               width = (categories && horiz && categories.length &&
                  !labelOptions.step && !labelOptions.staggerLines &&
                  !labelOptions.rotation &&
                  plotWidth / categories.length) ||
                  (!horiz && plotWidth / 2),
               css,
               value = categories && defined(categories[pos]) ? categories[pos] : pos,
               label = this.label;


            // get the string
            str = labelFormatter.call({
                  isFirst: pos === tickPositions[0],
                  isLast: pos === tickPositions[tickPositions.length - 1],
                  dateTimeLabelFormat: dateTimeLabelFormat,
                  value: isLog ? lin2log(value) : value
               });


            // prepare CSS
            css = width && { width: mathMax(1, mathRound(width - 2 * (labelOptions.padding || 10))) + PX };
            css = extend(css, labelOptions.style);

            // first call
            if (label === UNDEFINED) {
               this.label =
                  defined(str) && withLabel && labelOptions.enabled ?
                     renderer.text(
                           str,
                           0,
                           0,
                           labelOptions.useHTML
                        )
                        .attr({
                           align: labelOptions.align,
                           rotation: labelOptions.rotation
                        })
                        // without position absolute, IE export sometimes is wrong
                        .css(css)
                        .add(axisGroup) :
                     null;

            // update
            } else if (label) {
               label.attr({ text: str })
                  .css(css);
            }
         },
         /**
          * Get the offset height or width of the label
          */
         getLabelSize: function () {
            var label = this.label;
            return label ?
               ((this.labelBBox = label.getBBox()))[horiz ? 'height' : 'width'] :
               0;
            },
         /**
          * Put everything in place
          *
          * @param index {Number}
          * @param old {Boolean} Use old coordinates to prepare an animation into new position
          */
         render: function (index, old) {
            var tick = this,
               major = !tick.minor,
               label = tick.label,
               pos = tick.pos,
               labelOptions = options.labels,
               gridLine = tick.gridLine,
               gridLineWidth = major ? options.gridLineWidth : options.minorGridLineWidth,
               gridLineColor = major ? options.gridLineColor : options.minorGridLineColor,
               dashStyle = major ?
                  options.gridLineDashStyle :
                  options.minorGridLineDashStyle,
               gridLinePath,
               mark = tick.mark,
               markPath,
               tickLength = major ? options.tickLength : options.minorTickLength,
               tickWidth = major ? options.tickWidth : (options.minorTickWidth || 0),
               tickColor = major ? options.tickColor : options.minorTickColor,
               tickPosition = major ? options.tickPosition : options.minorTickPosition,
               step = labelOptions.step,
               cHeight = (old && oldChartHeight) || chartHeight,
               attribs,
               x,
               y;

            // get x and y position for ticks and labels
            x = horiz ?
               translate(pos + tickmarkOffset, null, null, old) + transB :
               plotLeft + offset + (opposite ? ((old && oldChartWidth) || chartWidth) - marginRight - plotLeft : 0);

            y = horiz ?
               cHeight - marginBottom + offset - (opposite ? plotHeight : 0) :
               cHeight - translate(pos + tickmarkOffset, null, null, old) - transB;

            // create the grid line
            if (gridLineWidth) {
               gridLinePath = getPlotLinePath(pos + tickmarkOffset, gridLineWidth, old);

               if (gridLine === UNDEFINED) {
                  attribs = {
                     stroke: gridLineColor,
                     'stroke-width': gridLineWidth
                  };
                  if (dashStyle) {
                     attribs.dashstyle = dashStyle;
                  }
                  if (major) {
                     attribs.zIndex = 1;
                  }
                  tick.gridLine = gridLine =
                     gridLineWidth ?
                        renderer.path(gridLinePath)
                           .attr(attribs).add(gridGroup) :
                        null;
               }

               // If the parameter 'old' is set, the current call will be followed
               // by another call, therefore do not do any animations this time
               if (!old && gridLine && gridLinePath) {
                  gridLine.animate({
                     d: gridLinePath
                  });
               }
            }

            // create the tick mark
            if (tickWidth) {

               // negate the length
               if (tickPosition === 'inside') {
                  tickLength = -tickLength;
               }
               if (opposite) {
                  tickLength = -tickLength;
               }

               markPath = renderer.crispLine([
                  M,
                  x,
                  y,
                  L,
                  x + (horiz ? 0 : -tickLength),
                  y + (horiz ? tickLength : 0)
               ], tickWidth);

               if (mark) { // updating
                  mark.animate({
                     d: markPath
                  });
               } else { // first time
                  tick.mark = renderer.path(
                     markPath
                  ).attr({
                     stroke: tickColor,
                     'stroke-width': tickWidth
                  }).add(axisGroup);
               }
            }

            // the label is created on init - now move it into place
            if (label && !isNaN(x)) {
               x = x + labelOptions.x - (tickmarkOffset && horiz ?
                  tickmarkOffset * transA * (reversed ? -1 : 1) : 0);
               y = y + labelOptions.y - (tickmarkOffset && !horiz ?
                  tickmarkOffset * transA * (reversed ? 1 : -1) : 0);

               // vertically centered
               if (!defined(labelOptions.y)) {
                  y += pInt(label.styles.lineHeight) * 0.9 - label.getBBox().height / 2;
               }


               // correct for staggered labels
               if (staggerLines) {
                  y += (index / (step || 1) % staggerLines) * 16;
               }
               // apply step
               if (step) {
                  // show those indices dividable by step
                  label[index % step ? 'hide' : 'show']();
               }

               label[tick.isNew ? 'attr' : 'animate']({
                  x: x,
                  y: y
               });
            }

            tick.isNew = false;
         },
         /**
          * Destructor for the tick prototype
          */
         destroy: function () {
            destroyObjectProperties(this);
         }
      };

      /**
       * The object wrapper for plot lines and plot bands
       * @param {Object} options
       */
      function PlotLineOrBand(options) {
         var plotLine = this;
         if (options) {
            plotLine.options = options;
            plotLine.id = options.id;
         }

         //plotLine.render()
         return plotLine;
      }

      PlotLineOrBand.prototype = {

      /**
       * Render the plot line or plot band. If it is already existing,
       * move it.
       */
      render: function () {
         var plotLine = this,
            options = plotLine.options,
            optionsLabel = options.label,
            label = plotLine.label,
            width = options.width,
            to = options.to,
            from = options.from,
            value = options.value,
            toPath, // bands only
            dashStyle = options.dashStyle,
            svgElem = plotLine.svgElem,
            path = [],
            addEvent,
            eventType,
            xs,
            ys,
            x,
            y,
            color = options.color,
            zIndex = options.zIndex,
            events = options.events,
            attribs;

         // logarithmic conversion
         if (isLog) {
            from = log2lin(from);
            to = log2lin(to);
            value = log2lin(value);
         }

         // plot line
         if (width) {
            path = getPlotLinePath(value, width);
            attribs = {
               stroke: color,
               'stroke-width': width
            };
            if (dashStyle) {
               attribs.dashstyle = dashStyle;
            }
         } else if (defined(from) && defined(to)) { // plot band
            // keep within plot area
            from = mathMax(from, min);
            to = mathMin(to, max);

            toPath = getPlotLinePath(to);
            path = getPlotLinePath(from);
            if (path && toPath) {
               path.push(
                  toPath[4],
                  toPath[5],
                  toPath[1],
                  toPath[2]
               );
            } else { // outside the axis area
               path = null;
            }
            attribs = {
               fill: color
            };
         } else {
            return;
         }
         // zIndex
         if (defined(zIndex)) {
            attribs.zIndex = zIndex;
         }

         // common for lines and bands
         if (svgElem) {
            if (path) {
               svgElem.animate({
                  d: path
               }, null, svgElem.onGetPath);
            } else {
               svgElem.hide();
               svgElem.onGetPath = function () {
                  svgElem.show();
               };
            }
         } else if (path && path.length) {
            plotLine.svgElem = svgElem = renderer.path(path)
               .attr(attribs).add();

            // events
            if (events) {
               addEvent = function (eventType) {
                  svgElem.on(eventType, function (e) {
                     events[eventType].apply(plotLine, [e]);
                  });
               };
               for (eventType in events) {
                  addEvent(eventType);
               }
            }
         }

         // the plot band/line label
         if (optionsLabel && defined(optionsLabel.text) && path && path.length && plotWidth > 0 && plotHeight > 0) {
            // apply defaults
            optionsLabel = merge({
               align: horiz && toPath && 'center',
               x: horiz ? !toPath && 4 : 10,
               verticalAlign : !horiz && toPath && 'middle',
               y: horiz ? toPath ? 16 : 10 : toPath ? 6 : -4,
               rotation: horiz && !toPath && 90
            }, optionsLabel);

            // add the SVG element
            if (!label) {
               plotLine.label = label = renderer.text(
                     optionsLabel.text,
                     0,
                     0
                  )
                  .attr({
                     align: optionsLabel.textAlign || optionsLabel.align,
                     rotation: optionsLabel.rotation,
                     zIndex: zIndex
                  })
                  .css(optionsLabel.style)
                  .add();
            }

            // get the bounding box and align the label
            xs = [path[1], path[4], pick(path[6], path[1])];
            ys = [path[2], path[5], pick(path[7], path[2])];
            x = mathMin.apply(math, xs);
            y = mathMin.apply(math, ys);

            label.align(optionsLabel, false, {
               x: x,
               y: y,
               width: mathMax.apply(math, xs) - x,
               height: mathMax.apply(math, ys) - y
            });
            label.show();

         } else if (label) { // move out of sight
            label.hide();
         }

         // chainable
         return plotLine;
      },

      /**
       * Remove the plot line or band
       */
      destroy: function () {
         var obj = this;

         destroyObjectProperties(obj);

         // remove it from the lookup
         erase(plotLinesAndBands, obj);
      }
      };

      /**
       * The class for stack items
       */
      function StackItem(options, isNegative, x, stackOption) {
         var stackItem = this;

         // Tells if the stack is negative
         stackItem.isNegative = isNegative;

         // Save the options to be able to style the label
         stackItem.options = options;

         // Save the x value to be able to position the label later
         stackItem.x = x;

         // Save the stack option on the series configuration object
         stackItem.stack = stackOption;

         // The align options and text align varies on whether the stack is negative and
         // if the chart is inverted or not.
         // First test the user supplied value, then use the dynamic.
         stackItem.alignOptions = {
            align: options.align || (inverted ? (isNegative ? 'left' : 'right') : 'center'),
            verticalAlign: options.verticalAlign || (inverted ? 'middle' : (isNegative ? 'bottom' : 'top')),
            y: pick(options.y, inverted ? 4 : (isNegative ? 14 : -6)),
            x: pick(options.x, inverted ? (isNegative ? -6 : 6) : 0)
         };

         stackItem.textAlign = options.textAlign || (inverted ? (isNegative ? 'right' : 'left') : 'center');
      }

      StackItem.prototype = {
         destroy: function () {
            destroyObjectProperties(this);
         },

         /**
          * Sets the total of this stack. Should be called when a serie is hidden or shown
          * since that will affect the total of other stacks.
          */
         setTotal: function (total) {
            this.total = total;
            this.cum = total;
         },

         /**
          * Renders the stack total label and adds it to the stack label group.
          */
         render: function (group) {
            var stackItem = this,                           // aliased this
               str = stackItem.options.formatter.call(stackItem);   // format the text in the label

            // Change the text to reflect the new total and set visibility to hidden in case the serie is hidden
            if (stackItem.label) {
               stackItem.label.attr(" str, visibility: HIDDEN");
            // Create new label
            } else {
               stackItem.label =
                  chart.renderer.text(str, 0, 0)            // dummy positions, actual position updated with setOffset method in columnseries
                     .css(stackItem.options.style)         // apply style
                     .attr({align: stackItem.textAlign,         // fix the text-anchor
                        rotation: stackItem.options.rotation,   // rotation
                        visibility: HIDDEN })               // hidden until setOffset is called
                     .add(group);                     // add to the labels-group
            }
         },

         /**
          * Sets the offset that the stack has from the x value and repositions the label.
          */
         setOffset: function (xOffset, xWidth) {
            var stackItem = this,                              // aliased this
               neg = stackItem.isNegative,                        // special treatment is needed for negative stacks
               y = axis.translate(stackItem.total),               // stack value translated mapped to chart coordinates
               yZero = axis.translate(0),                        // stack origin
               h = mathAbs(y - yZero),                           // stack height
               x = chart.xAxis[0].translate(stackItem.x) + xOffset,   // stack x position
               plotHeight = chart.plotHeight,
               stackBox = {   // this is the box for the complete stack
                     x: inverted ? (neg ? y : y - h) : x,
                     y: inverted ? plotHeight - x - xWidth : (neg ? (plotHeight - y - h) : plotHeight - y),
                     width: inverted ? h : xWidth,
                     height: inverted ? xWidth : h
               };

            if (stackItem.label) {
               stackItem.label
                  .align(stackItem.alignOptions, null, stackBox)   // align the label to the box
                  .attr({visibility: VISIBLE});               // set visibility
            }
         }
      };

      /**
       * Get the minimum and maximum for the series of each axis
       */
      function getSeriesExtremes() {
         var posStack = [],
            negStack = [],
            run;

         // reset dataMin and dataMax in case we're redrawing
         dataMin = dataMax = null;

         // get an overview of what series are associated with this axis
         associatedSeries = [];

         each(series, function (serie) {
            run = false;


            // match this axis against the series' given or implicated axis
            each(['xAxis', 'yAxis'], function (strAxis) {
               if (
                  // the series is a cartesian type, and...
                  serie.isCartesian &&
                  // we're in the right x or y dimension, and...
                  ((strAxis === 'xAxis' && isXAxis) || (strAxis === 'yAxis' && !isXAxis)) && (
                     // the axis number is given in the options and matches this axis index, or
                     (serie.options[strAxis] === options.index) ||
                     // the axis index is not given
                     (serie.options[strAxis] === UNDEFINED && options.index === 0)
                  )
               ) {
                  serie[strAxis] = axis;
                  associatedSeries.push(serie);

                  // the series is visible, run the min/max detection
                  run = true;
               }
            });
            // ignore hidden series if opted
            if (!serie.visible && optionsChart.ignoreHiddenSeries) {
               run = false;
            }

            if (run) {

               var stacking,
                  posPointStack,
                  negPointStack,
                  stackKey,
                  stackOption,
                  negKey;

               if (!isXAxis) {
                  stacking = serie.options.stacking;
                  usePercentage = stacking === 'percent';

                  // create a stack for this particular series type
                  if (stacking) {
                     stackOption = serie.options.stack;
                     stackKey = serie.type + pick(stackOption, '');
                     negKey = '-' + stackKey;
                     serie.stackKey = stackKey; // used in translate

                     posPointStack = posStack[stackKey] || []; // contains the total values for each x
                     posStack[stackKey] = posPointStack;

                     negPointStack = negStack[negKey] || [];
                     negStack[negKey] = negPointStack;
                  }
                  if (usePercentage) {
                     dataMin = 0;
                     dataMax = 99;
                  }
               }
               if (serie.isCartesian) { // line, column etc. need axes, pie doesn't
                  each(serie.data, function (point) {
                     var pointX = point.x,
                        pointY = point.y,
                        isNegative = pointY < 0,
                        pointStack = isNegative ? negPointStack : posPointStack,
                        key = isNegative ? negKey : stackKey,
                        totalPos,
                        pointLow;

                     // initial values
                     if (dataMin === null) {

                        // start out with the first point
                        dataMin = dataMax = point[xOrY];
                     }

                     // x axis
                     if (isXAxis) {
                        if (pointX > dataMax) {
                           dataMax = pointX;
                        } else if (pointX < dataMin) {
                           dataMin = pointX;
                        }
                     } else if (defined(pointY)) { // y axis
                        if (stacking) {
                           pointStack[pointX] =
                              defined(pointStack[pointX]) ?
                              pointStack[pointX] + pointY : pointY;
                        }
                        totalPos = pointStack ? pointStack[pointX] : pointY;
                        pointLow = pick(point.low, totalPos);
                        if (!usePercentage) {
                           if (totalPos > dataMax) {
                              dataMax = totalPos;
                           } else if (pointLow < dataMin) {
                              dataMin = pointLow;
                           }
                        }
                        if (stacking) {
                           // add the series
                           if (!stacks[key]) {
                              stacks[key] = {};
                           }

                           // If the StackItem is there, just update the values,
                           // if not, create one first
                           if (!stacks[key][pointX]) {
                              stacks[key][pointX] = new StackItem(options.stackLabels, isNegative, pointX, stackOption);
                           }
                           stacks[key][pointX].setTotal(totalPos);
                        }
                     }
                  });


                  // For column, areas and bars, set the minimum automatically to zero
                  // and prevent that minPadding is added in setScale
                  if (/(area|column|bar)/.test(serie.type) && !isXAxis) {
                     var threshold = 0; // use series.options.threshold?
                     if (dataMin >= threshold) {
                        dataMin = threshold;
                        ignoreMinPadding = true;
                     } else if (dataMax < threshold) {
                        dataMax = threshold;
                        ignoreMaxPadding = true;
                     }
                  }
               }
            }
         });

      }

      /**
       * Translate from axis value to pixel position on the chart, or back
       *
       */
      translate = function (val, backwards, cvsCoord, old, handleLog) {
         var sign = 1,
            cvsOffset = 0,
            localA = old ? oldTransA : transA,
            localMin = old ? oldMin : min,
            returnValue;

         if (!localA) {
            localA = transA;
         }

         if (cvsCoord) {
            sign *= -1; // canvas coordinates inverts the value
            cvsOffset = axisLength;
         }
         if (reversed) { // reversed axis
            sign *= -1;
            cvsOffset -= sign * axisLength;
         }

         if (backwards) { // reverse translation
            if (reversed) {
               val = axisLength - val;
            }
            returnValue = val / localA + localMin; // from chart pixel to value
            if (isLog && handleLog) {
               returnValue = lin2log(returnValue);
            }

         } else { // normal translation
            if (isLog && handleLog) {
               val = log2lin(val);
            }
            returnValue = sign * (val - localMin) * localA + cvsOffset; // from value to chart pixel
         }

         return returnValue;
      };

      /**
       * Create the path for a plot line that goes from the given value on
       * this axis, across the plot to the opposite side
       * @param {Number} value
       * @param {Number} lineWidth Used for calculation crisp line
       * @param {Number] old Use old coordinates (for resizing and rescaling)
       */
      getPlotLinePath = function (value, lineWidth, old) {
         var x1,
            y1,
            x2,
            y2,
            translatedValue = translate(value, null, null, old),
            cHeight = (old && oldChartHeight) || chartHeight,
            cWidth = (old && oldChartWidth) || chartWidth,
            skip;

         x1 = x2 = mathRound(translatedValue + transB);
         y1 = y2 = mathRound(cHeight - translatedValue - transB);

         if (isNaN(translatedValue)) { // no min or max
            skip = true;

         } else if (horiz) {
            y1 = plotTop;
            y2 = cHeight - marginBottom;
            if (x1 < plotLeft || x1 > plotLeft + plotWidth) {
               skip = true;
            }
         } else {
            x1 = plotLeft;
            x2 = cWidth - marginRight;
            if (y1 < plotTop || y1 > plotTop + plotHeight) {
               skip = true;
            }
         }
         return skip ?
            null :
            renderer.crispLine([M, x1, y1, L, x2, y2], lineWidth || 0);
      };


      /**
       * Take an interval and normalize it to multiples of 1, 2, 2.5 and 5
       * @param {Number} interval
       */
      function normalizeTickInterval(interval, multiples) {
         var normalized, i;

         // round to a tenfold of 1, 2, 2.5 or 5
         magnitude = multiples ? 1 : math.pow(10, mathFloor(math.log(interval) / math.LN10));
         normalized = interval / magnitude;

         // multiples for a linear scale
         if (!multiples) {
            multiples = [1, 2, 2.5, 5, 10];
            //multiples = [1, 2, 2.5, 4, 5, 7.5, 10];

            // the allowDecimals option
            if (options.allowDecimals === false || isLog) {
               if (magnitude === 1) {
                  multiples = [1, 2, 5, 10];
               } else if (magnitude <= 0.1) {
                  multiples = [1 / magnitude];
               }
            }
         }

         // normalize the interval to the nearest multiple
         for (i = 0; i < multiples.length; i++) {
            interval = multiples[i];
            if (normalized <= (multiples[i] + (multiples[i + 1] || multiples[i])) / 2) {
               break;
            }
         }

         // multiply back to the correct magnitude
         interval *= magnitude;

         return interval;
      }

      /**
       * Set the tick positions to a time unit that makes sense, for example
       * on the first of each month or on every Monday.
       */
      function setDateTimeTickPositions() {
         tickPositions = [];
         var i,
            useUTC = defaultOptions.global.useUTC,
            oneSecond = 1000 / timeFactor,
            oneMinute = 60000 / timeFactor,
            oneHour = 3600000 / timeFactor,
            oneDay = 24 * 3600000 / timeFactor,
            oneWeek = 7 * 24 * 3600000 / timeFactor,
            oneMonth = 30 * 24 * 3600000 / timeFactor,
            oneYear = 31556952000 / timeFactor,

            units = [[
               'second',                  // unit name
               oneSecond,                  // fixed incremental unit
               [1, 2, 5, 10, 15, 30]         // allowed multiples
            ], [
               'minute',                  // unit name
               oneMinute,                  // fixed incremental unit
               [1, 2, 5, 10, 15, 30]         // allowed multiples
            ], [
               'hour',                     // unit name
               oneHour,                  // fixed incremental unit
               [1, 2, 3, 4, 6, 8, 12]         // allowed multiples
            ], [
               'day',                     // unit name
               oneDay,                     // fixed incremental unit
               [1, 2]                     // allowed multiples
            ], [
               'week',                     // unit name
               oneWeek,                  // fixed incremental unit
               [1, 2]                     // allowed multiples
            ], [
               'month',
               oneMonth,
               [1, 2, 3, 4, 6]
            ], [
               'year',
               oneYear,
               null
            ]],

            unit = units[6], // default unit is years
            interval = unit[1],
            multiples = unit[2];

         // loop through the units to find the one that best fits the tickInterval
         for (i = 0; i < units.length; i++) {
            unit = units[i];
            interval = unit[1];
            multiples = unit[2];


            if (units[i + 1]) {
               // lessThan is in the middle between the highest multiple and the next unit.
               var lessThan = (interval * multiples[multiples.length - 1] +
                        units[i + 1][1]) / 2;

               // break and keep the current unit
               if (tickInterval <= lessThan) {
                  break;
               }
            }
         }

         // prevent 2.5 years intervals, though 25, 250 etc. are allowed
         if (interval === oneYear && tickInterval < 5 * interval) {
            multiples = [1, 2, 5];
         }

         // get the minimum value by flooring the date
         var multitude = normalizeTickInterval(tickInterval / interval, multiples),
            minYear, // used in months and years as a basis for Date.UTC()
            minDate = new Date(min * timeFactor);

         minDate.setMilliseconds(0);

         if (interval >= oneSecond) { // second
            minDate.setSeconds(interval >= oneMinute ? 0 :
               multitude * mathFloor(minDate.getSeconds() / multitude));
         }

         if (interval >= oneMinute) { // minute
            minDate[setMinutes](interval >= oneHour ? 0 :
               multitude * mathFloor(minDate[getMinutes]() / multitude));
         }

         if (interval >= oneHour) { // hour
            minDate[setHours](interval >= oneDay ? 0 :
               multitude * mathFloor(minDate[getHours]() / multitude));
         }

         if (interval >= oneDay) { // day
            minDate[setDate](interval >= oneMonth ? 1 :
               multitude * mathFloor(minDate[getDate]() / multitude));
         }

         if (interval >= oneMonth) { // month
            minDate[setMonth](interval >= oneYear ? 0 :
               multitude * mathFloor(minDate[getMonth]() / multitude));
            minYear = minDate[getFullYear]();
         }

         if (interval >= oneYear) { // year
            minYear -= minYear % multitude;
            minDate[setFullYear](minYear);
         }

         // week is a special case that runs outside the hierarchy
         if (interval === oneWeek) {
            // get start of current week, independent of multitude
            minDate[setDate](minDate[getDate]() - minDate[getDay]() +
               options.startOfWeek);
         }


         // get tick positions
         i = 1; // prevent crash just in case
         minYear = minDate[getFullYear]();
         var time = minDate.getTime() / timeFactor,
            minMonth = minDate[getMonth](),
            minDateDate = minDate[getDate]();

         // iterate and add tick positions at appropriate values
         while (time < max && i < plotWidth) {
            tickPositions.push(time);

            // if the interval is years, use Date.UTC to increase years
            if (interval === oneYear) {
               time = makeTime(minYear + i * multitude, 0) / timeFactor;

            // if the interval is months, use Date.UTC to increase months
            } else if (interval === oneMonth) {
               time = makeTime(minYear, minMonth + i * multitude) / timeFactor;

            // if we're using global time, the interval is not fixed as it jumps
            // one hour at the DST crossover
            } else if (!useUTC && (interval === oneDay || interval === oneWeek)) {
               time = makeTime(minYear, minMonth, minDateDate +
                  i * multitude * (interval === oneDay ? 1 : 7));

            // else, the interval is fixed and we use simple addition
            } else {
               time += interval * multitude;
            }

            i++;
         }
         // push the last time
         tickPositions.push(time);


         // dynamic label formatter
         dateTimeLabelFormat = options.dateTimeLabelFormats[unit[0]];
      }

      /**
       * Fix JS round off float errors
       * @param {Number} num
       */
      function correctFloat(num) {
         var invMag, ret = num;
         magnitude = pick(magnitude, math.pow(10, mathFloor(math.log(tickInterval) / math.LN10)));

         if (magnitude < 1) {
            invMag = mathRound(1 / magnitude)  * 10;
            ret = mathRound(num * invMag) / invMag;
         }
         return ret;
      }

      /**
       * Set the tick positions of a linear axis to round values like whole tens or every five.
       */
      function setLinearTickPositions() {

         var i,
            roundedMin = correctFloat(mathFloor(min / tickInterval) * tickInterval),
            roundedMax = correctFloat(mathCeil(max / tickInterval) * tickInterval);

         tickPositions = [];

         // populate the intermediate values
         i = correctFloat(roundedMin);
         while (i <= roundedMax) {
            tickPositions.push(i);
            i = correctFloat(i + tickInterval);
         }

      }

      /**
       * Set the tick positions to round values and optionally extend the extremes
       * to the nearest tick
       */
      function setTickPositions() {
         var length,
            catPad,
            linkedParent,
            linkedParentExtremes,
            tickIntervalOption = options.tickInterval,
            tickPixelIntervalOption = options.tickPixelInterval,
            maxZoom = options.maxZoom || (
               isXAxis && !defined(options.min) && !defined(options.max) ?
                  mathMin(chart.smallestInterval * 5, dataMax - dataMin) :
                  null
            ),
            zoomOffset;


         axisLength = horiz ? plotWidth : plotHeight;

         // linked axis gets the extremes from the parent axis
         if (isLinked) {
            linkedParent = chart[isXAxis ? 'xAxis' : 'yAxis'][options.linkedTo];
            linkedParentExtremes = linkedParent.getExtremes();
            min = pick(linkedParentExtremes.min, linkedParentExtremes.dataMin);
            max = pick(linkedParentExtremes.max, linkedParentExtremes.dataMax);
         } else { // initial min and max from the extreme data values
            min = pick(userMin, options.min, dataMin);
            max = pick(userMax, options.max, dataMax);
         }

         if (isLog) {
            min = log2lin(min);
            max = log2lin(max);
         }

         // maxZoom exceeded, just center the selection
         if (max - min < maxZoom) {
            zoomOffset = (maxZoom - max + min) / 2;
            // if min and max options have been set, don't go beyond it
            min = mathMax(min - zoomOffset, pick(options.min, min - zoomOffset), dataMin);
            max = mathMin(min + maxZoom, pick(options.max, min + maxZoom), dataMax);
         }

         // pad the values to get clear of the chart's edges
         if (!categories && !usePercentage && !isLinked && defined(min) && defined(max)) {
            length = (max - min) || 1;
            if (!defined(options.min) && !defined(userMin) && minPadding && (dataMin < 0 || !ignoreMinPadding)) {
               min -= length * minPadding;
            }
            if (!defined(options.max) && !defined(userMax)  && maxPadding && (dataMax > 0 || !ignoreMaxPadding)) {
               max += length * maxPadding;
            }
         }

         // get tickInterval
         if (min === max) {
            tickInterval = 1;
         } else if (isLinked && !tickIntervalOption &&
               tickPixelIntervalOption === linkedParent.options.tickPixelInterval) {
            tickInterval = linkedParent.tickInterval;
         } else {
            tickInterval = pick(
               tickIntervalOption,
               categories ? // for categoried axis, 1 is default, for linear axis use tickPix
                  1 :
                  (max - min) * tickPixelIntervalOption / axisLength
            );
         }

         if (!isDatetimeAxis && !defined(options.tickInterval)) { // linear
            tickInterval = normalizeTickInterval(tickInterval);
         }
         axis.tickInterval = tickInterval; // record for linked axis

         // get minorTickInterval
         minorTickInterval = options.minorTickInterval === 'auto' && tickInterval ?
               tickInterval / 5 : options.minorTickInterval;

         // find the tick positions
         if (isDatetimeAxis) {
            setDateTimeTickPositions();
         } else {
            setLinearTickPositions();
         }

         if (!isLinked) {
            // pad categorised axis to nearest half unit
            if (categories || (isXAxis && chart.hasColumn)) {
               catPad = (categories ? 1 : tickInterval) * 0.5;
               if (categories || !defined(pick(options.min, userMin))) {
                  min -= catPad;
               }
               if (categories || !defined(pick(options.max, userMax))) {
                  max += catPad;
               }
            }

            // reset min/max or remove extremes based on start/end on tick
            var roundedMin = tickPositions[0],
               roundedMax = tickPositions[tickPositions.length - 1];

            if (options.startOnTick) {
               min = roundedMin;
            } else if (min > roundedMin) {
               tickPositions.shift();
            }

            if (options.endOnTick) {
               max = roundedMax;
            } else if (max < roundedMax) {
               tickPositions.pop();
            }

            // record the greatest number of ticks for multi axis
            if (!maxTicks) { // first call, or maxTicks have been reset after a zoom operation
               maxTicks = {
                  x: 0,
                  y: 0
               };
            }

            if (!isDatetimeAxis && tickPositions.length > maxTicks[xOrY]) {
               maxTicks[xOrY] = tickPositions.length;
            }
         }


      }

      /**
       * When using multiple axes, adjust the number of ticks to match the highest
       * number of ticks in that group
       */
      function adjustTickAmount() {

         if (maxTicks && !isDatetimeAxis && !categories && !isLinked) { // only apply to linear scale
            var oldTickAmount = tickAmount,
               calculatedTickAmount = tickPositions.length;

            // set the axis-level tickAmount to use below
            tickAmount = maxTicks[xOrY];

            if (calculatedTickAmount < tickAmount) {
               while (tickPositions.length < tickAmount) {
                  tickPositions.push(correctFloat(
                     tickPositions[tickPositions.length - 1] + tickInterval
                  ));
               }
               transA *= (calculatedTickAmount - 1) / (tickAmount - 1);
               max = tickPositions[tickPositions.length - 1];

            }
            if (defined(oldTickAmount) && tickAmount !== oldTickAmount) {
               axis.isDirty = true;
            }
         }

      }

      /**
       * Set the scale based on data min and max, user set min and max or options
       *
       */
      function setScale() {
         var type,
            i;

         oldMin = min;
         oldMax = max;

         // get data extremes if needed
         getSeriesExtremes();

         // get fixed positions based on tickInterval
         setTickPositions();

         // the translation factor used in translate function
         oldTransA = transA;
         transA = axisLength / ((max - min) || 1);

         // reset stacks
         if (!isXAxis) {
            for (type in stacks) {
               for (i in stacks[type]) {
                  stacks[type][i].cum = stacks[type][i].total;
               }
            }
         }

         // mark as dirty if it is not already set to dirty and extremes have changed
         if (!axis.isDirty) {
            axis.isDirty = (min !== oldMin || max !== oldMax);
         }

      }

      /**
       * Set the extremes and optionally redraw
       * @param {Number} newMin
       * @param {Number} newMax
       * @param {Boolean} redraw
       * @param {Boolean|Object} animation Whether to apply animation, and optionally animation
       *    configuration
       *
       */
      function setExtremes(newMin, newMax, redraw, animation) {

         redraw = pick(redraw, true); // defaults to true

         fireEvent(axis, 'setExtremes', { // fire an event to enable syncing of multiple charts
            min: newMin,
            max: newMax
         }, function () { // the default event handler

            userMin = newMin;
            userMax = newMax;


            // redraw
            if (redraw) {
               chart.redraw(animation);
            }
         });

      }

      /**
       * Get the actual axis extremes
       */
      function getExtremes() {
         return {
            min: min,
            max: max,
            dataMin: dataMin,
            dataMax: dataMax,
            userMin: userMin,
            userMax: userMax
         };
      }

      /**
       * Get the zero plane either based on zero or on the min or max value.
       * Used in bar and area plots
       */
      function getThreshold(threshold) {
         if (min > threshold) {
            threshold = min;
         } else if (max < threshold) {
            threshold = max;
         }

         return translate(threshold, 0, 1);
      }

      /**
       * Add a plot band or plot line after render time
       *
       * @param options {Object} The plotBand or plotLine configuration object
       */
      function addPlotBandOrLine(options) {
         var obj = new PlotLineOrBand(options).render();
         plotLinesAndBands.push(obj);
         return obj;
      }

      /**
       * Render the tick labels to a preliminary position to get their sizes
       */
      function getOffset() {

         var hasData = associatedSeries.length && defined(min) && defined(max),
            titleOffset = 0,
            titleMargin = 0,
            axisTitleOptions = options.title,
            labelOptions = options.labels,
            directionFactor = [-1, 1, 1, -1][side],
            n;

         if (!axisGroup) {
            axisGroup = renderer.g('axis')
               .attr({ zIndex: 7 })
               .add();
            gridGroup = renderer.g('grid')
               .attr({ zIndex: 1 })
               .add();
         }

         labelOffset = 0; // reset

         if (hasData || isLinked) {
            each(tickPositions, function (pos) {
               if (!ticks[pos]) {
                  ticks[pos] = new Tick(pos);
               } else {
                  ticks[pos].addLabel(); // update labels depending on tick interval
               }

               // left side must be align: right and right side must have align: left for labels
               if (side === 0 || side === 2 || { 1: 'left', 3: 'right' }[side] === labelOptions.align) {

                  // get the highest offset
                  labelOffset = mathMax(
                     ticks[pos].getLabelSize(),
                     labelOffset
                  );
               }

            });

            if (staggerLines) {
               labelOffset += (staggerLines - 1) * 16;
            }

         } else { // doesn't have data
            for (n in ticks) {
               ticks[n].destroy();
               delete ticks[n];
            }
         }

         if (axisTitleOptions && axisTitleOptions.text) {
            if (!axisTitle) {
               axisTitle = axis.axisTitle = renderer.text(
                  axisTitleOptions.text,
                  0,
                  0,
                  axisTitleOptions.useHTML
               )
               .attr({
                  zIndex: 7,
                  rotation: axisTitleOptions.rotation || 0,
                  align:
                     axisTitleOptions.textAlign ||
                     { low: 'left', middle: 'center', high: 'right' }[axisTitleOptions.align]
               })
               .css(axisTitleOptions.style)
               .add();
               axisTitle.isNew = true;
            }

            titleOffset = axisTitle.getBBox()[horiz ? 'height' : 'width'];
            titleMargin = pick(axisTitleOptions.margin, horiz ? 5 : 10);

         }

         // handle automatic or user set offset
         offset = directionFactor * (options.offset || axisOffset[side]);

         axisTitleMargin =
            labelOffset +
            (side !== 2 && labelOffset && directionFactor * options.labels[horiz ? 'y' : 'x']) +
            titleMargin;

         axisOffset[side] = mathMax(
            axisOffset[side],
            axisTitleMargin + titleOffset + directionFactor * offset
         );

      }

      /**
       * Render the axis
       */
      function render() {
         var axisTitleOptions = options.title,
            stackLabelOptions = options.stackLabels,
            alternateGridColor = options.alternateGridColor,
            lineWidth = options.lineWidth,
            lineLeft,
            lineTop,
            linePath,
            hasRendered = chart.hasRendered,
            slideInTicks = hasRendered && defined(oldMin) && !isNaN(oldMin),
            hasData = associatedSeries.length && defined(min) && defined(max);

         // update metrics
         axisLength = horiz ? plotWidth : plotHeight;
         transA = axisLength / ((max - min) || 1);
         transB = horiz ? plotLeft : marginBottom; // translation addend

         // If the series has data draw the ticks. Else only the line and title
         if (hasData || isLinked) {

            // minor ticks
            if (minorTickInterval && !categories) {
               var pos = min + (tickPositions[0] - min) % minorTickInterval;
               for (; pos <= max; pos += minorTickInterval) {
                  if (!minorTicks[pos]) {
                     minorTicks[pos] = new Tick(pos, true);
                  }

                  // render new ticks in old position
                  if (slideInTicks && minorTicks[pos].isNew) {
                     minorTicks[pos].render(null, true);
                  }


                  minorTicks[pos].isActive = true;
                  minorTicks[pos].render();
               }
            }

            // major ticks
            each(tickPositions, function (pos, i) {
               // linked axes need an extra check to find out if
               if (!isLinked || (pos >= min && pos <= max)) {

                  // render new ticks in old position
                  if (slideInTicks && ticks[pos].isNew) {
                     ticks[pos].render(i, true);
                  }

                  ticks[pos].isActive = true;
                  ticks[pos].render(i);
               }
            });

            // alternate grid color
            if (alternateGridColor) {
               each(tickPositions, function (pos, i) {
                  if (i % 2 === 0 && pos < max) {
                     /*plotLinesAndBands.push(new PlotLineOrBand({
                        from: pos,
                        to: tickPositions[i + 1] !== UNDEFINED ? tickPositions[i + 1] : max,
                        color: alternateGridColor
                     }));*/

                     if (!alternateBands[pos]) {
                        alternateBands[pos] = new PlotLineOrBand();
                     }
                     alternateBands[pos].options = {
                        from: pos,
                        to: tickPositions[i + 1] !== UNDEFINED ? tickPositions[i + 1] : max,
                        color: alternateGridColor
                     };
                     alternateBands[pos].render();
                     alternateBands[pos].isActive = true;
                  }
               });
            }

            // custom plot bands (behind grid lines)
            /*if (!hasRendered) { // only first time
               each(options.plotBands || [], function(plotBandOptions) {
                  plotLinesAndBands.push(new PlotLineOrBand(
                     extend({ zIndex: 1 }, plotBandOptions)
                  ).render());
               });
            }*/




            // custom plot lines and bands
            if (!hasRendered) { // only first time
               each((options.plotLines || []).concat(options.plotBands || []), function (plotLineOptions) {
                  plotLinesAndBands.push(new PlotLineOrBand(plotLineOptions).render());
               });
            }



         } // end if hasData

         // remove inactive ticks
         each([ticks, minorTicks, alternateBands], function (coll) {
            var pos;
            for (pos in coll) {
               if (!coll[pos].isActive) {
                  coll[pos].destroy();
                  delete coll[pos];
               } else {
                  coll[pos].isActive = false; // reset
               }
            }
         });




         // Static items. As the axis group is cleared on subsequent calls
         // to render, these items are added outside the group.
         // axis line
         if (lineWidth) {
            lineLeft = plotLeft + (opposite ? plotWidth : 0) + offset;
            lineTop = chartHeight - marginBottom - (opposite ? plotHeight : 0) + offset;

            linePath = renderer.crispLine([
                  M,
                  horiz ?
                     plotLeft :
                     lineLeft,
                  horiz ?
                     lineTop :
                     plotTop,
                  L,
                  horiz ?
                     chartWidth - marginRight :
                     lineLeft,
                  horiz ?
                     lineTop :
                     chartHeight - marginBottom
               ], lineWidth);
            if (!axisLine) {
               axisLine = renderer.path(linePath)
                  .attr({
                     stroke: options.lineColor,
                     'stroke-width': lineWidth,
                     zIndex: 7
                  })
                  .add();
            } else {
               axisLine.animate({ d: linePath });
            }

         }

         if (axisTitle) {
            // compute anchor points for each of the title align options
            var margin = horiz ? plotLeft : plotTop,
               fontSize = pInt(axisTitleOptions.style.fontSize || 12),
            // the position in the length direction of the axis
            alongAxis = {
               low: margin + (horiz ? 0 : axisLength),
               middle: margin + axisLength / 2,
               high: margin + (horiz ? axisLength : 0)
            }[axisTitleOptions.align],

            // the position in the perpendicular direction of the axis
            offAxis = (horiz ? plotTop + plotHeight : plotLeft) +
               (horiz ? 1 : -1) * // horizontal axis reverses the margin
               (opposite ? -1 : 1) * // so does opposite axes
               axisTitleMargin +
               //(isIE ? fontSize / 3 : 0)+ // preliminary fix for vml's centerline
               (side === 2 ? fontSize : 0);

            axisTitle[axisTitle.isNew ? 'attr' : 'animate']({
               x: horiz ?
                  alongAxis :
                  offAxis + (opposite ? plotWidth : 0) + offset +
                     (axisTitleOptions.x || 0), // x
               y: horiz ?
                  offAxis - (opposite ? plotHeight : 0) + offset :
                  alongAxis + (axisTitleOptions.y || 0) // y
            });
            axisTitle.isNew = false;
         }

         // Stacked totals:
         if (stackLabelOptions && stackLabelOptions.enabled) {
            var stackKey, oneStack, stackCategory,
               stackTotalGroup = axis.stackTotalGroup;

            // Create a separate group for the stack total labels
            if (!stackTotalGroup) {
               axis.stackTotalGroup = stackTotalGroup =
                  renderer.g('stack-labels')
                     .attr({
                        visibility: VISIBLE,
                        zIndex: 6
                     })
                     .translate(plotLeft, plotTop)
                     .add();
            }

            // Render each stack total
            for (stackKey in stacks) {
               oneStack = stacks[stackKey];
               for (stackCategory in oneStack) {
                  oneStack[stackCategory].render(stackTotalGroup);
               }
            }
         }
         // End stacked totals

         axis.isDirty = false;
      }

      /**
       * Remove a plot band or plot line from the chart by id
       * @param {Object} id
       */
      function removePlotBandOrLine(id) {
         var i = plotLinesAndBands.length;
         while (i--) {
            if (plotLinesAndBands[i].id === id) {
               plotLinesAndBands[i].destroy();
            }
         }
      }

      /**
       * Redraw the axis to reflect changes in the data or axis extremes
       */
      function redraw() {

         // hide tooltip and hover states
         if (tracker.resetTracker) {
            tracker.resetTracker();
         }

         // render the axis
         render();

         // move plot lines and bands
         each(plotLinesAndBands, function (plotLine) {
            plotLine.render();
         });

         // mark associated series as dirty and ready for redraw
         each(associatedSeries, function (series) {
            series.isDirty = true;
         });

      }

      /**
       * Set new axis categories and optionally redraw
       * @param {Array} newCategories
       * @param {Boolean} doRedraw
       */
      function setCategories(newCategories, doRedraw) {
            // set the categories
            axis.categories = userOptions.categories = categories = newCategories;

            // force reindexing tooltips
            each(associatedSeries, function (series) {
               series.translate();
               series.setTooltipPoints(true);
            });


            // optionally redraw
            axis.isDirty = true;

            if (pick(doRedraw, true)) {
               chart.redraw();
            }
      }

      /**
       * Destroys an Axis instance.
       */
      function destroy() {
         var stackKey;

         // Remove the events
         removeEvent(axis);

         // Destroy each stack total
         for (stackKey in stacks) {
            destroyObjectProperties(stacks[stackKey]);

            stacks[stackKey] = null;
         }

         // Destroy stack total group
         if (axis.stackTotalGroup) {
            axis.stackTotalGroup = axis.stackTotalGroup.destroy();
         }

         // Destroy collections
         each([ticks, minorTicks, alternateBands, plotLinesAndBands], function (coll) {
            destroyObjectProperties(coll);
         });

         // Destroy local variables
         each([axisLine, axisGroup, gridGroup, axisTitle], function (obj) {
            if (obj) {
               obj.destroy();
            }
         });
         axisLine = axisGroup = gridGroup = axisTitle = null;
      }


      // Run Axis

      // inverted charts have reversed xAxes as default
      if (inverted && isXAxis && reversed === UNDEFINED) {
         reversed = true;
      }


      // expose some variables
      extend(axis, {
         addPlotBand: addPlotBandOrLine,
         addPlotLine: addPlotBandOrLine,
         adjustTickAmount: adjustTickAmount,
         categories: categories,
         getExtremes: getExtremes,
         getPlotLinePath: getPlotLinePath,
         getThreshold: getThreshold,
         isXAxis: isXAxis,
         options: options,
         plotLinesAndBands: plotLinesAndBands,
         getOffset: getOffset,
         render: render,
         setCategories: setCategories,
         setExtremes: setExtremes,
         setScale: setScale,
         setTickPositions: setTickPositions,
         translate: translate,
         redraw: redraw,
         removePlotBand: removePlotBandOrLine,
         removePlotLine: removePlotBandOrLine,
         reversed: reversed,
         stacks: stacks,
         destroy: destroy
      });

      // register event listeners
      for (eventType in events) {
         addEvent(axis, eventType, events[eventType]);
      }

      // set min and max
      setScale();

   } // end Axis


   /**
    * The toolbar object
    */
   function Toolbar() {
      var buttons = {};

      /*jslint unparam: true*//* allow the unused param title until Toolbar rewrite*/
      function add(id, text, title, fn) {
         if (!buttons[id]) {
            var button = renderer.text(
               text,
               0,
               0
            )
            .css(options.toolbar.itemStyle)
            .align({
               align: 'right',
               x: -marginRight - 20,
               y: plotTop + 30
            })
            .on('click', fn)
            /*.on('touchstart', function(e) {
               e.stopPropagation(); // don't fire the container event
               fn();
            })*/
            .attr({
               align: 'right',
               zIndex: 20
            })
            .add();
            buttons[id] = button;
         }
      }
      /*jslint unparam: false*/

      function remove(id) {
         discardElement(buttons[id].element);
         buttons[id] = null;
      }

      // public
      return {
         add: add,
         remove: remove
      };
   }

   /**
    * The tooltip object
    * @param {Object} options Tooltip options
    */
   function Tooltip(options) {
      var currentSeries,
         borderWidth = options.borderWidth,
         crosshairsOptions = options.crosshairs,
         crosshairs = [],
         style = options.style,
         shared = options.shared,
         padding = pInt(style.padding),
         boxOffLeft = borderWidth + padding, // off left/top position as IE can't
            //properly handle negative positioned shapes
         tooltipIsHidden = true,
         boxWidth,
         boxHeight,
         currentX = 0,
         currentY = 0;

      // remove padding CSS and apply padding on box instead
      style.padding = 0;

      // create the elements
      var group = renderer.g('tooltip')
         .attr({   zIndex: 8 })
         .add(),

         box = renderer.rect(boxOffLeft, boxOffLeft, 0, 0, options.borderRadius, borderWidth)
            .attr({
               fill: options.backgroundColor,
               'stroke-width': borderWidth
            })
            .add(group)
            .shadow(options.shadow),
         label = renderer.text('', padding + boxOffLeft, pInt(style.fontSize) + padding + boxOffLeft, options.useHTML)
            .attr({ zIndex: 1 })
            .css(style)
            .add(group);

      group.hide();

      /**
       * Destroy the tooltip and its elements.
       */
      function destroy() {
         each(crosshairs, function (crosshair) {
            if (crosshair) {
               crosshair.destroy();
            }
         });

         // Destroy and clear local variables
         each([box, label, group], function (obj) {
            if (obj) {
               obj.destroy();
            }
         });
         box = label = group = null;
      }

      /**
       * In case no user defined formatter is given, this will be used
       */
      function defaultFormatter() {
         var pThis = this,
            items = pThis.points || splat(pThis),
            xAxis = items[0].series.xAxis,
            x = pThis.x,
            isDateTime = xAxis && xAxis.options.type === 'datetime',
            useHeader = isString(x) || isDateTime,
            s;

         // build the header
         s = useHeader ?
            ['<span style="font-size: 10px">' +
            (isDateTime ? dateFormat('%A, %b %e, %Y', x) :  x) +
            '</span>'] : [];

         // build the values
         each(items, function (item) {
            s.push(item.point.tooltipFormatter(useHeader));
         });
         return s.join('<br/>');
      }

      /**
       * Provide a soft movement for the tooltip
       *
       * @param {Number} finalX
       * @param {Number} finalY
       */
      function move(finalX, finalY) {

         currentX = tooltipIsHidden ? finalX : (2 * currentX + finalX) / 3;
         currentY = tooltipIsHidden ? finalY : (currentY + finalY) / 2;

         group.translate(currentX, currentY);


         // run on next tick of the mouse tracker
         if (mathAbs(finalX - currentX) > 1 || mathAbs(finalY - currentY) > 1) {
            tooltipTick = function () {
               move(finalX, finalY);
            };
         } else {
            tooltipTick = null;
         }
      }

      /**
       * Hide the tooltip
       */
      function hide() {
         if (!tooltipIsHidden) {
            var hoverPoints = chart.hoverPoints;

            group.hide();

            each(crosshairs, function (crosshair) {
               if (crosshair) {
                  crosshair.hide();
               }
            });

            // hide previous hoverPoints and set new
            if (hoverPoints) {
               each(hoverPoints, function (point) {
                  point.setState();
               });
            }
            chart.hoverPoints = null;


            tooltipIsHidden = true;
         }

      }

      /**
       * Refresh the tooltip's text and position.
       * @param {Object} point
       *
       */
      function refresh(point) {
         var x,
            y,
            show,
            bBox,
            plotX,
            plotY = 0,
            textConfig = {},
            text,
            pointConfig = [],
            tooltipPos = point.tooltipPos,
            formatter = options.formatter || defaultFormatter,
            hoverPoints = chart.hoverPoints,
            placedTooltipPoint;

         // shared tooltip, array is sent over
         if (shared) {

            // hide previous hoverPoints and set new
            if (hoverPoints) {
               each(hoverPoints, function (point) {
                  point.setState();
               });
            }
            chart.hoverPoints = point;

            each(point, function (item) {
               /*var series = item.series,
                  hoverPoint = series.hoverPoint;
               if (hoverPoint) {
                  hoverPoint.setState();
               }
               series.hoverPoint = item;*/
               item.setState(HOVER_STATE);
               plotY += item.plotY; // for average

               pointConfig.push(item.getLabelConfig());
            });

            plotX = point[0].plotX;
            plotY = mathRound(plotY) / point.length; // mathRound because Opera 10 has problems here

            textConfig = {
               x: point[0].category
            };
            textConfig.points = pointConfig;
            point = point[0];

         // single point tooltip
         } else {
            textConfig = point.getLabelConfig();
         }
         text = formatter.call(textConfig);

         // register the current series
         currentSeries = point.series;

         // get the reference point coordinates (pie charts use tooltipPos)
         plotX = shared ? plotX : point.plotX;
         plotY = shared ? plotY : point.plotY;
         x = mathRound(tooltipPos ? tooltipPos[0] : (inverted ? plotWidth - plotY : plotX));
         y = mathRound(tooltipPos ? tooltipPos[1] : (inverted ? plotHeight - plotX : plotY));


         // hide tooltip if the point falls outside the plot
         show = shared || !point.series.isCartesian || isInsidePlot(x, y);

         // update the inner HTML
         if (text === false || !show) {
            hide();
         } else {

            // show it
            if (tooltipIsHidden) {
               group.show();
               tooltipIsHidden = false;
            }

            // update text
            label.attr({
               text: text
            });

            // get the bounding box
            bBox = label.getBBox();
            boxWidth = bBox.width + 2 * padding;
            boxHeight = bBox.height + 2 * padding;

            // set the size of the box
            box.attr({
               width: boxWidth,
               height: boxHeight,
               stroke: options.borderColor || point.color || currentSeries.color || '#606060'
            });

            placedTooltipPoint = placeBox(boxWidth, boxHeight, plotLeft, plotTop, plotWidth, plotHeight, {x: x, y: y});

            // do the move
            move(mathRound(placedTooltipPoint.x - boxOffLeft), mathRound(placedTooltipPoint.y - boxOffLeft));
         }


         // crosshairs
         if (crosshairsOptions) {
            crosshairsOptions = splat(crosshairsOptions); // [x, y]

            var path,
               i = crosshairsOptions.length,
               attribs,
               axis;

            while (i--) {
               axis = point.series[i ? 'yAxis' : 'xAxis'];
               if (crosshairsOptions[i] && axis) {
                  path = axis
                     .getPlotLinePath(point[i ? 'y' : 'x'], 1);
                  if (crosshairs[i]) {
                     crosshairs[i].attr({ d: path, visibility: VISIBLE });

                  } else {
                     attribs = {
                        'stroke-width': crosshairsOptions[i].width || 1,
                        stroke: crosshairsOptions[i].color || '#C0C0C0',
                        zIndex: 2
                     };
                     if (crosshairsOptions[i].dashStyle) {
                        attribs.dashstyle = crosshairsOptions[i].dashStyle;
                     }
                     crosshairs[i] = renderer.path(path)
                        .attr(attribs)
                        .add();
                  }
               }
            }
         }
      }



      // public members
      return {
         shared: shared,
         refresh: refresh,
         hide: hide,
         destroy: destroy
      };
   }

   /**
    * The mouse tracker object
    * @param {Object} options
    */
   function MouseTracker(options) {


      var mouseDownX,
         mouseDownY,
         hasDragged,
         selectionMarker,
         zoomType = optionsChart.zoomType,
         zoomX = /x/.test(zoomType),
         zoomY = /y/.test(zoomType),
         zoomHor = (zoomX && !inverted) || (zoomY && inverted),
         zoomVert = (zoomY && !inverted) || (zoomX && inverted);

      /**
       * Add crossbrowser support for chartX and chartY
       * @param {Object} e The event object in standard browsers
       */
      function normalizeMouseEvent(e) {
         var ePos,
            pageZoomFix = isWebKit &&
               doc.width / doc.body.scrollWidth -
               1, // #224, #348
            chartPosLeft,
            chartPosTop,
            chartX,
            chartY;

         // common IE normalizing
         e = e || win.event;
         if (!e.target) {
            e.target = e.srcElement;
         }

         // iOS
         ePos = e.touches ? e.touches.item(0) : e;

         // in certain cases, get mouse position
         if (e.type !== 'mousemove' || win.opera || pageZoomFix) { // only Opera needs position on mouse move, see below
            chartPosition = getPosition(container);
            chartPosLeft = chartPosition.left;
            chartPosTop = chartPosition.top;
         }

         // chartX and chartY
         if (isIE) { // IE including IE9 that has chartX but in a different meaning
            chartX = e.x;
            chartY = e.y;
         } else {
            if (ePos.layerX === UNDEFINED) { // Opera and iOS
               chartX = ePos.pageX - chartPosLeft;
               chartY = ePos.pageY - chartPosTop;
            } else {
               chartX = e.layerX;
               chartY = e.layerY;
            }
         }

         // correct for page zoom bug in WebKit
         if (pageZoomFix) {
            chartX += mathRound((pageZoomFix + 1) * chartPosLeft - chartPosLeft);
            chartY += mathRound((pageZoomFix + 1) * chartPosTop - chartPosTop);
         }

         return extend(e, {
            chartX: chartX,
            chartY: chartY
         });
      }

      /**
       * Get the click position in terms of axis values.
       *
       * @param {Object} e A mouse event
       */
      function getMouseCoordinates(e) {
         var coordinates = {
            xAxis: [],
            yAxis: []
         };
         each(axes, function (axis) {
            var translate = axis.translate,
               isXAxis = axis.isXAxis,
               isHorizontal = inverted ? !isXAxis : isXAxis;

            coordinates[isXAxis ? 'xAxis' : 'yAxis'].push({
               axis: axis,
               value: translate(
                  isHorizontal ?
                     e.chartX - plotLeft  :
                     plotHeight - e.chartY + plotTop,
                  true
               )
            });
         });
         return coordinates;
      }

      /**
       * With line type charts with a single tracker, get the point closest to the mouse
       */
      function onmousemove(e) {
         var point,
            points,
            hoverPoint = chart.hoverPoint,
            hoverSeries = chart.hoverSeries,
            i,
            j,
            distance = chartWidth,
            index = inverted ? e.chartY : e.chartX - plotLeft; // wtf?

         // shared tooltip
         if (tooltip && options.shared) {
            points = [];

            // loop over all series and find the ones with points closest to the mouse
            i = series.length;
            for (j = 0; j < i; j++) {
               if (series[j].visible && series[j].tooltipPoints.length) {
                  point = series[j].tooltipPoints[index];
                  point._dist = mathAbs(index - point.plotX);
                  distance = mathMin(distance, point._dist);
                  points.push(point);
               }
            }
            // remove furthest points
            i = points.length;
            while (i--) {
               if (points[i]._dist > distance) {
                  points.splice(i, 1);
               }
            }
            // refresh the tooltip if necessary
            if (points.length && (points[0].plotX !== hoverX)) {
               tooltip.refresh(points);
               hoverX = points[0].plotX;
            }
         }

         // separate tooltip and general mouse events
         if (hoverSeries && hoverSeries.tracker) { // only use for line-type series with common tracker

            // get the point
            point = hoverSeries.tooltipPoints[index];

            // a new point is hovered, refresh the tooltip
            if (point && point !== hoverPoint) {

               // trigger the events
               point.onMouseOver();

            }
         }
      }



      /**
       * Reset the tracking by hiding the tooltip, the hover series state and the hover point
       */
      function resetTracker() {
         var hoverSeries = chart.hoverSeries,
            hoverPoint = chart.hoverPoint;

         if (hoverPoint) {
            hoverPoint.onMouseOut();
         }

         if (hoverSeries) {
            hoverSeries.onMouseOut();
         }

         if (tooltip) {
            tooltip.hide();
         }

         hoverX = null;
      }

      /**
       * Mouse up or outside the plot area
       */
      function drop() {
         if (selectionMarker) {
            var selectionData = {
                  xAxis: [],
                  yAxis: []
               },
               selectionBox = selectionMarker.getBBox(),
               selectionLeft = selectionBox.x - plotLeft,
               selectionTop = selectionBox.y - plotTop;


            // a selection has been made
            if (hasDragged) {

               // record each axis' min and max
               each(axes, function (axis) {
                  var translate = axis.translate,
                     isXAxis = axis.isXAxis,
                     isHorizontal = inverted ? !isXAxis : isXAxis,
                     selectionMin = translate(
                        isHorizontal ?
                           selectionLeft :
                           plotHeight - selectionTop - selectionBox.height,
                        true,
                        0,
                        0,
                        1
                     ),
                     selectionMax = translate(
                        isHorizontal ?
                           selectionLeft + selectionBox.width :
                           plotHeight - selectionTop,
                        true,
                        0,
                        0,
                        1
                     );

                     selectionData[isXAxis ? 'xAxis' : 'yAxis'].push({
                        axis: axis,
                        min: mathMin(selectionMin, selectionMax), // for reversed axes,
                        max: mathMax(selectionMin, selectionMax)
                     });

               });
               fireEvent(chart, 'selection', selectionData, zoom);

            }
            selectionMarker = selectionMarker.destroy();
         }

         chart.mouseIsDown = mouseIsDown = hasDragged = false;
         removeEvent(doc, hasTouch ? 'touchend' : 'mouseup', drop);

      }

      /**
       * Special handler for mouse move that will hide the tooltip when the mouse leaves the plotarea.
       */
      function hideTooltipOnMouseMove(e) {
         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
            pageY = defined(e.pageX) ? e.pageY : e.page.y; // Ref: http://mootools.net/docs/core/Types/DOMEvent

         if (chartPosition &&
               !isInsidePlot(pageX - chartPosition.left - plotLeft,
                  pageY - chartPosition.top - plotTop)) {
            resetTracker();
         }
      }

      /**
       * Set the JS events on the container element
       */
      function setDOMEvents() {
         var lastWasOutsidePlot = true;
         /*
          * Record the starting position of a dragoperation
          */
         container.onmousedown = function (e) {
            e = normalizeMouseEvent(e);

            // issue #295, dragging not always working in Firefox
            if (!hasTouch && e.preventDefault) {
               e.preventDefault();
            }

            // record the start position
            chart.mouseIsDown = mouseIsDown = true;
            mouseDownX = e.chartX;
            mouseDownY = e.chartY;

            addEvent(doc, hasTouch ? 'touchend' : 'mouseup', drop);
         };

         // The mousemove, touchmove and touchstart event handler
         var mouseMove = function (e) {

            // let the system handle multitouch operations like two finger scroll
            // and pinching
            if (e && e.touches && e.touches.length > 1) {
               return;
            }

            // normalize
            e = normalizeMouseEvent(e);
            if (!hasTouch) { // not for touch devices
               e.returnValue = false;
            }

            var chartX = e.chartX,
               chartY = e.chartY,
               isOutsidePlot = !isInsidePlot(chartX - plotLeft, chartY - plotTop);

            // cache chart position for issue #149 fix
            if (!chartPosition) {
               chartPosition = getPosition(container);
            }

            // on touch devices, only trigger click if a handler is defined
            if (hasTouch && e.type === 'touchstart') {
               if (attr(e.target, 'isTracker')) {
                  if (!chart.runTrackerClick) {
                     e.preventDefault();
                  }
               } else if (!runChartClick && !isOutsidePlot) {
                  e.preventDefault();
               }
            }

            // cancel on mouse outside
            if (isOutsidePlot) {

               /*if (!lastWasOutsidePlot) {
                  // reset the tracker
                  resetTracker();
               }*/

               // drop the selection if any and reset mouseIsDown and hasDragged
               //drop();
               if (chartX < plotLeft) {
                  chartX = plotLeft;
               } else if (chartX > plotLeft + plotWidth) {
                  chartX = plotLeft + plotWidth;
               }

               if (chartY < plotTop) {
                  chartY = plotTop;
               } else if (chartY > plotTop + plotHeight) {
                  chartY = plotTop + plotHeight;
               }

            }

            if (mouseIsDown && e.type !== 'touchstart') { // make selection

               // determine if the mouse has moved more than 10px
               hasDragged = Math.sqrt(
                  Math.pow(mouseDownX - chartX, 2) +
                  Math.pow(mouseDownY - chartY, 2)
               );
               if (hasDragged > 10) {

                  // make a selection
                  if (hasCartesianSeries && (zoomX || zoomY) &&
                        isInsidePlot(mouseDownX - plotLeft, mouseDownY - plotTop)) {
                     if (!selectionMarker) {
                        selectionMarker = renderer.rect(
                           plotLeft,
                           plotTop,
                           zoomHor ? 1 : plotWidth,
                           zoomVert ? 1 : plotHeight,
                           0
                        )
                        .attr({
                           fill: optionsChart.selectionMarkerFill || 'rgba(69,114,167,0.25)',
                           zIndex: 7
                        })
                        .add();
                     }
                  }

                  // adjust the width of the selection marker
                  if (selectionMarker && zoomHor) {
                     var xSize = chartX - mouseDownX;
                     selectionMarker.attr({
                        width: mathAbs(xSize),
                        x: (xSize > 0 ? 0 : xSize) + mouseDownX
                     });
                  }
                  // adjust the height of the selection marker
                  if (selectionMarker && zoomVert) {
                     var ySize = chartY - mouseDownY;
                     selectionMarker.attr({
                        height: mathAbs(ySize),
                        y: (ySize > 0 ? 0 : ySize) + mouseDownY
                     });
                  }
               }

            } else if (!isOutsidePlot) {
               // show the tooltip
               onmousemove(e);
            }

            lastWasOutsidePlot = isOutsidePlot;

            // when outside plot, allow touch-drag by returning true
            return isOutsidePlot || !hasCartesianSeries;
         };

         /*
          * When the mouse enters the container, run mouseMove
          */
         container.onmousemove = mouseMove;

         /*
          * When the mouse leaves the container, hide the tracking (tooltip).
          */
         addEvent(container, 'mouseleave', resetTracker);

         // issue #149 workaround
         // The mouseleave event above does not always fire. Whenever the mouse is moving
         // outside the plotarea, hide the tooltip
         addEvent(doc, 'mousemove', hideTooltipOnMouseMove);

         container.ontouchstart = function (e) {
            // For touch devices, use touchmove to zoom
            if (zoomX || zoomY) {
               container.onmousedown(e);
            }
            // Show tooltip and prevent the lower mouse pseudo event
            mouseMove(e);
         };

         /*
          * Allow dragging the finger over the chart to read the values on touch
          * devices
          */
         container.ontouchmove = mouseMove;

         /*
          * Allow dragging the finger over the chart to read the values on touch
          * devices
          */
         container.ontouchend = function () {
            if (hasDragged) {
               resetTracker();
            }
         };


         // MooTools 1.2.3 doesn't fire this in IE when using addEvent
         container.onclick = function (e) {
            var hoverPoint = chart.hoverPoint;
            e = normalizeMouseEvent(e);

            e.cancelBubble = true; // IE specific


            if (!hasDragged) {
               if (hoverPoint && attr(e.target, 'isTracker')) {
                  var plotX = hoverPoint.plotX,
                     plotY = hoverPoint.plotY;

                  // add page position info
                  extend(hoverPoint, {
                     pageX: chartPosition.left + plotLeft +
                        (inverted ? plotWidth - plotY : plotX),
                     pageY: chartPosition.top + plotTop +
                        (inverted ? plotHeight - plotX : plotY)
                  });

                  // the series click event
                  fireEvent(hoverPoint.series, 'click', extend(e, {
                     point: hoverPoint
                  }));

                  // the point click event
                  hoverPoint.firePointEvent('click', e);

               } else {
                  extend(e, getMouseCoordinates(e));

                  // fire a click event in the chart
                  if (isInsidePlot(e.chartX - plotLeft, e.chartY - plotTop)) {
                     fireEvent(chart, 'click', e);
                  }
               }


            }
            // reset mouseIsDown and hasDragged
            hasDragged = false;
         };

      }

      /**
       * Destroys the MouseTracker object and disconnects DOM events.
       */
      function destroy() {
         // Destroy the tracker group element
         if (chart.trackerGroup) {
            chart.trackerGroup = trackerGroup = chart.trackerGroup.destroy();
         }

         removeEvent(doc, 'mousemove', hideTooltipOnMouseMove);
         container.onclick = container.onmousedown = container.onmousemove = container.ontouchstart = container.ontouchend = container.ontouchmove = null;
      }

      /**
       * Create the image map that listens for mouseovers
       */
      placeTrackerGroup = function () {

         // first create - plot positions is not final at this stage
         if (!trackerGroup) {
            chart.trackerGroup = trackerGroup = renderer.g('tracker')
               .attr({ zIndex: 9 })
               .add();

         // then position - this happens on load and after resizing and changing
         // axis or box positions
         } else {
            trackerGroup.translate(plotLeft, plotTop);
            if (inverted) {
               trackerGroup.attr({
                  width: chart.plotWidth,
                  height: chart.plotHeight
               }).invert();
            }
         }
      };


      // Run MouseTracker
      placeTrackerGroup();
      if (options.enabled) {
         chart.tooltip = tooltip = Tooltip(options);
      }

      setDOMEvents();

      // set the fixed interval ticking for the smooth tooltip
      tooltipInterval = setInterval(function () {
         if (tooltipTick) {
            tooltipTick();
         }
      }, 32);

      // expose properties
      extend(this, {
         zoomX: zoomX,
         zoomY: zoomY,
         resetTracker: resetTracker,
         destroy: destroy
      });
   }



   /**
    * The overview of the chart's series
    */
   var Legend = function () {

      var options = chart.options.legend;

      if (!options.enabled) {
         return;
      }

      var horizontal = options.layout === 'horizontal',
         symbolWidth = options.symbolWidth,
         symbolPadding = options.symbolPadding,
         allItems,
         style = options.style,
         itemStyle = options.itemStyle,
         itemHoverStyle = options.itemHoverStyle,
         itemHiddenStyle = options.itemHiddenStyle,
         padding = pInt(style.padding),
         y = 18,
         initialItemX = 4 + padding + symbolWidth + symbolPadding,
         itemX,
         itemY,
         lastItemY,
         itemHeight = 0,
         box,
         legendBorderWidth = options.borderWidth,
         legendBackgroundColor = options.backgroundColor,
         legendGroup,
         offsetWidth,
         widthOption = options.width,
         series = chart.series,
         reversedLegend = options.reversed;



      /**
       * Set the colors for the legend item
       * @param {Object} item A Series or Point instance
       * @param {Object} visible Dimmed or colored
       */
      function colorizeItem(item, visible) {
         var legendItem = item.legendItem,
            legendLine = item.legendLine,
            legendSymbol = item.legendSymbol,
            hiddenColor = itemHiddenStyle.color,
            textColor = visible ? options.itemStyle.color : hiddenColor,
            lineColor = visible ? item.color : hiddenColor,
            symbolAttr = visible ? item.pointAttr[NORMAL_STATE] : {
               stroke: hiddenColor,
               fill: hiddenColor
            };

         if (legendItem) {
            legendItem.css({ fill: textColor });
         }
         if (legendLine) {
            legendLine.attr({ stroke: lineColor });
         }
         if (legendSymbol) {
            legendSymbol.attr(symbolAttr);
         }

      }

      /**
       * Position the legend item
       * @param {Object} item A Series or Point instance
       * @param {Object} visible Dimmed or colored
       */
      function positionItem(item, itemX, itemY) {
         var legendItem = item.legendItem,
            legendLine = item.legendLine,
            legendSymbol = item.legendSymbol,
            checkbox = item.checkbox;
         if (legendItem) {
            legendItem.attr({
               x: itemX,
               y: itemY
            });
         }
         if (legendLine) {
            legendLine.translate(itemX, itemY - 4);
         }
         if (legendSymbol) {
            legendSymbol.attr({
               x: itemX + legendSymbol.xOff,
               y: itemY + legendSymbol.yOff
            });
         }
         if (checkbox) {
            checkbox.x = itemX;
            checkbox.y = itemY;
         }
      }

      /**
       * Destroy a single legend item
       * @param {Object} item The series or point
       */
      function destroyItem(item) {
         var checkbox = item.checkbox;

         // pull out from the array
         //erase(allItems, item);

         // destroy SVG elements
         each(['legendItem', 'legendLine', 'legendSymbol'], function (key) {
            if (item[key]) {
               item[key].destroy();
            }
         });

         if (checkbox) {
            discardElement(item.checkbox);
         }


      }

      /**
       * Destroys the legend.
       */
      function destroy() {
         if (box) {
            box = box.destroy();
         }

         if (legendGroup) {
            legendGroup = legendGroup.destroy();
         }
      }

      /**
       * Position the checkboxes after the width is determined
       */
      function positionCheckboxes() {
         each(allItems, function (item) {
            var checkbox = item.checkbox,
               alignAttr = legendGroup.alignAttr;
            if (checkbox) {
               css(checkbox, {
                  left: (alignAttr.translateX + item.legendItemWidth + checkbox.x - 40) + PX,
                  top: (alignAttr.translateY + checkbox.y - 11) + PX
               });
            }
         });
      }

      /**
       * Render a single specific legend item
       * @param {Object} item A series or point
       */
      function renderItem(item) {
         var bBox,
            itemWidth,
            legendSymbol,
            symbolX,
            symbolY,
            simpleSymbol,
            li = item.legendItem,
            series = item.series || item,
            itemOptions = series.options,
            strokeWidth = (itemOptions && itemOptions.borderWidth) || 0;

         if (!li) { // generate it once, later move it

            // let these series types use a simple symbol
            simpleSymbol = /^(bar|pie|area|column)$/.test(series.type);

            // generate the list item text
            item.legendItem = li = renderer.text(
                  options.labelFormatter.call(item),
                  0,
                  0
               )
               .css(item.visible ? itemStyle : itemHiddenStyle)
               .on('mouseover', function () {
                  item.setState(HOVER_STATE);
                  li.css(itemHoverStyle);
               })
               .on('mouseout', function () {
                  li.css(item.visible ? itemStyle : itemHiddenStyle);
                  item.setState();
               })
               .on('click', function () {
                  var strLegendItemClick = 'legendItemClick',
                     fnLegendItemClick = function () {
                        item.setVisible();
                     };

                  // click the name or symbol
                  if (item.firePointEvent) { // point
                     item.firePointEvent(strLegendItemClick, null, fnLegendItemClick);
                  } else {
                     fireEvent(item, strLegendItemClick, null, fnLegendItemClick);
                  }
               })
               .attr({ zIndex: 2 })
               .add(legendGroup);

            // draw the line
            if (!simpleSymbol && itemOptions && itemOptions.lineWidth) {
               var attrs = {
                     'stroke-width': itemOptions.lineWidth,
                     zIndex: 2
                  };
               if (itemOptions.dashStyle) {
                  attrs.dashstyle = itemOptions.dashStyle;
               }
               item.legendLine = renderer.path([
                  M,
                  -symbolWidth - symbolPadding,
                  0,
                  L,
                  -symbolPadding,
                  0
               ])
               .attr(attrs)
               .add(legendGroup);
            }

            // draw a simple symbol
            if (simpleSymbol) { // bar|pie|area|column

               legendSymbol = renderer.rect(
                  (symbolX = -symbolWidth - symbolPadding),
                  (symbolY = -11),
                  symbolWidth,
                  12,
                  2
               ).attr({
                  //'stroke-width': 0,
                  zIndex: 3
               }).add(legendGroup);

            // draw the marker
            } else if (itemOptions && itemOptions.marker && itemOptions.marker.enabled) {
               legendSymbol = renderer.symbol(
                  item.symbol,
                  (symbolX = -symbolWidth / 2 - symbolPadding),
                  (symbolY = -4),
                  itemOptions.marker.radius
               )
               //.attr(item.pointAttr[NORMAL_STATE])
               .attr({ zIndex: 3 })
               .add(legendGroup);

            }
            if (legendSymbol) {
               legendSymbol.xOff = symbolX + (strokeWidth % 2 / 2);
               legendSymbol.yOff = symbolY + (strokeWidth % 2 / 2);
            }

            item.legendSymbol = legendSymbol;

            // colorize the items
            colorizeItem(item, item.visible);


            // add the HTML checkbox on top
            if (itemOptions && itemOptions.showCheckbox) {
               item.checkbox = createElement('input', {
                  type: 'checkbox',
                  checked: item.selected,
                  defaultChecked: item.selected // required by IE7
               }, options.itemCheckboxStyle, container);

               addEvent(item.checkbox, 'click', function (event) {
                  var target = event.target;
                  fireEvent(item, 'checkboxClick', {
                        checked: target.checked
                     },
                     function () {
                        item.select();
                     }
                  );
               });
            }
         }


         // calculate the positions for the next line
         bBox = li.getBBox();

         itemWidth = item.legendItemWidth =
            options.itemWidth || symbolWidth + symbolPadding + bBox.width + padding;
         itemHeight = bBox.height;

         // if the item exceeds the width, start a new line
         if (horizontal && itemX - initialItemX + itemWidth >
               (widthOption || (chartWidth - 2 * padding - initialItemX))) {
            itemX = initialItemX;
            itemY += itemHeight;
         }
         lastItemY = itemY;

         // position the newly generated or reordered items
         positionItem(item, itemX, itemY);

         // advance
         if (horizontal) {
            itemX += itemWidth;
         } else {
            itemY += itemHeight;
         }

         // the width of the widest item
         offsetWidth = widthOption || mathMax(
            horizontal ? itemX - initialItemX : itemWidth,
            offsetWidth
         );



         // add it all to an array to use below
         //allItems.push(item);
      }

      /**
       * Render the legend. This method can be called both before and after
       * chart.render. If called after, it will only rearrange items instead
       * of creating new ones.
       */
      function renderLegend() {
         itemX = initialItemX;
         itemY = y;
         offsetWidth = 0;
         lastItemY = 0;

         if (!legendGroup) {
            legendGroup = renderer.g('legend')
               .attr({ zIndex: 7 })
               .add();
         }


         // add each series or point
         allItems = [];
         each(series, function (serie) {
            var seriesOptions = serie.options;

            if (!seriesOptions.showInLegend) {
               return;
            }

            // use points or series for the legend item depending on legendType
            allItems = allItems.concat(seriesOptions.legendType === 'point' ?
               serie.data :
               serie
            );

         });

         // sort by legendIndex
         stableSort(allItems, function (a, b) {
            return (a.options.legendIndex || 0) - (b.options.legendIndex || 0);
         });

         // reversed legend
         if (reversedLegend) {
            allItems.reverse();
         }

         // render the items
         each(allItems, renderItem);



         // Draw the border
         legendWidth = widthOption || offsetWidth;
         legendHeight = lastItemY - y + itemHeight;

         if (legendBorderWidth || legendBackgroundColor) {
            legendWidth += 2 * padding;
            legendHeight += 2 * padding;

            if (!box) {
               box = renderer.rect(
                  0,
                  0,
                  legendWidth,
                  legendHeight,
                  options.borderRadius,
                  legendBorderWidth || 0
               ).attr({
                  stroke: options.borderColor,
                  'stroke-width': legendBorderWidth || 0,
                  fill: legendBackgroundColor || NONE
               })
               .add(legendGroup)
               .shadow(options.shadow);
               box.isNew = true;

            } else if (legendWidth > 0 && legendHeight > 0) {
               box[box.isNew ? 'attr' : 'animate'](
                  box.crisp(null, null, null, legendWidth, legendHeight)
               );
               box.isNew = false;
            }

            // hide the border if no items
            box[allItems.length ? 'show' : 'hide']();
         }

         // 1.x compatibility: positioning based on style
         var props = ['left', 'right', 'top', 'bottom'],
            prop,
            i = 4;
         while (i--) {
            prop = props[i];
            if (style[prop] && style[prop] !== 'auto') {
               options[i < 2 ? 'align' : 'verticalAlign'] = prop;
               options[i < 2 ? 'x' : 'y'] = pInt(style[prop]) * (i % 2 ? -1 : 1);
            }
         }

         if (allItems.length) {
            legendGroup.align(extend(options, {
               width: legendWidth,
               height: legendHeight
            }), true, spacingBox);
         }

         if (!isResizing) {
            positionCheckboxes();
         }
      }


      // run legend
      renderLegend();

      // move checkboxes
      addEvent(chart, 'endResize', positionCheckboxes);

      // expose
      return {
         colorizeItem: colorizeItem,
         destroyItem: destroyItem,
         renderLegend: renderLegend,
         destroy: destroy
      };
   };






   /**
    * Initialize an individual series, called internally before render time
    */
   function initSeries(options) {
      var type = options.type || optionsChart.type || optionsChart.defaultSeriesType,
         typeClass = seriesTypes[type],
         serie,
         hasRendered = chart.hasRendered;

      // an inverted chart can't take a column series and vice versa
      if (hasRendered) {
         if (inverted && type === 'column') {
            typeClass = seriesTypes.bar;
         } else if (!inverted && type === 'bar') {
            typeClass = seriesTypes.column;
         }
      }

      serie = new typeClass();

      serie.init(chart, options);

      // set internal chart properties
      if (!hasRendered && serie.inverted) {
         inverted = true;
      }
      if (serie.isCartesian) {
         hasCartesianSeries = serie.isCartesian;
      }

      series.push(serie);

      return serie;
   }

   /**
    * Add a series dynamically after  time
    *
    * @param {Object} options The config options
    * @param {Boolean} redraw Whether to redraw the chart after adding. Defaults to true.
    * @param {Boolean|Object} animation Whether to apply animation, and optionally animation
    *    configuration
    *
    * @return {Object} series The newly created series object
    */
   function addSeries(options, redraw, animation) {
      var series;

      if (options) {
         setAnimation(animation, chart);
         redraw = pick(redraw, true); // defaults to true

         fireEvent(chart, 'addSeries', { options: options }, function () {
            series = initSeries(options);
            series.isDirty = true;

            chart.isDirtyLegend = true; // the series array is out of sync with the display
            if (redraw) {
               chart.redraw();
            }
         });
      }

      return series;
   }

   /**
    * Check whether a given point is within the plot area
    *
    * @param {Number} x Pixel x relative to the coordinateSystem
    * @param {Number} y Pixel y relative to the coordinateSystem
    */
   isInsidePlot = function (x, y) {
      return x >= 0 &&
         x <= plotWidth &&
         y >= 0 &&
         y <= plotHeight;
   };

   /**
    * Adjust all axes tick amounts
    */
   function adjustTickAmounts() {
      if (optionsChart.alignTicks !== false) {
         each(axes, function (axis) {
            axis.adjustTickAmount();
         });
      }
      maxTicks = null;
   }

   /**
    * Redraw legend, axes or series based on updated data
    *
    * @param {Boolean|Object} animation Whether to apply animation, and optionally animation
    *    configuration
    */
   function redraw(animation) {
      var redrawLegend = chart.isDirtyLegend,
         hasStackedSeries,
         isDirtyBox = chart.isDirtyBox, // todo: check if it has actually changed?
         seriesLength = series.length,
         i = seriesLength,
         clipRect = chart.clipRect,
         serie;

      setAnimation(animation, chart);

      // link stacked series
      while (i--) {
         serie = series[i];
         if (serie.isDirty && serie.options.stacking) {
            hasStackedSeries = true;
            break;
         }
      }
      if (hasStackedSeries) { // mark others as dirty
         i = seriesLength;
         while (i--) {
            serie = series[i];
            if (serie.options.stacking) {
               serie.isDirty = true;
            }
         }
      }

      // handle updated data in the series
      each(series, function (serie) {
         if (serie.isDirty) { // prepare the data so axis can read it
            serie.cleanData();
            serie.getSegments();

            if (serie.options.legendType === 'point') {
               redrawLegend = true;
            }
         }
      });

      // handle added or removed series
      if (redrawLegend && legend.renderLegend) { // series or pie points are added or removed
         // draw legend graphics
         legend.renderLegend();

         chart.isDirtyLegend = false;
      }

      if (hasCartesianSeries) {
         if (!isResizing) {

            // reset maxTicks
            maxTicks = null;

            // set axes scales
            each(axes, function (axis) {
               axis.setScale();
            });
         }
         adjustTickAmounts();
         getMargins();

         // redraw axes
         each(axes, function (axis) {
            if (axis.isDirty || isDirtyBox) {
               axis.redraw();
               isDirtyBox = true; // always redraw box to reflect changes in the axis labels
            }
         });


      }

      // the plot areas size has changed
      if (isDirtyBox) {
         drawChartBox();
         placeTrackerGroup();

         // move clip rect
         if (clipRect) {
            stop(clipRect);
            clipRect.animate({ // for chart resize
               width: chart.plotSizeX,
               height: chart.plotSizeY
            });
         }

      }


      // redraw affected series
      each(series, function (serie) {
         if (serie.isDirty && serie.visible &&
               (!serie.isCartesian || serie.xAxis)) { // issue #153
            serie.redraw();
         }
      });


      // hide tooltip and hover states
      if (tracker && tracker.resetTracker) {
         tracker.resetTracker();
      }

      // fire the event
      fireEvent(chart, 'redraw');
   }



   /**
    * Dim the chart and show a loading text or symbol
    * @param {String} str An optional text to show in the loading label instead of the default one
    */
   function showLoading(str) {
      var loadingOptions = options.loading;

      // create the layer at the first call
      if (!loadingDiv) {
         loadingDiv = createElement(DIV, {
            className: 'highcharts-loading'
         }, extend(loadingOptions.style, {
            left: plotLeft + PX,
            top: plotTop + PX,
            width: plotWidth + PX,
            height: plotHeight + PX,
            zIndex: 10,
            display: NONE
         }), container);

         loadingSpan = createElement(
            'span',
            null,
            loadingOptions.labelStyle,
            loadingDiv
         );

      }

      // update text
      loadingSpan.innerHTML = str || options.lang.loading;

      // show it
      if (!loadingShown) {
         css(loadingDiv, { opacity: 0, display: '' });
         animate(loadingDiv, {
            opacity: loadingOptions.style.opacity
         }, {
            duration: loadingOptions.showDuration
         });
         loadingShown = true;
      }
   }
   /**
    * Hide the loading layer
    */
   function hideLoading() {
      animate(loadingDiv, {
         opacity: 0
      }, {
         duration: options.loading.hideDuration,
         complete: function () {
            css(loadingDiv, { display: NONE });
         }
      });
      loadingShown = false;
   }

   /**
    * Get an axis, series or point object by id.
    * @param id {String} The id as given in the configuration options
    */
   function get(id) {
      var i,
         j,
         data;

      // search axes
      for (i = 0; i < axes.length; i++) {
         if (axes[i].options.id === id) {
            return axes[i];
         }
      }

      // search series
      for (i = 0; i < series.length; i++) {
         if (series[i].options.id === id) {
            return series[i];
         }
      }

      // search points
      for (i = 0; i < series.length; i++) {
         data = series[i].data;
         for (j = 0; j < data.length; j++) {
            if (data[j].id === id) {
               return data[j];
            }
         }
      }
      return null;
   }

   /**
    * Create the Axis instances based on the config options
    */
   function getAxes() {
      var xAxisOptions = options.xAxis || {},
         yAxisOptions = options.yAxis || {},
         axis;

      // make sure the options are arrays and add some members
      xAxisOptions = splat(xAxisOptions);
      each(xAxisOptions, function (axis, i) {
         axis.index = i;
         axis.isX = true;
      });

      yAxisOptions = splat(yAxisOptions);
      each(yAxisOptions, function (axis, i) {
         axis.index = i;
      });

      // concatenate all axis options into one array
      axes = xAxisOptions.concat(yAxisOptions);

      // loop the options and construct axis objects
      chart.xAxis = [];
      chart.yAxis = [];
      axes = map(axes, function (axisOptions) {
         axis = new Axis(axisOptions);
         chart[axis.isXAxis ? 'xAxis' : 'yAxis'].push(axis);

         return axis;
      });

      adjustTickAmounts();
   }


   /**
    * Get the currently selected points from all series
    */
   function getSelectedPoints() {
      var points = [];
      each(series, function (serie) {
         points = points.concat(grep(serie.data, function (point) {
            return point.selected;
         }));
      });
      return points;
   }

   /**
    * Get the currently selected series
    */
   function getSelectedSeries() {
      return grep(series, function (serie) {
         return serie.selected;
      });
   }

   /**
    * Zoom out to 1:1
    */
   zoomOut = function () {
      fireEvent(chart, 'selection', { resetSelection: true }, zoom);
      chart.toolbar.remove('zoom');

   };
   /**
    * Zoom into a given portion of the chart given by axis coordinates
    * @param {Object} event
    */
   zoom = function (event) {

      // add button to reset selection
      var lang = defaultOptions.lang,
         animate = chart.pointCount < 100;
      chart.toolbar.add('zoom', lang.resetZoom, lang.resetZoomTitle, zoomOut);

      // if zoom is called with no arguments, reset the axes
      if (!event || event.resetSelection) {
         each(axes, function (axis) {
            axis.setExtremes(null, null, false, animate);
         });
      } else { // else, zoom in on all axes
         each(event.xAxis.concat(event.yAxis), function (axisData) {
            var axis = axisData.axis;

            // don't zoom more than maxZoom
            if (chart.tracker[axis.isXAxis ? 'zoomX' : 'zoomY']) {
               axis.setExtremes(axisData.min, axisData.max, false, animate);
            }
         });
      }

      // redraw chart
      redraw();
   };

   /**
    * Show the title and subtitle of the chart
    *
    * @param titleOptions {Object} New title options
    * @param subtitleOptions {Object} New subtitle options
    *
    */
   function setTitle(titleOptions, subtitleOptions) {

      chartTitleOptions = merge(options.title, titleOptions);
      chartSubtitleOptions = merge(options.subtitle, subtitleOptions);

      // add title and subtitle
      each([
         ['title', titleOptions, chartTitleOptions],
         ['subtitle', subtitleOptions, chartSubtitleOptions]
      ], function (arr) {
         var name = arr[0],
            title = chart[name],
            titleOptions = arr[1],
            chartTitleOptions = arr[2];

         if (title && titleOptions) {
            title = title.destroy(); // remove old
         }
         if (chartTitleOptions && chartTitleOptions.text && !title) {
            chart[name] = renderer.text(
               chartTitleOptions.text,
               0,
               0,
               chartTitleOptions.useHTML
            )
            .attr({
               align: chartTitleOptions.align,
               'class': 'highcharts-' + name,
               zIndex: 1
            })
            .css(chartTitleOptions.style)
            .add()
            .align(chartTitleOptions, false, spacingBox);
         }
      });

   }

   /**
    * Get chart width and height according to options and container size
    */
   function getChartSize() {

      containerWidth = (renderToClone || renderTo).offsetWidth;
      containerHeight = (renderToClone || renderTo).offsetHeight;
      chart.chartWidth = chartWidth = optionsChart.width || containerWidth || 600;
      chart.chartHeight = chartHeight = optionsChart.height ||
         // the offsetHeight of an empty container is 0 in standard browsers, but 19 in IE7:
         (containerHeight > 19 ? containerHeight : 400);
   }


   /**
    * Get the containing element, determine the size and create the inner container
    * div to hold the chart
    */
   function getContainer() {
      renderTo = optionsChart.renderTo;
      containerId = PREFIX + idCounter++;

      if (isString(renderTo)) {
         renderTo = doc.getElementById(renderTo);
      }

      // remove previous chart
      renderTo.innerHTML = '';

      // If the container doesn't have an offsetWidth, it has or is a child of a node
      // that has display:none. We need to temporarily move it out to a visible
      // state to determine the size, else the legend and tooltips won't render
      // properly
      if (!renderTo.offsetWidth) {
         renderToClone = renderTo.cloneNode(0);
         css(renderToClone, {
            position: ABSOLUTE,
            top: '-9999px',
            display: ''
         });
         doc.body.appendChild(renderToClone);
      }

      // get the width and height
      getChartSize();

      // create the inner container
      chart.container = container = createElement(DIV, {
            className: 'highcharts-container' +
               (optionsChart.className ? ' ' + optionsChart.className : ''),
            id: containerId
         }, extend({
            position: RELATIVE,
            overflow: HIDDEN, // needed for context menu (avoid scrollbars) and
               // content overflow in IE
            width: chartWidth + PX,
            height: chartHeight + PX,
            textAlign: 'left'
         }, optionsChart.style),
         renderToClone || renderTo
      );

      chart.renderer = renderer =
         optionsChart.forExport ? // force SVG, used for SVG export
            new SVGRenderer(container, chartWidth, chartHeight, true) :
            new Renderer(container, chartWidth, chartHeight);

      // Issue 110 workaround:
      // In Firefox, if a div is positioned by percentage, its pixel position may land
      // between pixels. The container itself doesn't display this, but an SVG element
      // inside this container will be drawn at subpixel precision. In order to draw
      // sharp lines, this must be compensated for. This doesn't seem to work inside
      // iframes though (like in jsFiddle).
      var subPixelFix, rect;
      if (isFirefox && container.getBoundingClientRect) {
         subPixelFix = function () {
            css(container, { left: 0, top: 0 });
            rect = container.getBoundingClientRect();
            css(container, {
               left: (-(rect.left - pInt(rect.left))) + PX,
               top: (-(rect.top - pInt(rect.top))) + PX
            });
         };

         // run the fix now
         subPixelFix();

         // run it on resize
         addEvent(win, 'resize', subPixelFix);

         // remove it on chart destroy
         addEvent(chart, 'destroy', function () {
            removeEvent(win, 'resize', subPixelFix);
         });
      }
   }

   /**
    * Calculate margins by rendering axis labels in a preliminary position. Title,
    * subtitle and legend have already been rendered at this stage, but will be
    * moved into their final positions
    */
   getMargins = function () {
      var legendOptions = options.legend,
         legendMargin = pick(legendOptions.margin, 10),
         legendX = legendOptions.x,
         legendY = legendOptions.y,
         align = legendOptions.align,
         verticalAlign = legendOptions.verticalAlign,
         titleOffset;

      resetMargins();

      // adjust for title and subtitle
      if ((chart.title || chart.subtitle) && !defined(optionsMarginTop)) {
         titleOffset = mathMax(
            (chart.title && !chartTitleOptions.floating && !chartTitleOptions.verticalAlign && chartTitleOptions.y) || 0,
            (chart.subtitle && !chartSubtitleOptions.floating && !chartSubtitleOptions.verticalAlign && chartSubtitleOptions.y) || 0
         );
         if (titleOffset) {
            plotTop = mathMax(plotTop, titleOffset + pick(chartTitleOptions.margin, 15) + spacingTop);
         }
      }
      // adjust for legend
      if (legendOptions.enabled && !legendOptions.floating) {
         if (align === 'right') { // horizontal alignment handled first
            if (!defined(optionsMarginRight)) {
               marginRight = mathMax(
                  marginRight,
                  legendWidth - legendX + legendMargin + spacingRight
               );
            }
         } else if (align === 'left') {
            if (!defined(optionsMarginLeft)) {
               plotLeft = mathMax(
                  plotLeft,
                  legendWidth + legendX + legendMargin + spacingLeft
               );
            }

         } else if (verticalAlign === 'top') {
            if (!defined(optionsMarginTop)) {
               plotTop = mathMax(
                  plotTop,
                  legendHeight + legendY + legendMargin + spacingTop
               );
            }

         } else if (verticalAlign === 'bottom') {
            if (!defined(optionsMarginBottom)) {
               marginBottom = mathMax(
                  marginBottom,
                  legendHeight - legendY + legendMargin + spacingBottom
               );
            }
         }
      }

      // pre-render axes to get labels offset width
      if (hasCartesianSeries) {
         each(axes, function (axis) {
            axis.getOffset();
         });
      }

      if (!defined(optionsMarginLeft)) {
         plotLeft += axisOffset[3];
      }
      if (!defined(optionsMarginTop)) {
         plotTop += axisOffset[0];
      }
      if (!defined(optionsMarginBottom)) {
         marginBottom += axisOffset[2];
      }
      if (!defined(optionsMarginRight)) {
         marginRight += axisOffset[1];
      }

      setChartSize();

   };

   /**
    * Add the event handlers necessary for auto resizing
    *
    */
   function initReflow() {
      var reflowTimeout;
      function reflow() {
         var width = optionsChart.width || renderTo.offsetWidth,
            height = optionsChart.height || renderTo.offsetHeight;

         if (width && height) { // means container is display:none
            if (width !== containerWidth || height !== containerHeight) {
               clearTimeout(reflowTimeout);
               reflowTimeout = setTimeout(function () {
                  resize(width, height, false);
               }, 100);
            }
            containerWidth = width;
            containerHeight = height;
         }
      }
      addEvent(win, 'resize', reflow);
      addEvent(chart, 'destroy', function () {
         removeEvent(win, 'resize', reflow);
      });
   }

   /**
    * Fires endResize event on chart instance.
    */
   function fireEndResize() {
      fireEvent(chart, 'endResize', null, function () {
         isResizing -= 1;
      });
   }

   /**
    * Resize the chart to a given width and height
    * @param {Number} width
    * @param {Number} height
    * @param {Object|Boolean} animation
    */
   resize = function (width, height, animation) {
      var chartTitle = chart.title,
         chartSubtitle = chart.subtitle;

      isResizing += 1;

      // set the animation for the current process
      setAnimation(animation, chart);

      oldChartHeight = chartHeight;
      oldChartWidth = chartWidth;
      chart.chartWidth = chartWidth = mathRound(width);
      chart.chartHeight = chartHeight = mathRound(height);

      css(container, {
         width: chartWidth + PX,
         height: chartHeight + PX
      });
      renderer.setSize(chartWidth, chartHeight, animation);

      // update axis lengths for more correct tick intervals:
      plotWidth = chartWidth - plotLeft - marginRight;
      plotHeight = chartHeight - plotTop - marginBottom;

      // handle axes
      maxTicks = null;
      each(axes, function (axis) {
         axis.isDirty = true;
         axis.setScale();
      });

      // make sure non-cartesian series are also handled
      each(series, function (serie) {
         serie.isDirty = true;
      });

      chart.isDirtyLegend = true; // force legend redraw
      chart.isDirtyBox = true; // force redraw of plot and chart border

      getMargins();

      // move titles
      if (chartTitle) {
         chartTitle.align(null, null, spacingBox);
      }
      if (chartSubtitle) {
         chartSubtitle.align(null, null, spacingBox);
      }

      redraw(animation);


      oldChartHeight = null;
      fireEvent(chart, 'resize');

      // fire endResize and set isResizing back
      // If animation is disabled, fire without delay
      if (globalAnimation === false) {
         fireEndResize();
      } else { // else set a timeout with the animation duration
         setTimeout(fireEndResize, (globalAnimation && globalAnimation.duration) || 500);
      }
   };

   /**
    * Set the public chart properties. This is done before and after the pre-render
    * to determine margin sizes
    */
   setChartSize = function () {

      chart.plotLeft = plotLeft = mathRound(plotLeft);
      chart.plotTop = plotTop = mathRound(plotTop);
      chart.plotWidth = plotWidth = mathRound(chartWidth - plotLeft - marginRight);
      chart.plotHeight = plotHeight = mathRound(chartHeight - plotTop - marginBottom);

      chart.plotSizeX = inverted ? plotHeight : plotWidth;
      chart.plotSizeY = inverted ? plotWidth : plotHeight;

      spacingBox = {
         x: spacingLeft,
         y: spacingTop,
         width: chartWidth - spacingLeft - spacingRight,
         height: chartHeight - spacingTop - spacingBottom
      };
   };

   /**
    * Initial margins before auto size margins are applied
    */
   resetMargins = function () {
      plotTop = pick(optionsMarginTop, spacingTop);
      marginRight = pick(optionsMarginRight, spacingRight);
      marginBottom = pick(optionsMarginBottom, spacingBottom);
      plotLeft = pick(optionsMarginLeft, spacingLeft);
      axisOffset = [0, 0, 0, 0]; // top, right, bottom, left
   };

   /**
    * Draw the borders and backgrounds for chart and plot area
    */
   drawChartBox = function () {
      var chartBorderWidth = optionsChart.borderWidth || 0,
         chartBackgroundColor = optionsChart.backgroundColor,
         plotBackgroundColor = optionsChart.plotBackgroundColor,
         plotBackgroundImage = optionsChart.plotBackgroundImage,
         mgn,
         plotSize = {
            x: plotLeft,
            y: plotTop,
            width: plotWidth,
            height: plotHeight
         };

      // Chart area
      mgn = chartBorderWidth + (optionsChart.shadow ? 8 : 0);

      if (chartBorderWidth || chartBackgroundColor) {
         if (!chartBackground) {
            chartBackground = renderer.rect(mgn / 2, mgn / 2, chartWidth - mgn, chartHeight - mgn,
                  optionsChart.borderRadius, chartBorderWidth)
               .attr({
                  stroke: optionsChart.borderColor,
                  'stroke-width': chartBorderWidth,
                  fill: chartBackgroundColor || NONE
               })
               .add()
               .shadow(optionsChart.shadow);
         } else { // resize
            chartBackground.animate(
               chartBackground.crisp(null, null, null, chartWidth - mgn, chartHeight - mgn)
            );
         }
      }


      // Plot background
      if (plotBackgroundColor) {
         if (!plotBackground) {
            plotBackground = renderer.rect(plotLeft, plotTop, plotWidth, plotHeight, 0)
               .attr({
                  fill: plotBackgroundColor
               })
               .add()
               .shadow(optionsChart.plotShadow);
         } else {
            plotBackground.animate(plotSize);
         }
      }
      if (plotBackgroundImage) {
         if (!plotBGImage) {
            plotBGImage = renderer.image(plotBackgroundImage, plotLeft, plotTop, plotWidth, plotHeight)
               .add();
         } else {
            plotBGImage.animate(plotSize);
         }
      }

      // Plot area border
      if (optionsChart.plotBorderWidth) {
         if (!plotBorder) {
            plotBorder = renderer.rect(plotLeft, plotTop, plotWidth, plotHeight, 0, optionsChart.plotBorderWidth)
               .attr({
                  stroke: optionsChart.plotBorderColor,
                  'stroke-width': optionsChart.plotBorderWidth,
                  zIndex: 4
               })
               .add();
         } else {
            plotBorder.animate(
               plotBorder.crisp(null, plotLeft, plotTop, plotWidth, plotHeight)
            );
         }
      }

      // reset
      chart.isDirtyBox = false;
   };

   /**
    * Render all graphics for the chart
    */
   function render() {
      var labels = options.labels,
         credits = options.credits,
         creditsHref;

      // Title
      setTitle();


      // Legend
      legend = chart.legend = new Legend();

      // Get margins by pre-rendering axes
      getMargins();
      each(axes, function (axis) {
         axis.setTickPositions(true); // update to reflect the new margins
      });
      adjustTickAmounts();
      getMargins(); // second pass to check for new labels


      // Draw the borders and backgrounds
      drawChartBox();

      // Axes
      if (hasCartesianSeries) {
         each(axes, function (axis) {
            axis.render();
         });
      }


      // The series
      if (!chart.seriesGroup) {
         chart.seriesGroup = renderer.g('series-group')
            .attr({ zIndex: 3 })
            .add();
      }
      each(series, function (serie) {
         serie.translate();
         serie.setTooltipPoints();
         serie.render();
      });


      // Labels
      if (labels.items) {
         each(labels.items, function () {
            var style = extend(labels.style, this.style),
               x = pInt(style.left) + plotLeft,
               y = pInt(style.top) + plotTop + 12;

            // delete to prevent rewriting in IE
            delete style.left;
            delete style.top;

            renderer.text(
               this.html,
               x,
               y
            )
            .attr({ zIndex: 2 })
            .css(style)
            .add();

         });
      }

      // Toolbar (don't redraw)
      if (!chart.toolbar) {
         chart.toolbar = Toolbar();
      }

      // Credits
      if (credits.enabled && !chart.credits) {
         creditsHref = credits.href;
         chart.credits = renderer.text(
            credits.text,
            0,
            0
         )
         .on('click', function () {
            if (creditsHref) {
               location.href = creditsHref;
            }
         })
         .attr({
            align: credits.position.align,
            zIndex: 8
         })
         .css(credits.style)
         .add()
         .align(credits.position);
      }

      placeTrackerGroup();

      // Set flag
      chart.hasRendered = true;

      // If the chart was rendered outside the top container, put it back in
      if (renderToClone) {
         renderTo.appendChild(container);
         discardElement(renderToClone);
         //updatePosition(container);
      }
   }

   /**
    * Clean up memory usage
    */
   function destroy() {
      var i,
         parentNode = container && container.parentNode;

      // If the chart is destroyed already, do nothing.
      // This will happen if if a script invokes chart.destroy and
      // then it will be called again on win.unload
      if (chart === null) {
         return;
      }

      // fire the chart.destoy event
      fireEvent(chart, 'destroy');

      // remove events
      removeEvent(win, 'unload', destroy);
      removeEvent(chart);

      // ==== Destroy collections:
      // Destroy axes
      i = axes.length;
      while (i--) {
         axes[i] = axes[i].destroy();
      }

      // Destroy each series
      i = series.length;
      while (i--) {
         series[i] = series[i].destroy();
      }

      // ==== Destroy chart properties:
      each(['title', 'subtitle', 'seriesGroup', 'clipRect', 'credits', 'tracker'], function (name) {
         var prop = chart[name];

         if (prop) {
            chart[name] = prop.destroy();
         }
      });

      // ==== Destroy local variables:
      each([chartBackground, plotBorder, plotBackground, legend, tooltip, renderer, tracker], function (obj) {
         if (obj && obj.destroy) {
            obj.destroy();
         }
      });
      chartBackground = plotBorder = plotBackground = legend = tooltip = renderer = tracker = null;

      // remove container and all SVG
      if (container) { // can break in IE when destroyed before finished loading
         container.innerHTML = '';
         removeEvent(container);
         if (parentNode) {
            discardElement(container);
         }

         // IE6 leak
         container = null;
      }

      // memory and CPU leak
      clearInterval(tooltipInterval);

      // clean it all up
      for (i in chart) {
         delete chart[i];
      }

      chart = null;
   }
   /**
    * Prepare for first rendering after all data are loaded
    */
   function firstRender() {

      // VML namespaces can't be added until after complete. Listening
      // for Perini's doScroll hack is not enough.
      var ONREADYSTATECHANGE = 'onreadystatechange',
         COMPLETE = 'complete';
      // Note: in spite of JSLint's complaints, win == win.top is required
      /*jslint eqeq: true*/
      if (!hasSVG && win == win.top && doc.readyState !== COMPLETE) {
      /*jslint eqeq: false*/
         doc.attachEvent(ONREADYSTATECHANGE, function () {
            doc.detachEvent(ONREADYSTATECHANGE, firstRender);
            if (doc.readyState === COMPLETE) {
               firstRender();
            }
         });
         return;
      }

      // create the container
      getContainer();

      resetMargins();
      setChartSize();

      // Initialize the series
      each(options.series || [], function (serieOptions) {
         initSeries(serieOptions);
      });

      // Set the common inversion and transformation for inverted series after initSeries
      chart.inverted = inverted = pick(inverted, options.chart.inverted);


      getAxes();


      chart.render = render;

      // depends on inverted and on margins being set
      chart.tracker = tracker = new MouseTracker(options.tooltip);

      //globalAnimation = false;
      render();

      fireEvent(chart, 'load');

      //globalAnimation = true;

      // run callbacks
      if (callback) {
         callback.apply(chart, [chart]);
      }
      each(chart.callbacks, function (fn) {
         fn.apply(chart, [chart]);
      });
   }

   // Run chart


   // Destroy the chart and free up memory.
   addEvent(win, 'unload', destroy);

   // Set up auto resize
   if (optionsChart.reflow !== false) {
      addEvent(chart, 'load', initReflow);
   }

   // Chart event handlers
   if (chartEvents) {
      for (eventType in chartEvents) {
         addEvent(chart, eventType, chartEvents[eventType]);
      }
   }


   chart.options = options;
   chart.series = series;





   // Expose methods and variables
   chart.addSeries = addSeries;
   chart.animation = pick(optionsChart.animation, true);
   chart.destroy = destroy;
   chart.get = get;
   chart.getSelectedPoints = getSelectedPoints;
   chart.getSelectedSeries = getSelectedSeries;
   chart.hideLoading = hideLoading;
   chart.isInsidePlot = isInsidePlot;
   chart.redraw = redraw;
   chart.setSize = resize;
   chart.setTitle = setTitle;
   chart.showLoading = showLoading;
   chart.pointCount = 0;
   chart.counters = new ChartCounters();
   /*
   if ($) $(function() {
      $container = $('#container');
      var origChartWidth,
         origChartHeight;
      if ($container) {
         $('<button>+</button>')
            .insertBefore($container)
            .click(function() {
               if (origChartWidth === UNDEFINED) {
                  origChartWidth = chartWidth;
                  origChartHeight = chartHeight;
               }
               chart.resize(chartWidth *= 1.1, chartHeight *= 1.1);
            });
         $('<button>-</button>')
            .insertBefore($container)
            .click(function() {
               if (origChartWidth === UNDEFINED) {
                  origChartWidth = chartWidth;
                  origChartHeight = chartHeight;
               }
               chart.resize(chartWidth *= 0.9, chartHeight *= 0.9);
            });
         $('<button>1:1</button>')
            .insertBefore($container)
            .click(function() {
               if (origChartWidth === UNDEFINED) {
                  origChartWidth = chartWidth;
                  origChartHeight = chartHeight;
               }
               chart.resize(origChartWidth, origChartHeight);
            });
      }
   })
   */




   firstRender();


} // end Chart

// Hook for exporting module
Chart.prototype.callbacks = [];
/**
 * The Point object and prototype. Inheritable and used as base for PiePoint
 */
var Point = function () {};
Point.prototype = {

   /**
    * Initialize the point
    * @param {Object} series The series object containing this point
    * @param {Object} options The data in either number, array or object format
    */
   init: function (series, options) {
      var point = this,
         counters = series.chart.counters,
         defaultColors;
      point.series = series;
      point.applyOptions(options);
      point.pointAttr = {};

      if (series.options.colorByPoint) {
         defaultColors = series.chart.options.colors;
         if (!point.options) {
            point.options = {};
         }
         point.color = point.options.color = point.color || defaultColors[counters.color++];

         // loop back to zero
         counters.wrapColor(defaultColors.length);
      }

      series.chart.pointCount++;
      return point;
   },
   /**
    * Apply the options containing the x and y data and possible some extra properties.
    * This is called on point init or from point.update.
    *
    * @param {Object} options
    */
   applyOptions: function (options) {
      var point = this,
         series = point.series;

      point.config = options;

      // onedimensional array input
      if (isNumber(options) || options === null) {
         point.y = options;
      } else if (isObject(options) && !isNumber(options.length)) { // object input
         // copy options directly to point
         extend(point, options);
         point.options = options;
      } else if (isString(options[0])) { // categorized data with name in first position
         point.name = options[0];
         point.y = options[1];
      } else if (isNumber(options[0])) { // two-dimentional array
         point.x = options[0];
         point.y = options[1];
      }

      /*
       * If no x is set by now, get auto incremented value. All points must have an
       * x value, however the y value can be null to create a gap in the series
       */
      if (point.x === UNDEFINED) {
         point.x = series.autoIncrement();
      }

   },

   /**
    * Destroy a point to clear memory. Its reference still stays in series.data.
    */
   destroy: function () {
      var point = this,
         series = point.series,
         hoverPoints = series.chart.hoverPoints,
         prop;

      series.chart.pointCount--;

      if (hoverPoints) {
         point.setState();
         erase(hoverPoints, point);
      }
      if (point === series.chart.hoverPoint) {
         point.onMouseOut();
      }


      // remove all events
      removeEvent(point);

      each(['graphic', 'tracker', 'group', 'dataLabel', 'connector', 'shadowGroup'], function (prop) {
         if (point[prop]) {
            point[prop].destroy();
         }
      });

      if (point.legendItem) { // pies have legend items
         point.series.chart.legend.destroyItem(point);
      }

      for (prop in point) {
         point[prop] = null;
      }


   },

   /**
    * Return the configuration hash needed for the data label and tooltip formatters
    */
   getLabelConfig: function () {
      var point = this;
      return {
         x: point.category,
         y: point.y,
         series: point.series,
         point: point,
         percentage: point.percentage,
         total: point.total || point.stackTotal
      };
   },

   /**
    * Toggle the selection status of a point
    * @param {Boolean} selected Whether to select or unselect the point.
    * @param {Boolean} accumulate Whether to add to the previous selection. By default,
    *     this happens if the control key (Cmd on Mac) was pressed during clicking.
    */
   select: function (selected, accumulate) {
      var point = this,
         series = point.series,
         chart = series.chart;

      selected = pick(selected, !point.selected);

      // fire the event with the defalut handler
      point.firePointEvent(selected ? 'select' : 'unselect', { accumulate: accumulate }, function () {
         point.selected = selected;
         point.setState(selected && SELECT_STATE);

         // unselect all other points unless Ctrl or Cmd + click
         if (!accumulate) {
            each(chart.getSelectedPoints(), function (loopPoint) {
               if (loopPoint.selected && loopPoint !== point) {
                  loopPoint.selected = false;
                  loopPoint.setState(NORMAL_STATE);
                  loopPoint.firePointEvent('unselect');
               }
            });
         }
      });
   },

   onMouseOver: function () {
      var point = this,
         chart = point.series.chart,
         tooltip = chart.tooltip,
         hoverPoint = chart.hoverPoint;

      // set normal state to previous series
      if (hoverPoint && hoverPoint !== point) {
         hoverPoint.onMouseOut();
      }

      // trigger the event
      point.firePointEvent('mouseOver');

      // update the tooltip
      if (tooltip && !tooltip.shared) {
         tooltip.refresh(point);
      }

      // hover this
      point.setState(HOVER_STATE);
      chart.hoverPoint = point;
   },

   onMouseOut: function () {
      var point = this;
      point.firePointEvent('mouseOut');

      point.setState();
      point.series.chart.hoverPoint = null;
   },

   /**
    * Extendable method for formatting each point's tooltip line
    *
    * @param {Boolean} useHeader Whether a common header is used for multiple series in the tooltip
    *
    * @return {String} A string to be concatenated in to the common tooltip text
    */
   tooltipFormatter: function (useHeader) {
      var point = this,
         series = point.series;

      return ['<span style="color:' + series.color + '">', (point.name || series.name), '</span>: ',
         (!useHeader ? ('<b>x = ' + (point.name || point.x) + ',</b> ') : ''),
         '<b>', (!useHeader ? 'y = ' : ''), point.y, '</b>'].join('');

   },

   /**
    * Update the point with new options (typically x/y data) and optionally redraw the series.
    *
    * @param {Object} options Point options as defined in the series.data array
    * @param {Boolean} redraw Whether to redraw the chart or wait for an explicit call
    * @param {Boolean|Object} animation Whether to apply animation, and optionally animation
    *    configuration
    *
    */
   update: function (options, redraw, animation) {
      var point = this,
         series = point.series,
         graphic = point.graphic,
         chart = series.chart;

      redraw = pick(redraw, true);

      // fire the event with a default handler of doing the update
      point.firePointEvent('update', { options: options }, function () {

         point.applyOptions(options);

         // update visuals
         if (isObject(options)) {
            series.getAttribs();
            if (graphic) {
               graphic.attr(point.pointAttr[series.state]);
            }
         }

         // redraw
         series.isDirty = true;
         if (redraw) {
            chart.redraw(animation);
         }
      });
   },

   /**
    * Remove a point and optionally redraw the series and if necessary the axes
    * @param {Boolean} redraw Whether to redraw the chart or wait for an explicit call
    * @param {Boolean|Object} animation Whether to apply animation, and optionally animation
    *    configuration
    */
   remove: function (redraw, animation) {
      var point = this,
         series = point.series,
         chart = series.chart,
         data = series.data;

      setAnimation(animation, chart);
      redraw = pick(redraw, true);

      // fire the event with a default handler of removing the point
      point.firePointEvent('remove', null, function () {

         erase(data, point);

         point.destroy();


         // redraw
         series.isDirty = true;
         if (redraw) {
            chart.redraw();
         }
      });


   },

   /**
    * Fire an event on the Point object. Must not be renamed to fireEvent, as this
    * causes a name clash in MooTools
    * @param {String} eventType
    * @param {Object} eventArgs Additional event arguments
    * @param {Function} defaultFunction Default event handler
    */
   firePointEvent: function (eventType, eventArgs, defaultFunction) {
      var point = this,
         series = this.series,
         seriesOptions = series.options;

      // load event handlers on demand to save time on mouseover/out
      if (seriesOptions.point.events[eventType] ||
         (point.options && point.options.events && point.options.events[eventType])) {
         this.importEvents();
      }

      // add default handler if in selection mode
      if (eventType === 'click' && seriesOptions.allowPointSelect) {
         defaultFunction = function (event) {
            // Control key is for Windows, meta (= Cmd key) for Mac, Shift for Opera
            point.select(null, event.ctrlKey || event.metaKey || event.shiftKey);
         };
      }

      fireEvent(this, eventType, eventArgs, defaultFunction);
   },
   /**
    * Import events from the series' and point's options. Only do it on
    * demand, to save processing time on hovering.
    */
   importEvents: function () {
      if (!this.hasImportedEvents) {
         var point = this,
            options = merge(point.series.options.point, point.options),
            events = options.events,
            eventType;

         point.events = events;

         for (eventType in events) {
            addEvent(point, eventType, events[eventType]);
         }
         this.hasImportedEvents = true;

      }
   },

   /**
    * Set the point's state
    * @param {String} state
    */
   setState: function (state) {
      var point = this,
         series = point.series,
         stateOptions = series.options.states,
         markerOptions = defaultPlotOptions[series.type].marker && series.options.marker,
         normalDisabled = markerOptions && !markerOptions.enabled,
         markerStateOptions = markerOptions && markerOptions.states[state],
         stateDisabled = markerStateOptions && markerStateOptions.enabled === false,
         stateMarkerGraphic = series.stateMarkerGraphic,
         chart = series.chart,
         pointAttr = point.pointAttr;

      state = state || NORMAL_STATE; // empty string

      if (
            // already has this state
            state === point.state ||
            // selected points don't respond to hover
            (point.selected && state !== SELECT_STATE) ||
            // series' state options is disabled
            (stateOptions[state] && stateOptions[state].enabled === false) ||
            // point marker's state options is disabled
            (state && (stateDisabled || (normalDisabled && !markerStateOptions.enabled)))

         ) {
         return;
      }

      // apply hover styles to the existing point
      if (point.graphic) {
         point.graphic.attr(pointAttr[state]);
      } else {
         // if a graphic is not applied to each point in the normal state, create a shared
         // graphic for the hover state
         if (state) {
            if (!stateMarkerGraphic) {
               series.stateMarkerGraphic = stateMarkerGraphic = chart.renderer.circle(
                  0,
                  0,
                  pointAttr[state].r
               )
               .attr(pointAttr[state])
               .add(series.group);
            }

            stateMarkerGraphic.translate(
               point.plotX,
               point.plotY
            );
         }

         if (stateMarkerGraphic) {
            stateMarkerGraphic[state ? 'show' : 'hide']();
         }
      }

      point.state = state;
   }
};

/**
 * The base function which all other series types inherit from
 * @param {Object} chart
 * @param {Object} options
 */
var Series = function () {};

Series.prototype = {

   isCartesian: true,
   type: 'line',
   pointClass: Point,
   pointAttrToOptions: { // mapping between SVG attributes and the corresponding options
      stroke: 'lineColor',
      'stroke-width': 'lineWidth',
      fill: 'fillColor',
      r: 'radius'
   },
   init: function (chart, options) {
      var series = this,
         eventType,
         events,
         //pointEvent,
         index = chart.series.length;

      series.chart = chart;
      options = series.setOptions(options); // merge with plotOptions

      // set some variables
      extend(series, {
         index: index,
         options: options,
         name: options.name || 'Series ' + (index + 1),
         state: NORMAL_STATE,
         pointAttr: {},
         visible: options.visible !== false, // true by default
         selected: options.selected === true // false by default
      });

      // register event listeners
      events = options.events;
      for (eventType in events) {
         addEvent(series, eventType, events[eventType]);
      }
      if (
         (events && events.click) ||
         (options.point && options.point.events && options.point.events.click) ||
         options.allowPointSelect
      ) {
         chart.runTrackerClick = true;
      }

      series.getColor();
      series.getSymbol();


      // set the data
      series.setData(options.data, false);

   },


   /**
    * Return an auto incremented x value based on the pointStart and pointInterval options.
    * This is only used if an x value is not given for the point that calls autoIncrement.
    */
   autoIncrement: function () {
      var series = this,
         options = series.options,
         xIncrement = series.xIncrement;

      xIncrement = pick(xIncrement, options.pointStart, 0);

      series.pointInterval = pick(series.pointInterval, options.pointInterval, 1);

      series.xIncrement = xIncrement + series.pointInterval;
      return xIncrement;
   },

   /**
    * Sort the data and remove duplicates
    */
   cleanData: function () {
      var series = this,
         chart = series.chart,
         data = series.data,
         closestPoints,
         smallestInterval,
         chartSmallestInterval = chart.smallestInterval,
         interval,
         i;

      // sort the data points
      stableSort(data, function (a, b) {
         return (a.x - b.x);
      });

      // remove points with equal x values
      // record the closest distance for calculation of column widths
      /*for (i = data.length - 1; i >= 0; i--) {
         if (data[i - 1]) {
            if (data[i - 1].x == data[i].x)   {
               data[i - 1].destroy();
               data.splice(i - 1, 1); // remove the duplicate
            }
         }
      }*/

      // connect nulls
      if (series.options.connectNulls) {
         for (i = data.length - 1; i >= 0; i--) {
            if (data[i].y === null && data[i - 1] && data[i + 1]) {
               data.splice(i, 1);
            }
         }
      }

      // find the closes pair of points
      for (i = data.length - 1; i >= 0; i--) {
         if (data[i - 1]) {
            interval = data[i].x - data[i - 1].x;
            if (interval > 0 && (smallestInterval === UNDEFINED || interval < smallestInterval)) {
               smallestInterval = interval;
               closestPoints = i;
            }
         }
      }

      if (chartSmallestInterval === UNDEFINED || smallestInterval < chartSmallestInterval) {
         chart.smallestInterval = smallestInterval;
      }
      series.closestPoints = closestPoints;
   },

   /**
    * Divide the series data into segments divided by null values. Also sort
    * the data points and delete duplicate values.
    */
   getSegments: function () {
      var lastNull = -1,
         segments = [],
         data = this.data;

      // create the segments
      each(data, function (point, i) {
         if (point.y === null) {
            if (i > lastNull + 1) {
               segments.push(data.slice(lastNull + 1, i));
            }
            lastNull = i;
         } else if (i === data.length - 1) { // last value
            segments.push(data.slice(lastNull + 1, i + 1));
         }
      });
      this.segments = segments;


   },
   /**
    * Set the series options by merging from the options tree
    * @param {Object} itemOptions
    */
   setOptions: function (itemOptions) {
      var plotOptions = this.chart.options.plotOptions,
         options = merge(
            plotOptions[this.type],
            plotOptions.series,
            itemOptions
         );

      return options;

   },
   /**
    * Get the series' color
    */
   getColor: function () {
      var defaultColors = this.chart.options.colors,
         counters = this.chart.counters;
      this.color = this.options.color || defaultColors[counters.color++] || '#0000ff';
      counters.wrapColor(defaultColors.length);
   },
   /**
    * Get the series' symbol
    */
   getSymbol: function () {
      var defaultSymbols = this.chart.options.symbols,
         counters = this.chart.counters;
      this.symbol = this.options.marker.symbol || defaultSymbols[counters.symbol++];
      counters.wrapSymbol(defaultSymbols.length);
   },

   /**
    * Add a point dynamically after chart load time
    * @param {Object} options Point options as given in series.data
    * @param {Boolean} redraw Whether to redraw the chart or wait for an explicit call
    * @param {Boolean} shift If shift is true, a point is shifted off the start
    *    of the series as one is appended to the end.
    * @param {Boolean|Object} animation Whether to apply animation, and optionally animation
    *    configuration
    */
   addPoint: function (options, redraw, shift, animation) {
      var series = this,
         data = series.data,
         graph = series.graph,
         area = series.area,
         chart = series.chart,
         point = (new series.pointClass()).init(series, options);

      setAnimation(animation, chart);

      if (graph && shift) { // make graph animate sideways
         graph.shift = shift;
      }
      if (area) {
         area.shift = shift;
         area.isArea = true;
      }

      redraw = pick(redraw, true);

      data.push(point);
      if (shift) {
         data[0].remove(false);
      }
      series.getAttribs();


      // redraw
      series.isDirty = true;
      if (redraw) {
         chart.redraw();
      }
   },

   /**
    * Replace the series data with a new set of data
    * @param {Object} data
    * @param {Object} redraw
    */
   setData: function (data, redraw) {
      var series = this,
         oldData = series.data,
         initialColor = series.initialColor,
         chart = series.chart,
         i = (oldData && oldData.length) || 0;

      series.xIncrement = null; // reset for new data
      if (defined(initialColor)) { // reset colors for pie
         chart.counters.color = initialColor;
      }

      data = map(splat(data || []), function (pointOptions) {
         return (new series.pointClass()).init(series, pointOptions);
      });

      // destroy old points
      while (i--) {
         oldData[i].destroy();
      }

      // set the data
      series.data = data;

      series.cleanData();
      series.getSegments();


      // cache attributes for shapes
      series.getAttribs();

      // redraw
      series.isDirty = true;
      chart.isDirtyBox = true;
      if (pick(redraw, true)) {
         chart.redraw(false);
      }
   },

   /**
    * Remove a series and optionally redraw the chart
    *
    * @param {Boolean} redraw Whether to redraw the chart or wait for an explicit call
    * @param {Boolean|Object} animation Whether to apply animation, and optionally animation
    *    configuration
    */

   remove: function (redraw, animation) {
      var series = this,
         chart = series.chart;
      redraw = pick(redraw, true);

      if (!series.isRemoving) {  /* prevent triggering native event in jQuery
            (calling the remove function from the remove event) */
         series.isRemoving = true;

         // fire the event with a default handler of removing the point
         fireEvent(series, 'remove', null, function () {


            // destroy elements
            series.destroy();


            // redraw
            chart.isDirtyLegend = chart.isDirtyBox = true;
            if (redraw) {
               chart.redraw(animation);
            }
         });

      }
      series.isRemoving = false;
   },

   /**
    * Translate data points from raw data values to chart specific positioning data
    * needed later in drawPoints, drawGraph and drawTracker.
    */
   translate: function () {
      var series = this,
         chart = series.chart,
         stacking = series.options.stacking,
         categories = series.xAxis.categories,
         yAxis = series.yAxis,
         data = series.data,
         i = data.length;

      // do the translation
      while (i--) {
         var point = data[i],
            xValue = point.x,
            yValue = point.y,
            yBottom = point.low,
            stack = yAxis.stacks[(yValue < 0 ? '-' : '') + series.stackKey],
            pointStack,
            pointStackTotal;
         point.plotX = series.xAxis.translate(xValue);

         // calculate the bottom y value for stacked series
         if (stacking && series.visible && stack && stack[xValue]) {
            pointStack = stack[xValue];
            pointStackTotal = pointStack.total;
            pointStack.cum = yBottom = pointStack.cum - yValue; // start from top
            yValue = yBottom + yValue;

            if (stacking === 'percent') {
               yBottom = pointStackTotal ? yBottom * 100 / pointStackTotal : 0;
               yValue = pointStackTotal ? yValue * 100 / pointStackTotal : 0;
            }

            point.percentage = pointStackTotal ? point.y * 100 / pointStackTotal : 0;
            point.stackTotal = pointStackTotal;
         }

         if (defined(yBottom)) {
            point.yBottom = yAxis.translate(yBottom, 0, 1, 0, 1);
         }

         // set the y value
         if (yValue !== null) {
            point.plotY = yAxis.translate(yValue, 0, 1, 0, 1);
         }

         // set client related positions for mouse tracking
         point.clientX = chart.inverted ?
            chart.plotHeight - point.plotX :
            point.plotX; // for mouse tracking

         // some API data
         point.category = categories && categories[point.x] !== UNDEFINED ?
            categories[point.x] : point.x;

      }
   },
   /**
    * Memoize tooltip texts and positions
    */
   setTooltipPoints: function (renew) {
      var series = this,
         chart = series.chart,
         inverted = chart.inverted,
         data = [],
         plotSize = mathRound((inverted ? chart.plotTop : chart.plotLeft) + chart.plotSizeX),
         low,
         high,
         tooltipPoints = []; // a lookup array for each pixel in the x dimension

      // renew
      if (renew) {
         series.tooltipPoints = null;
      }

      // concat segments to overcome null values
      each(series.segments, function (segment) {
         data = data.concat(segment);
      });

      // loop the concatenated data and apply each point to all the closest
      // pixel positions
      if (series.xAxis && series.xAxis.reversed) {
         data = data.reverse();//reverseArray(data);
      }

      each(data, function (point, i) {

         low = data[i - 1] ? data[i - 1]._high + 1 : 0;
         high = point._high = data[i + 1] ?
            (mathFloor((point.plotX + (data[i + 1] ? data[i + 1].plotX : plotSize)) / 2)) :
            plotSize;

         while (low <= high) {
            tooltipPoints[inverted ? plotSize - low++ : low++] = point;
         }
      });
      series.tooltipPoints = tooltipPoints;
   },




   /**
    * Series mouse over handler
    */
   onMouseOver: function () {
      var series = this,
         chart = series.chart,
         hoverSeries = chart.hoverSeries;

      if (!hasTouch && chart.mouseIsDown) {
         return;
      }

      // set normal state to previous series
      if (hoverSeries && hoverSeries !== series) {
         hoverSeries.onMouseOut();
      }

      // trigger the event, but to save processing time,
      // only if defined
      if (series.options.events.mouseOver) {
         fireEvent(series, 'mouseOver');
      }


      // bring to front
      // Todo: optimize. This is one of two operations slowing down the tooltip in Firefox.
      // Can the tracking be done otherwise?
      if (series.tracker) {
         series.tracker.toFront();
      }

      // hover this
      series.setState(HOVER_STATE);
      chart.hoverSeries = series;
   },

   /**
    * Series mouse out handler
    */
   onMouseOut: function () {
      // trigger the event only if listeners exist
      var series = this,
         options = series.options,
         chart = series.chart,
         tooltip = chart.tooltip,
         hoverPoint = chart.hoverPoint;

      // trigger mouse out on the point, which must be in this series
      if (hoverPoint) {
         hoverPoint.onMouseOut();
      }

      // fire the mouse out event
      if (series && options.events.mouseOut) {
         fireEvent(series, 'mouseOut');
      }


      // hide the tooltip
      if (tooltip && !options.stickyTracking) {
         tooltip.hide();
      }

      // set normal state
      series.setState();
      chart.hoverSeries = null;
   },

   /**
    * Animate in the series
    */
   animate: function (init) {
      var series = this,
         chart = series.chart,
         clipRect = series.clipRect,
         animation = series.options.animation;

      if (animation && !isObject(animation)) {
         animation = {};
      }

      if (init) { // initialize the animation
         if (!clipRect.isAnimating) { // apply it only for one of the series
            cli
