UNPKG

@progress/kendo-ui

Version:

This package is part of the [Kendo UI for jQuery](http://www.telerik.com/kendo-ui) suite.

7,533 lines 223 kB
module.exports =
/******/ (function(modules) { // webpackBootstrap
/******/ 	// The module cache
/******/ 	var installedModules = {};

/******/ 	// The require function
/******/ 	function __webpack_require__(moduleId) {

/******/ 		// Check if module is in cache
/******/ 		if(installedModules[moduleId])
/******/ 			return installedModules[moduleId].exports;

/******/ 		// Create a new module (and put it into the cache)
/******/ 		var module = installedModules[moduleId] = {
/******/ 			exports: {},
/******/ 			id: moduleId,
/******/ 			loaded: false
/******/ 		};

/******/ 		// Execute the module function
/******/ 		modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);

/******/ 		// Flag the module as loaded
/******/ 		module.loaded = true;

/******/ 		// Return the exports of the module
/******/ 		return module.exports;
/******/ 	}


/******/ 	// expose the modules object (__webpack_modules__)
/******/ 	__webpack_require__.m = modules;

/******/ 	// expose the module cache
/******/ 	__webpack_require__.c = installedModules;

/******/ 	// __webpack_public_path__
/******/ 	__webpack_require__.p = "";

/******/ 	// Load entry module and return exports
/******/ 	return __webpack_require__(0);
/******/ })
/************************************************************************/
/******/ ({

/***/ 0:
/***/ (function(module, exports, __webpack_require__) {

	module.exports = __webpack_require__(866);


/***/ }),

/***/ 3:
/***/ (function(module, exports) {

	module.exports = function() { throw new Error("define cannot be used indirect"); };


/***/ }),

/***/ 859:
/***/ (function(module, exports) {

	module.exports = require("../../kendo.drawing");

/***/ }),

/***/ 862:
/***/ (function(module, exports) {

	module.exports = require("../../kendo.core");

/***/ }),

/***/ 866:
/***/ (function(module, exports, __webpack_require__) {

	var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/***********************************************************************
	 * WARNING: this file is auto-generated.  If you change it directly,
	 * your modifications will eventually be lost.  The source code is in
	 * `kendo-charts` repository, you should make your changes there and
	 * run `src-modules/sync.sh` in this repository.
	 */
	(function(f, define){
	    !(__WEBPACK_AMD_DEFINE_ARRAY__ = [
	        __webpack_require__(862),
	        __webpack_require__(859)
	    ], __WEBPACK_AMD_DEFINE_FACTORY__ = (f), __WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ? (__WEBPACK_AMD_DEFINE_FACTORY__.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__)) : __WEBPACK_AMD_DEFINE_FACTORY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
	})(function(){

	(function ($) {
	/* jshint curly:false */

	window.kendo.dataviz = window.kendo.dataviz || {};
	var drawing = kendo.drawing;
	var util = drawing.util;
	var Path = drawing.Path;
	var Group = drawing.Group;
	var Class = kendo.Class;
	var geometry = kendo.geometry;
	var Rect = geometry.Rect;
	var Circle = geometry.Circle;
	var geometryTransform = geometry.transform;
	var Segment = geometry.Segment;
	var dataviz = kendo.dataviz;

	var deepExtend = kendo.deepExtend;
	var isFunction = kendo.isFunction;
	var __common_getter_js = kendo.getter;

	var ARC = "arc";
	var AXIS_LABEL_CLICK = "axisLabelClick";
	var BLACK = "#000";
	var BOTTOM = "bottom";
	var CENTER = "center";
	var CIRCLE = "circle";
	var COORD_PRECISION = 3;
	var CROSS = "cross";
	var DATE = "date";
	var DEFAULT_FONT = "12px sans-serif";
	var DEFAULT_HEIGHT = 400;
	var DEFAULT_PRECISION = 10;
	var DEFAULT_WIDTH = 600;
	var END = "end";
	var FORMAT_REGEX = /\{\d+:?/;
	var HEIGHT = "height";
	var HIGHLIGHT_ZINDEX = 100;
	var INSIDE = "inside";
	var LEFT = "left";
	var MAX_VALUE = Number.MAX_VALUE;
	var MIN_VALUE = -Number.MAX_VALUE;
	var NONE = "none";
	var NOTE_CLICK = "noteClick";
	var NOTE_HOVER = "noteHover";
	var NOTE_LEAVE = "noteLeave";
	var OBJECT = "object";
	var OUTSIDE = "outside";
	var RIGHT = "right";
	var START = "start";
	var STRING = "string";
	var TOP = "top";
	var TRIANGLE = "triangle";
	var VALUE = "value";
	var WHITE = "#fff";
	var WIDTH = "width";
	var X = "x";
	var Y = "y";

	var constants = {
		ARC: ARC,
		AXIS_LABEL_CLICK: AXIS_LABEL_CLICK,
		BLACK: BLACK,
		BOTTOM: BOTTOM,
		CENTER: CENTER,
		CIRCLE: CIRCLE,
		COORD_PRECISION: COORD_PRECISION,
		CROSS: CROSS,
		DATE: DATE,
		DEFAULT_FONT: DEFAULT_FONT,
		DEFAULT_HEIGHT: DEFAULT_HEIGHT,
		DEFAULT_PRECISION: DEFAULT_PRECISION,
		DEFAULT_WIDTH: DEFAULT_WIDTH,
		END: END,
		FORMAT_REGEX: FORMAT_REGEX,
		HEIGHT: HEIGHT,
		HIGHLIGHT_ZINDEX: HIGHLIGHT_ZINDEX,
		INSIDE: INSIDE,
		LEFT: LEFT,
		MAX_VALUE: MAX_VALUE,
		MIN_VALUE: MIN_VALUE,
		NONE: NONE,
		NOTE_CLICK: NOTE_CLICK,
		NOTE_HOVER: NOTE_HOVER,
		NOTE_LEAVE: NOTE_LEAVE,
		OBJECT: OBJECT,
		OUTSIDE: OUTSIDE,
		RIGHT: RIGHT,
		START: START,
		STRING: STRING,
		TOP: TOP,
		TRIANGLE: TRIANGLE,
		VALUE: VALUE,
		WHITE: WHITE,
		WIDTH: WIDTH,
		X: X,
		Y: Y
	};

	function isArray(value) {
	    return Array.isArray(value);
	}

	function addClass(element, classes) {
	    var classArray = isArray(classes) ? classes : [ classes ];

	    for (var idx = 0; idx < classArray.length; idx++) {
	        var className = classArray[idx];
	        if (element.className.indexOf(className) === -1) {
	            element.className += " " + className;
	        }
	    }
	}

	var SPACE_REGEX = /\s+/g;

	function removeClass(element, className) {
	    if (element && element.className) {
	        element.className = element.className.replace(className, "").replace(SPACE_REGEX, " ");
	    }
	}

	function alignPathToPixel(path) {
	    var offset = 0.5;
	    if (path.options.stroke && kendo.drawing.util.defined(path.options.stroke.width)) {
	        if (path.options.stroke.width % 2 === 0) {
	            offset = 0;
	        }
	    }

	    for (var i = 0; i < path.segments.length; i++) {
	        path.segments[i].anchor().round(0).translate(offset, offset);
	    }

	    return path;
	}

	function clockwise(angle1, angle2) {
	    // True if angle2 is clockwise of angle1
	    // assuming angles grow in clock-wise direction
	    // (as in the pie and radar charts)
	    return -angle1.x * angle2.y + angle1.y * angle2.x < 0;
	}

	function isNumber(value) {
	    return typeof value === "number" && !isNaN(value);
	}

	function isString(value) {
	    return typeof value === STRING;
	}

	function convertableToNumber(value) {
	    return isNumber(value) || (isString(value) && isFinite(value));
	}

	function isObject(value) {
	    return typeof value === "object";
	}

	function styleValue(value) {
	    if (isNumber(value)) {
	        return value + "px";
	    }
	    return value;
	}

	var SIZE_STYLES_REGEX = /width|height|top|left|bottom|right/i;

	function isSizeField(field) {
	    return SIZE_STYLES_REGEX.test(field);
	}

	function elementStyles(element, styles) {
	    var stylesArray = isString(styles) ? [ styles ] : styles;

	    if (isArray(stylesArray)) {
	        var result = {};
	        var style = window.getComputedStyle(element);

	        for (var idx = 0; idx < stylesArray.length; idx++) {
	            var field = stylesArray[idx];
	            result[field] = isSizeField(field) ? parseFloat(style[field]) : style[field];
	        }

	        return result;
	    } else if (isObject(styles)) {
	        for (var field$1 in styles) {
	            element.style[field$1] = styleValue(styles[field$1]);
	        }
	    }
	}

	function getSpacing(value, defaultSpacing) {
	    if (defaultSpacing === void 0) { defaultSpacing = 0; }

	    var spacing = { top: 0, right: 0, bottom: 0, left: 0 };

	    if (typeof(value) === "number") {
	        spacing[TOP] = spacing[RIGHT] = spacing[BOTTOM] = spacing[LEFT] = value;
	    } else {
	        spacing[TOP] = value[TOP] || defaultSpacing;
	        spacing[RIGHT] = value[RIGHT] || defaultSpacing;
	        spacing[BOTTOM] = value[BOTTOM] || defaultSpacing;
	        spacing[LEFT] = value[LEFT] || defaultSpacing;
	    }

	    return spacing;
	}

	var defaultImplementation = {
	    format: function (format, value) { return value; },

	    toString: function (value) { return value; },

	    parseDate: function (value) { return new Date(value); }
	};

	var current = defaultImplementation;

	var IntlService = Class.extend({

	});

	IntlService.register = function(userImplementation) {
	    current = userImplementation;
	};

	if (Object.defineProperties) {
	    Object.defineProperties(IntlService, {
	        implementation: {
	            get: function() {
	                return current;
	            }
	        }
	    });
	}

	var FORMAT_REPLACE_REGEX = /\{(\d+)(:[^\}]+)?\}/g;

	var FormatService = Class.extend({
	    init: function(intlService) {
	        this._intlService = intlService;
	    },

	    auto: function(formatString) {
	        var values = [], len = arguments.length - 1;
	        while ( len-- > 0 ) values[ len ] = arguments[ len + 1 ];

	        var intl = this.intl;

	        if (isString(formatString) && formatString.match(FORMAT_REGEX)) {
	            return intl.format.apply(intl, [ formatString ].concat( values ));
	        }

	        return intl.toString(values[0], formatString);
	    },

	    localeAuto: function(formatString, values, locale) {
	        var intl = this.intl;
	        var result;

	        if (isString(formatString) && formatString.match(FORMAT_REGEX)) {
	            result = formatString.replace(FORMAT_REPLACE_REGEX, function(match, index, placeholderFormat) {
	                var value = values[parseInt(index, 10)];

	                return intl.toString(value, placeholderFormat ? placeholderFormat.substring(1) : "", locale);
	            });
	        } else {
	            result = intl.toString(values[0], formatString, locale);
	        }

	        return result;
	    }
	});

	if (Object.defineProperties) {
	    Object.defineProperties(FormatService.fn, {
	        intl: {
	            get: function() {
	                return this._intlService || IntlService.implementation;
	            },
	            set: function(value) {
	                this._intlService = value;
	            }
	        }
	    });
	}

	var ChartService = Class.extend({
	    init: function(chart, context) {
	        if (context === void 0) { context = {}; }

	        this._intlService = context.intlService;
	        this.sender = context.sender || chart;
	        this.format = new FormatService(context.intlService);
	        this.chart = chart;
	        this.rtl = Boolean(context.rtl);
	    },

	    notify: function(name, args) {
	        if (this.chart) {
	            this.chart.trigger(name, args);
	        }
	    },

	    isPannable: function(axis) {
	        var pannable = ((this.chart || {}).options || {}).pannable;
	        return pannable && pannable.lock !== axis;
	    }
	});

	if (Object.defineProperties) {
	    Object.defineProperties(ChartService.fn, {
	        intl: {
	            get: function() {
	                return this._intlService || IntlService.implementation;
	            },
	            set: function(value) {
	                this._intlService = value;
	                this.format.intl = value;
	            }
	        }
	    });
	}

	var current$1;

	var DomEventsBuilder = Class.extend({

	});

	DomEventsBuilder.register = function(userImplementation) {
	    current$1 = userImplementation;
	};

	DomEventsBuilder.create = function(element, events) {
	    if (current$1) {
	        return current$1.create(element, events);
	    }
	};

	var current$2 = {
	    compile: function(template) {
	        return template;
	    }
	};

	var TemplateService = Class.extend({

	});

	TemplateService.register = function(userImplementation) {
	    current$2 = userImplementation;
	};

	TemplateService.compile = function(template) {
	    return current$2.compile(template);
	};

	var services = {
		ChartService: ChartService,
		DomEventsBuilder: DomEventsBuilder,
		FormatService: FormatService,
		IntlService: IntlService,
		TemplateService: TemplateService
	};

	function getTemplate(options) {
	    if (options === void 0) { options = {}; }

	    var template;
	    if (options.template) {
	        options.template = template = TemplateService.compile(options.template);
	    } else if (isFunction(options.content)) {
	        template = options.content;
	    }

	    return template;
	}

	function grep(array, callback) {
	    var length = array.length;
	    var result = [];
	    for (var idx = 0; idx < length; idx++) {
	        if (callback(array[idx])) {
	            result .push(array[idx]);
	        }
	    }

	    return result;
	}

	function hasClasses(element, classNames) {
	    if (element.className) {
	        var names = classNames.split(" ");
	        for (var idx = 0; idx < names.length; idx++) {
	            if (element.className.indexOf(names[idx]) !== -1) {
	                return true;
	            }
	        }
	    }
	}

	var HashMap = function HashMap() {
	    this._map = {};
	};

	HashMap.prototype.get = function get (name) {
	    return this._map[this._key(name)];
	};

	HashMap.prototype.set = function set (name, value) {
	    this._map[this._key(name)] = value;
	};

	HashMap.prototype._key = function _key (name) {
	    return name instanceof Date ? name.getTime() : name;
	};

	function inArray(value, array) {
	    if (array) {
	        return array.indexOf(value) !== -1;
	    }
	}

	function interpolateValue(start, end, progress) {
	    return kendo.drawing.util.round(start + (end - start) * progress, COORD_PRECISION);
	}

	var TRIGGER = 'trigger';

	var InstanceObserver = Class.extend({
	    init: function(observer, handlers) {
	        this.observer = observer;
	        this.handlerMap = deepExtend({}, this.handlerMap, handlers);
	    },

	    trigger: function(name, args) {
	        var ref = this;
	        var observer = ref.observer;
	        var handlerMap = ref.handlerMap;
	        var isDefaultPrevented;
	        if (handlerMap[name]) {
	            isDefaultPrevented = this.callObserver(handlerMap[name], args);
	        } else if (observer[TRIGGER]) {
	            isDefaultPrevented = this.callObserver(TRIGGER, name, args);
	        }

	        return isDefaultPrevented;
	    },

	    callObserver: function(fnName) {
	        var args = [], len = arguments.length - 1;
	        while ( len-- > 0 ) args[ len ] = arguments[ len + 1 ];

	        return this.observer[fnName].apply(this.observer, args);
	    },

	    requiresHandlers: function(names) {
	        var this$1 = this;

	        if (this.observer.requiresHandlers) {
	            return this.observer.requiresHandlers(names);
	        }

	        for (var idx = 0; idx < names.length; idx++) {
	            if (this$1.handlerMap[names[idx]]) {
	                return true;
	            }
	        }
	    }
	});

	function map(array, callback) {
	    var length = array.length;
	    var result = [];
	    for (var idx = 0; idx < length; idx++) {
	        var value = callback(array[idx]);
	        if (kendo.drawing.util.defined(value)) {
	            result.push(value);
	        }
	    }
	    return result;
	}

	function mousewheelDelta(e) {
	    var delta = 0;

	    if (e.wheelDelta) {
	        delta = -e.wheelDelta / 120;
	        delta = delta > 0 ? Math.ceil(delta) : Math.floor(delta);
	    }

	    if (e.detail) {
	        delta = kendo.drawing.util.round(e.detail / 3);
	    }

	    return delta;
	}

	var ref = kendo.drawing.util;
	var append = ref.append;
	var bindEvents = ref.bindEvents;
	var defined = ref.defined;
	var deg = ref.deg;
	var elementOffset = ref.elementOffset;
	var elementSize = ref.elementSize;
	var eventElement = ref.eventElement;
	var eventCoordinates = ref.eventCoordinates;
	var last = ref.last;
	var limitValue = ref.limitValue;
	var objectKey = ref.objectKey;
	var rad = ref.rad;
	var round = ref.round;
	var unbindEvents = ref.unbindEvents;
	var valueOrDefault = ref.valueOrDefault;

	var FontLoader = Class.extend({

	});

	FontLoader.fetchFonts = function(options, fonts, state) {
	    if (state === void 0) { state = { depth: 0 }; }

	    var MAX_DEPTH = 5;

	    if (!options || state.depth > MAX_DEPTH || !document.fonts) {
	        return;
	    }

	    Object.keys(options).forEach(function(key) {
	        var value = options[key];
	        if (key === "dataSource" || key[0] === "$" || !value) {
	            return;
	        }

	        if (key === "font") {
	            fonts.push(value);
	        } else if (typeof value === "object") {
	            state.depth++;
	            FontLoader.fetchFonts(value, fonts, state);
	            state.depth--;
	        }
	    });
	};

	FontLoader.loadFonts = function(fonts, callback) {
	    var promises = [];

	    if (fonts.length > 0 && document.fonts) {
	        try {
	            promises = fonts.map(function(font) {
	                return document.fonts.load(font);
	            });
	        } catch (e) {
	            // Silence font-loading errors
	            kendo.logToConsole(e);
	        }

	        Promise.all(promises).then(callback, callback);
	    } else {
	        callback();
	    }
	};

	FontLoader.preloadFonts = function(options, callback) {
	    var fonts = [];
	    FontLoader.fetchFonts(options, fonts);

	    FontLoader.loadFonts(fonts, callback);
	};

	function setDefaultOptions(type, options) {
	    var proto = type.prototype;
	    if (proto.options) {
	        proto.options = deepExtend({}, proto.options, options);
	    } else {
	        proto.options = options;
	    }
	}

	function sparseArrayLimits(arr) {
	    var min = MAX_VALUE;
	    var max = MIN_VALUE;

	    for (var idx = 0, length = arr.length; idx < length; idx++) {
	        var value = arr[idx];
	        if (value !== null && isFinite(value)) {
	            min = Math.min(min, value);
	            max = Math.max(max, value);
	        }
	    }

	    return {
	        min: min === MAX_VALUE ? undefined : min,
	        max: max === MIN_VALUE ? undefined : max
	    };
	}

	function autoMajorUnit(min, max) {
	    var diff = round(max - min, DEFAULT_PRECISION - 1);

	    if (diff === 0) {
	        if (max === 0) {
	            return 0.1;
	        }

	        diff = Math.abs(max);
	    }

	    var scale = Math.pow(10, Math.floor(Math.log(diff) / Math.log(10)));
	    var relativeValue = round((diff / scale), DEFAULT_PRECISION);
	    var scaleMultiplier = 1;

	    if (relativeValue < 1.904762) {
	        scaleMultiplier = 0.2;
	    } else if (relativeValue < 4.761904) {
	        scaleMultiplier = 0.5;
	    } else if (relativeValue < 9.523809) {
	        scaleMultiplier = 1;
	    } else {
	        scaleMultiplier = 2;
	    }

	    return round(scale * scaleMultiplier, DEFAULT_PRECISION);
	}

	var Point = Class.extend({
	    init: function(x, y) {

	        this.x = x || 0;
	        this.y = y || 0;
	    },

	    clone: function() {
	        return new Point(this.x, this.y);
	    },

	    equals: function(point) {
	        return point && this.x === point.x && this.y === point.y;
	    },

	    rotate: function(center, degrees) {
	        var theta = rad(degrees);
	        var cosT = Math.cos(theta);
	        var sinT = Math.sin(theta);
	        var cx = center.x;
	        var cy = center.y;
	        var ref = this;
	        var x = ref.x;
	        var y = ref.y;

	        this.x = round(
	            cx + (x - cx) * cosT + (y - cy) * sinT,
	            COORD_PRECISION
	        );

	        this.y = round(
	            cy + (y - cy) * cosT - (x - cx) * sinT,
	            COORD_PRECISION
	        );

	        return this;
	    },

	    multiply: function(a) {

	        this.x *= a;
	        this.y *= a;

	        return this;
	    },

	    distanceTo: function(point) {
	        var dx = this.x - point.x;
	        var dy = this.y - point.y;

	        return Math.sqrt(dx * dx + dy * dy);
	    }
	});

	Point.onCircle = function(center, angle, radius) {
	    var radians = rad(angle);

	    return new Point(
	        center.x - radius * Math.cos(radians),
	        center.y - radius * Math.sin(radians)
	    );
	};

	var Box = Class.extend({
	    init: function(x1, y1, x2, y2) {

	        this.x1 = x1 || 0;
	        this.y1 = y1 || 0;
	        this.x2 = x2 || 0;
	        this.y2 = y2 || 0;
	    },

	    equals: function(box) {
	        return this.x1 === box.x1 && this.x2 === box.x2 &&
	            this.y1 === box.y1 && this.y2 === box.y2;
	    },

	    width: function() {
	        return this.x2 - this.x1;
	    },

	    height: function() {
	        return this.y2 - this.y1;
	    },

	    translate: function(dx, dy) {
	        this.x1 += dx;
	        this.x2 += dx;
	        this.y1 += dy;
	        this.y2 += dy;

	        return this;
	    },

	    move: function(x, y) {
	        var height = this.height();
	        var width = this.width();

	        if (defined(x)) {
	            this.x1 = x;
	            this.x2 = this.x1 + width;
	        }

	        if (defined(y)) {
	            this.y1 = y;
	            this.y2 = this.y1 + height;
	        }

	        return this;
	    },

	    wrap: function(targetBox) {
	        this.x1 = Math.min(this.x1, targetBox.x1);
	        this.y1 = Math.min(this.y1, targetBox.y1);
	        this.x2 = Math.max(this.x2, targetBox.x2);
	        this.y2 = Math.max(this.y2, targetBox.y2);

	        return this;
	    },

	    wrapPoint: function(point) {
	        var arrayPoint = isArray(point);
	        var x = arrayPoint ? point[0] : point.x;
	        var y = arrayPoint ? point[1] : point.y;
	        this.wrap(new Box(x, y, x, y));

	        return this;
	    },

	    snapTo: function(targetBox, axis) {

	        if (axis === X || !axis) {
	            this.x1 = targetBox.x1;
	            this.x2 = targetBox.x2;
	        }

	        if (axis === Y || !axis) {
	            this.y1 = targetBox.y1;
	            this.y2 = targetBox.y2;
	        }

	        return this;
	    },

	    alignTo: function(targetBox, anchor) {
	        var height = this.height();
	        var width = this.width();
	        var axis = anchor === TOP || anchor === BOTTOM ? Y : X;
	        var offset = axis === Y ? height : width;

	        if (anchor === CENTER) {
	            var targetCenter = targetBox.center();
	            var center = this.center();

	            this.x1 += targetCenter.x - center.x;
	            this.y1 += targetCenter.y - center.y;
	        } else if (anchor === TOP || anchor === LEFT) {
	            this[axis + 1] = targetBox[axis + 1] - offset;
	        } else {
	            this[axis + 1] = targetBox[axis + 2];
	        }

	        this.x2 = this.x1 + width;
	        this.y2 = this.y1 + height;

	        return this;
	    },

	    shrink: function(dw, dh) {

	        this.x2 -= dw;
	        this.y2 -= dh;

	        return this;
	    },

	    expand: function(dw, dh) {
	        this.shrink(-dw, -dh);
	        return this;
	    },

	    pad: function(padding) {
	        var spacing = getSpacing(padding);

	        this.x1 -= spacing.left;
	        this.x2 += spacing.right;
	        this.y1 -= spacing.top;
	        this.y2 += spacing.bottom;

	        return this;
	    },

	    unpad: function(padding) {
	        var spacing = getSpacing(padding);

	        spacing.left = -spacing.left;
	        spacing.top = -spacing.top;
	        spacing.right = -spacing.right;
	        spacing.bottom = -spacing.bottom;

	        return this.pad(spacing);
	    },

	    clone: function() {
	        return new Box(this.x1, this.y1, this.x2, this.y2);
	    },

	    center: function() {
	        return new Point(
	            this.x1 + this.width() / 2,
	            this.y1 + this.height() / 2
	        );
	    },

	    containsPoint: function(point) {

	        return point.x >= this.x1 && point.x <= this.x2 &&
	               point.y >= this.y1 && point.y <= this.y2;
	    },

	    points: function() {
	        return [
	            new Point(this.x1, this.y1),
	            new Point(this.x2, this.y1),
	            new Point(this.x2, this.y2),
	            new Point(this.x1, this.y2)
	        ];
	    },

	    getHash: function() {
	        return [ this.x1, this.y1, this.x2, this.y2 ].join(",");
	    },

	    overlaps: function(box) {
	        return !(box.y2 < this.y1 || this.y2 < box.y1 || box.x2 < this.x1 || this.x2 < box.x1);
	    },

	    rotate: function(rotation) {
	        var width = this.width();
	        var height = this.height();
	        var ref = this.center();
	        var cx = ref.x;
	        var cy = ref.y;

	        var r1 = rotatePoint(0, 0, cx, cy, rotation);
	        var r2 = rotatePoint(width, 0, cx, cy, rotation);
	        var r3 = rotatePoint(width, height, cx, cy, rotation);
	        var r4 = rotatePoint(0, height, cx, cy, rotation);

	        width = Math.max(r1.x, r2.x, r3.x, r4.x) - Math.min(r1.x, r2.x, r3.x, r4.x);
	        height = Math.max(r1.y, r2.y, r3.y, r4.y) - Math.min(r1.y, r2.y, r3.y, r4.y);

	        this.x2 = this.x1 + width;
	        this.y2 = this.y1 + height;

	        return this;
	    },

	    toRect: function() {
	        return new Rect([ this.x1, this.y1 ], [ this.width(), this.height() ]);
	    },

	    hasSize: function() {
	        return this.width() !== 0 && this.height() !== 0;
	    },

	    align: function(targetBox, axis, alignment) {
	        var c1 = axis + 1;
	        var c2 = axis + 2;
	        var sizeFunc = axis === X ? WIDTH : HEIGHT;
	        var size = this[sizeFunc]();

	        if (inArray(alignment, [ LEFT, TOP ])) {
	            this[c1] = targetBox[c1];
	            this[c2] = this[c1] + size;
	        } else if (inArray(alignment, [ RIGHT, BOTTOM ])) {
	            this[c2] = targetBox[c2];
	            this[c1] = this[c2] - size;
	        } else if (alignment === CENTER) {
	            this[c1] = targetBox[c1] + (targetBox[sizeFunc]() - size) / 2;
	            this[c2] = this[c1] + size;
	        }
	    }
	});

	function rotatePoint(x, y, cx, cy, angle) {
	    var theta = rad(angle);

	    return new Point(
	        cx + (x - cx) * Math.cos(theta) + (y - cy) * Math.sin(theta),
	        cy - (x - cx) * Math.sin(theta) + (y - cy) * Math.cos(theta)
	    );
	}

	var Ring = Class.extend({
	    init: function(center, innerRadius, radius, startAngle, angle) {

	        this.center = center;
	        this.innerRadius = innerRadius;
	        this.radius = radius;
	        this.startAngle = startAngle;
	        this.angle = angle;
	    },

	    clone: function() {
	        return new Ring(this.center, this.innerRadius, this.radius, this.startAngle, this.angle);
	    },

	    middle: function() {
	        return this.startAngle + this.angle / 2;
	    },

	    setRadius: function(newRadius, innerRadius) {
	        if (innerRadius) {
	            this.innerRadius = newRadius;
	        } else {
	            this.radius = newRadius;
	        }

	        return this;
	    },

	    point: function(angle, innerRadius) {
	        var radianAngle = rad(angle);
	        var ax = Math.cos(radianAngle);
	        var ay = Math.sin(radianAngle);
	        var radius = innerRadius ? this.innerRadius : this.radius;
	        var x = round(this.center.x - (ax * radius), COORD_PRECISION);
	        var y = round(this.center.y - (ay * radius), COORD_PRECISION);

	        return new Point(x, y);
	    },

	    adjacentBox: function(distance, width, height) {
	        var sector = this.clone().expand(distance);
	        var midAndle = sector.middle();
	        var midPoint = sector.point(midAndle);
	        var hw = width / 2;
	        var hh = height / 2;
	        var sa = Math.sin(rad(midAndle));
	        var ca = Math.cos(rad(midAndle));
	        var x = midPoint.x - hw;
	        var y = midPoint.y - hh;

	        if (Math.abs(sa) < 0.9) {
	            x += hw * -ca / Math.abs(ca);
	        }

	        if (Math.abs(ca) < 0.9) {
	            y += hh * -sa / Math.abs(sa);
	        }

	        return new Box(x, y, x + width, y + height);
	    },

	    containsPoint: function(p) {
	        var center = this.center;
	        var innerRadius = this.innerRadius;
	        var radius = this.radius;
	        var startAngle = this.startAngle;
	        var endAngle = this.startAngle + this.angle;
	        var dx = p.x - center.x;
	        var dy = p.y - center.y;
	        var vector = new Point(dx, dy);
	        var startPoint = this.point(startAngle);
	        var startVector = new Point(startPoint.x - center.x, startPoint.y - center.y);
	        var endPoint = this.point(endAngle);
	        var endVector = new Point(endPoint.x - center.x, endPoint.y - center.y);
	        var dist = round(dx * dx + dy * dy, COORD_PRECISION);

	        return (startVector.equals(vector) || clockwise(startVector, vector)) &&
	               !clockwise(endVector, vector) &&
	               dist >= innerRadius * innerRadius && dist <= radius * radius;
	    },

	    getBBox: function() {
	        var this$1 = this;

	        var box = new Box(MAX_VALUE, MAX_VALUE, MIN_VALUE, MIN_VALUE);
	        var startAngle = round(this.startAngle % 360);
	        var endAngle = round((startAngle + this.angle) % 360);
	        var innerRadius = this.innerRadius;
	        var allAngles = [ 0, 90, 180, 270, startAngle, endAngle ].sort(numericComparer);
	        var startAngleIndex = allAngles.indexOf(startAngle);
	        var endAngleIndex = allAngles.indexOf(endAngle);
	        var angles;

	        if (startAngle === endAngle) {
	            angles = allAngles;
	        } else {
	            if (startAngleIndex < endAngleIndex) {
	                angles = allAngles.slice(startAngleIndex, endAngleIndex + 1);
	            } else {
	                angles = [].concat(
	                    allAngles.slice(0, endAngleIndex + 1),
	                    allAngles.slice(startAngleIndex, allAngles.length)
	                );
	            }
	        }

	        for (var i = 0; i < angles.length; i++) {
	            var point = this$1.point(angles[i]);
	            box.wrapPoint(point);
	            box.wrapPoint(point, innerRadius);
	        }

	        if (!innerRadius) {
	            box.wrapPoint(this.center);
	        }

	        return box;
	    },

	    expand: function(value) {
	        this.radius += value;
	        return this;
	    }
	});

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

	var Sector = Ring.extend({
	    init: function(center, radius, startAngle, angle) {
	        Ring.fn.init.call(this, center, 0, radius, startAngle, angle);
	    },

	    expand: function(value) {
	        return Ring.fn.expand.call(this, value);
	    },

	    clone: function() {
	        return new Sector(this.center, this.radius, this.startAngle, this.angle);
	    },

	    setRadius: function(newRadius) {
	        this.radius = newRadius;

	        return this;
	    }
	});

	var DIRECTION_ANGLE = 0.001; //any value that will make the endAngle bigger than the start angle will work here.

	var ShapeBuilder = Class.extend({
	    createRing: function(sector, options) {
	        var startAngle = sector.startAngle + 180;
	        var endAngle = sector.angle + startAngle;

	        //required in order to avoid reversing the arc direction in cases like 0.000000000000001 + 100 === 100
	        if (sector.angle > 0 && startAngle === endAngle) {
	            endAngle += DIRECTION_ANGLE;
	        }

	        var center = new geometry.Point(sector.center.x, sector.center.y);
	        var radius = Math.max(sector.radius, 0);
	        var innerRadius = Math.max(sector.innerRadius, 0);
	        var arc = new geometry.Arc(center, {
	            startAngle: startAngle,
	            endAngle: endAngle,
	            radiusX: radius,
	            radiusY: radius
	        });
	        var path = Path.fromArc(arc, options).close();

	        if (innerRadius) {
	            arc.radiusX = arc.radiusY = innerRadius;
	            var innerEnd = arc.pointAt(endAngle);
	            path.lineTo(innerEnd.x, innerEnd.y);
	            path.arc(endAngle, startAngle, innerRadius, innerRadius, true);
	        } else {
	            path.lineTo(center.x, center.y);
	        }

	        return path;
	    }
	});

	ShapeBuilder.current = new ShapeBuilder();

	var ChartElement = Class.extend({
	    init: function(options) {

	        this.children = [];

	        this.options = deepExtend({}, this.options, this.initUserOptions(options));
	    },

	    initUserOptions: function(options) {
	        return options;
	    },

	    reflow: function(targetBox) {
	        var children = this.children;
	        var box;

	        for (var i = 0; i < children.length; i++) {
	            var currentChild = children[i];
	            currentChild.reflow(targetBox);

	            box = box ? box.wrap(currentChild.box) : currentChild.box.clone();
	        }

	        this.box = box || targetBox;
	    },

	    destroy: function() {
	        var children = this.children;

	        if (this.animation) {
	            this.animation.destroy();
	        }

	        for (var i = 0; i < children.length; i++) {
	            children[i].destroy();
	        }
	    },

	    getRoot: function() {
	        var parent = this.parent;

	        return parent ? parent.getRoot() : null;
	    },

	    getSender: function() {
	        var service = this.getService();
	        if (service) {
	            return service.sender;
	        }
	    },

	    getService: function() {
	        var element = this;
	        while (element) {
	            if (element.chartService) {
	                return element.chartService;
	            }
	            element = element.parent;
	        }
	    },

	    translateChildren: function(dx, dy) {
	        var children = this.children;
	        var childrenCount = children.length;

	        for (var i = 0; i < childrenCount; i++) {
	            children[i].box.translate(dx, dy);
	        }
	    },

	    append: function() {
	        var arguments$1 = arguments;
	        var this$1 = this;

	        for (var i = 0; i < arguments.length; i++) {
	            var item = arguments$1[i];
	            this$1.children.push(item);
	            item.parent = this$1;
	        }
	    },

	    renderVisual: function() {
	        if (this.options.visible === false) {
	            return;
	        }

	        this.createVisual();

	        this.addVisual();

	        this.renderChildren();

	        this.createAnimation();
	        this.renderComplete();
	    },

	    addVisual: function() {
	        if (this.visual) {
	            this.visual.chartElement = this;

	            if (this.parent) {
	                this.parent.appendVisual(this.visual);
	            }
	        }
	    },

	    renderChildren: function() {
	        var children = this.children;
	        var length = children.length;
	        for (var i = 0; i < length; i++) {
	            children[i].renderVisual();
	        }
	    },

	    createVisual: function() {
	        this.visual = new Group({
	            zIndex: this.options.zIndex,
	            visible: valueOrDefault(this.options.visible, true)
	        });
	    },

	    createAnimation: function() {
	        if (this.visual && this.options.animation) {
	            this.animation = drawing.Animation.create(
	                this.visual, this.options.animation
	            );
	        }
	    },

	    appendVisual: function(childVisual) {
	        if (!childVisual.chartElement) {
	            childVisual.chartElement = this;
	        }

	        if (childVisual.options.noclip) {
	            this.clipRoot().visual.append(childVisual);
	        } else if (defined(childVisual.options.zIndex)) {
	            this.stackRoot().stackVisual(childVisual);
	        } else if (this.isStackRoot) {
	            this.stackVisual(childVisual);
	        } else if (this.visual) {
	            this.visual.append(childVisual);
	        } else {
	            // Allow chart elements without visuals to
	            // pass through child visuals
	            this.parent.appendVisual(childVisual);
	        }
	    },

	    clipRoot: function() {
	        if (this.parent) {
	            return this.parent.clipRoot();
	        }

	        return this;
	    },

	    stackRoot: function() {
	        if (this.parent) {
	            return this.parent.stackRoot();
	        }

	        return this;
	    },

	    stackVisual: function(childVisual) {
	        var zIndex = childVisual.options.zIndex || 0;
	        var visuals = this.visual.children;
	        var length = visuals.length;
	        var pos;

	        for (pos = 0; pos < length; pos++) {
	            var sibling = visuals[pos];
	            var here = valueOrDefault(sibling.options.zIndex, 0);
	            if (here > zIndex) {
	                break;
	            }
	        }

	        this.visual.insert(pos, childVisual);
	    },

	    traverse: function(callback) {
	        var children = this.children;
	        var length = children.length;

	        for (var i = 0; i < length; i++) {
	            var child = children[i];

	            callback(child);
	            if (child.traverse) {
	                child.traverse(callback);
	            }
	        }
	    },

	    closest: function(match) {
	        var element = this;
	        var matched = false;

	        while (element && !matched) {
	            matched = match(element);

	            if (!matched) {
	                element = element.parent;
	            }
	        }

	        if (matched) {
	            return element;
	        }
	    },

	    renderComplete: function() {},

	    hasHighlight: function() {
	        var options = (this.options || {}).highlight;
	        return !(!this.createHighlight || (options && options.visible === false));
	    },

	    toggleHighlight: function(show) {
	        var this$1 = this;

	        var options = (this.options || {}).highlight || {};
	        var customVisual = options.visual;
	        var highlight = this._highlight;

	        if (!highlight) {
	            var highlightOptions = {
	                fill: {
	                    color: WHITE,
	                    opacity: 0.2
	                },
	                stroke: {
	                    color: WHITE,
	                    width: 1,
	                    opacity: 0.2
	                }
	            };

	            if (customVisual) {
	                highlight = this._highlight = customVisual(
	                    $.extend(this.highlightVisualArgs(), {
	                        createVisual: function () { return this$1.createHighlight(highlightOptions); },
	                        sender: this.getSender(),
	                        series: this.series,
	                        dataItem: this.dataItem,
	                        category: this.category,
	                        value: this.value,
	                        percentage: this.percentage,
	                        runningTotal: this.runningTotal,
	                        total: this.total
	                    }
	                ));

	                if (!highlight) {
	                    return;
	                }
	            } else {
	                highlight = this._highlight = this.createHighlight(highlightOptions);
	            }

	            if (!defined(highlight.options.zIndex)) {
	                highlight.options.zIndex = valueOrDefault(options.zIndex, this.options.zIndex);
	            }

	            this.appendVisual(highlight);
	        }

	        highlight.visible(show);
	    },

	    createGradientOverlay: function(element, options, gradientOptions) {
	        var overlay = new Path($.extend({
	            stroke: {
	                color: "none"
	            },
	            fill: this.createGradient(gradientOptions),
	            closed: element.options.closed
	        }, options));

	        overlay.segments.elements(element.segments.elements());

	        return overlay;
	    },

	    createGradient: function(options) {
	        if (this.parent) {
	            return this.parent.createGradient(options);
	        }
	    }
	});

	ChartElement.prototype.options = { };

	var BoxElement = ChartElement.extend({
	    init: function(options) {
	        ChartElement.fn.init.call(this, options);

	        this.options.margin = getSpacing(this.options.margin);
	        this.options.padding = getSpacing(this.options.padding);
	    },

	    reflow: function(targetBox) {
	        var this$1 = this;

	        var options = this.options;
	        var width = options.width;
	        var height = options.height;
	        var shrinkToFit = options.shrinkToFit;
	        var hasSetSize = width && height;
	        var margin = options.margin;
	        var padding = options.padding;
	        var borderWidth = options.border.width;
	        var box;

	        var reflowPaddingBox = function () {
	            this$1.align(targetBox, X, options.align);
	            this$1.align(targetBox, Y, options.vAlign);
	            this$1.paddingBox = box.clone().unpad(margin).unpad(borderWidth);
	        };

	        var contentBox = targetBox.clone();
	        if (hasSetSize) {
	            contentBox.x2 = contentBox.x1 + width;
	            contentBox.y2 = contentBox.y1 + height;
	        }

	        if (shrinkToFit) {
	            contentBox.unpad(margin).unpad(borderWidth).unpad(padding);
	        }

	        ChartElement.fn.reflow.call(this, contentBox);

	        if (hasSetSize) {
	            box = this.box = new Box(0, 0, width, height);
	        } else {
	            box = this.box;
	        }

	        if (shrinkToFit && hasSetSize) {
	            reflowPaddingBox();
	            contentBox = this.contentBox = this.paddingBox.clone().unpad(padding);
	        } else {
	            contentBox = this.contentBox = box.clone();
	            box.pad(padding).pad(borderWidth).pad(margin);
	            reflowPaddingBox();
	        }

	        this.translateChildren(
	            box.x1 - contentBox.x1 + margin.left + borderWidth + padding.left,
	            box.y1 - contentBox.y1 + margin.top + borderWidth + padding.top
	        );

	        var children = this.children;
	        for (var i = 0; i < children.length; i++) {
	            var item = children[i];
	            item.reflow(item.box);
	        }
	    },

	    align: function(targetBox, axis, alignment) {
	        this.box.align(targetBox, axis, alignment);
	    },

	    hasBox: function() {
	        var options = this.options;
	        return options.border.width || options.background;
	    },

	    createVisual: function() {
	        ChartElement.fn.createVisual.call(this);

	        var options = this.options;
	        if (options.visible && this.hasBox()) {
	            this.visual.append(Path.fromRect(
	                this.paddingBox.toRect(),
	                this.visualStyle()
	            ));
	        }
	    },

	    visualStyle: function() {
	        var options = this.options;
	        var border = options.border || {};

	        return {
	            stroke: {
	                width: border.width,
	                color: border.color,
	                opacity: valueOrDefault(border.opacity, options.opacity),
	                dashType: border.dashType
	            },
	            fill: {
	                color: options.background,
	                opacity: options.opacity
	            },
	            cursor: options.cursor
	        };
	    }
	});

	setDefaultOptions(BoxElement, {
	    align: LEFT,
	    vAlign: TOP,
	    margin: {},
	    padding: {},
	    border: {
	        color: BLACK,
	        width: 0
	    },
	    background: "",
	    shrinkToFit: false,
	    width: 0,
	    height: 0,
	    visible: true
	});

	var ShapeElement = BoxElement.extend({
	    init: function(options, pointData) {
	        BoxElement.fn.init.call(this, options);

	        this.pointData = pointData;
	    },

	    getElement: function() {
	        var ref = this;
	        var options = ref.options;
	        var box = ref.paddingBox;
	        var type = options.type;
	        var rotation = options.rotation;
	        var center = box.center();
	        var halfWidth = box.width() / 2;

	        if (!options.visible || !this.hasBox()) {
	            return null;
	        }

	        var style = this.visualStyle();
	        var element;

	        if (type === CIRCLE) {
	            element = new drawing.Circle(
	                new Circle([
	                    round(box.x1 + halfWidth, COORD_PRECISION),
	                    round(box.y1 + box.height() / 2, COORD_PRECISION)
	                ], halfWidth),
	                style
	            );
	        } else if (type === TRIANGLE) {
	            element = Path.fromPoints([
	                [ box.x1 + halfWidth, box.y1 ],
	                [ box.x1, box.y2 ],
	                [ box.x2, box.y2 ]
	            ], style).close();
	        } else if (type === CROSS) {
	            element = new drawing.MultiPath(style);

	            element.moveTo(box.x1, box.y1).lineTo(box.x2, box.y2);
	            element.moveTo(box.x1, box.y2).lineTo(box.x2, box.y1);
	        } else {
	            element = Path.fromRect(box.toRect(), style);
	        }

	        if (rotation) {
	            element.transform(geometryTransform()
	                .rotate(-rotation, [ center.x, center.y ])
	            );
	        }

	        element.options.zIndex = options.zIndex;
	        return element;
	    },

	    createElement: function() {
	        var this$1 = this;

	        var customVisual = this.options.visual;
	        var pointData = this.pointData || {};
	        var visual;

	        if (customVisual) {
	            visual = customVisual({
	                value: pointData.value,
	                dataItem: pointData.dataItem,
	                sender: this.getSender(),
	                series: pointData.series,
	                category: pointData.category,
	                rect: this.paddingBox.toRect(),
	                options: this.visualOptions(),
	                createVisual: function () { return this$1.getElement(); }
	            });
	        } else {
	            visual = this.getElement();
	        }

	        return visual;
	    },

	    visualOptions: function() {
	        var options = this.options;
	        return {
	            background: options.background,
	            border: options.border,
	            margin: options.margin,
	            padding: options.padding,
	            type: options.type,
	            size: options.width,
	            visible: options.visible
	        };
	    },

	    createVisual: function() {
	        this.visual = this.createElement();
	    }
	});

	setDefaultOptions(ShapeElement, {
	    type: CIRCLE,
	    align: CENTER,
	    vAlign: CENTER
	});

	var LINEAR = "linear";
	var RADIAL = "radial";

	var GRADIENTS = {
	    glass: {
	        type: LINEAR,
	        rotation: 0,
	        stops: [ {
	            offset: 0,
	            color: WHITE,
	            opacity: 0
	        }, {
	            offset: 0.25,
	            color: WHITE,
	            opacity: 0.3
	        }, {
	            offset: 1,
	            color: WHITE,
	            opacity: 0
	        } ]
	    },
	    sharpBevel: {
	        type: RADIAL,
	        stops: [ {
	            offset: 0,
	            color: WHITE,
	            opacity: 0.55
	        }, {
	            offset: 0.65,
	            color: WHITE,
	            opacity: 0
	        }, {
	            offset: 0.95,
	            color: WHITE,
	            opacity: 0.25
	        } ]
	    },
	    roundedBevel: {
	        type: RADIAL,
	        stops: [ {
	            offset: 0.33,
	            color: WHITE,
	            opacity: 0.06
	        }, {
	            offset: 0.83,
	            color: WHITE,
	            opacity: 0.2
	        }, {
	            offset: 0.95,
	            color: WHITE,
	            opacity: 0
	        } ]
	    },
	    roundedGlass: {
	        type: RADIAL,
	        supportVML: false,
	        stops: [ {
	            offset: 0,
	            color: WHITE,
	            opacity: 0
	        }, {
	            offset: 0.5,
	            color: WHITE,
	            opacity: 0.3
	        }, {
	            offset: 0.99,
	            color: WHITE,
	            opacity: 0
	        } ]
	    },
	    sharpGlass: {
	        type: RADIAL,
	        supportVML: false,
	        stops: [ {
	            offset: 0,
	            color: WHITE,
	            opacity: 0.2
	        }, {
	            offset: 0.15,
	            color: WHITE,
	            opacity: 0.15
	        }, {
	            offset: 0.17,
	            color: WHITE,
	            opacity: 0.35
	        }, {
	            offset: 0.85,
	            color: WHITE,
	            opacity: 0.05
	        }, {
	            offset: 0.87,
	            color: WHITE,
	            opacity: 0.15
	        }, {
	            offset: 0.99,
	            color: WHITE,
	            opacity: 0
	        } ]
	    },
	    bubbleShadow: {
	        type: RADIAL,
	        center: [ 0.5, 0.5 ],
	        radius: 0.5
	    }
	};

	function boxDiff(r, s) {
	    if (r.x1 === s.x1 && r.y1 === s.y1 && r.x2 === s.x2 && r.y2 === s.y2) {
	        return s;
	    }

	    var a = Math.min(r.x1, s.x1);
	    var b = Math.max(r.x1, s.x1);
	    var c = Math.min(r.x2, s.x2);
	    var d = Math.max(r.x2, s.x2);
	    var e = Math.min(r.y1, s.y1);
	    var f = Math.max(r.y1, s.y1);
	    var g = Math.min(r.y2, s.y2);
	    var h = Math.max(r.y2, s.y2);
	    var boxes = [];

	    // X = intersection, 0-7 = possible difference areas
	    // h +-+-+-+
	    // . |5|6|7|
	    // g +-+-+-+
	    // . |3|X|4|
	    // f +-+-+-+
	    // . |0|1|2|
	    // e +-+-+-+
	    // . a b c d

	    // we'll always have rectangles 1, 3, 4 and 6
	    boxes[0] = new Box(b, e, c, f);
	    boxes[1] = new Box(a, f, b, g);
	    boxes[2] = new Box(c, f, d, g);
	    boxes[3] = new Box(b, g, c, h);

	    // decide which corners
	    if (r.x1 === a && r.y1 === e || s.x1 === a && s.y1 === e) { // corners 0 and 7
	        boxes[4] = new Box(a, e, b, f);
	        boxes[5] = new Box(c, g, d, h);
	    } else { // corners 2 and 5
	        boxes[4] = new Box(c, e, d, f);
	        boxes[5] = new Box(a, g, b, h);
	    }

	    return grep(boxes, function(box) {
	        return box.height() > 0 && box.width() > 0;
	    })[0];
	}

	var RootElement = ChartElement.extend({
	    init: function(options) {
	        ChartElement.fn.init.call(this, options);

	        var rootOptions = this.options;
	        rootOptions.width = parseInt(rootOptions.width, 10);
	        rootOptions.height = parseInt(rootOptions.height, 10);

	        this.gradients = {};
	    },

	    reflow: function() {
	        var ref = this;
	        var options = ref.options;
	        var children = ref.children;
	        var currentBox = new Box(0, 0, options.width, options.height);

	        this.box = currentBox.unpad(options.margin);

	        for (var i = 0; i < children.length; i++) {
	            children[i].reflow(currentBox);
	            currentBox = boxDiff(currentBox, children[i].box) || new Box();
	        }
	    },

	    createVisual: function() {
	        this.visual = new Group();
	        this.createBackground();
	    },

	    createBackground: function() {
	        var options = this.options;
	        var border = options.border || {};
	        var box = this.box.clone().pad(options.margin).unpad(border.width);

	        var background = Path.fromRect(box.toRect(), {
	            stroke: {
	                color: border.width ? border.color : "",
	                width: border.width,
	                dashType: border.dashType
	            },
	            fill: {
	                color: options.background,
	                opacity: options.opacity
	            },
	            zIndex: -10
	        });

	        this.visual.append(background);
	    },

	    getRoot: function() {
	        return this;
	    },

	    createGradient: function(options) {
	        var gradients = this.gradients;
	        var hashCode = objectKey(options);
	        var gradient = GRADIENTS[options.gradient];
	        var drawingGradient;

	        if (gradients[hashCode]) {
	            drawingGradient = gradients[hashCode];
	        } else {
	            var gradientOptions = $.extend({}, gradient, options);
	            if (gradient.type === "linear") {
	                drawingGradient = new drawing.LinearGradient(gradientOptions);
	            } else {
	                if (options.innerRadius) {
	                    gradientOptions.stops = innerRadialStops(gradientOptions);
	                }
	                drawingGradient = new drawing.RadialGradient(gradientOptions);
	                drawingGradient.supportVML = gradient.supportVML !== false;
	            }
	            gradients[hashCode] = drawingGradient;
	        }

	        return drawingGradient;
	    },

	    cleanGradients: function() {
	        var gradients = this.gradients;
	        for (var hashCode in gradients) {
	            gradients[hashCode]._observers = [];//add clear observers method in drawing ObserversMixin
	        }
	    },

	    size: function() {
	        var options = this.options;
	        return new Box(0, 0, options.width, options.height);
	    }
	});

	setDefaultOptions(RootElement, {
	    width: DEFAULT_WIDTH,
	    height: DEFAULT_HEIGHT,
	    background: WHITE,
	    border: {
	        color: BLACK,
	        width: 0
	    },
	    margin: getSpacing(5),
	    zIndex: -2
	});

	function innerRadialStops(options) {
	    var stops = options.stops;
	    var usedSpace = ((options.innerRadius / options.radius) * 100);
	    var length = stops.length;
	    var currentStops = [];

	    for (var i = 0; i < length; i++) {
	        var currentStop = $.extend({}, stops[i]);
	        currentStop.offset = (currentStop.offset * (100 - usedSpace) + usedSpace) / 100;
	        currentStops.push(currentStop);
	    }

	    return currentStops;
	}

	var FloatElement = ChartElement.extend({
	    init: function(options) {
	        ChartElement.fn.init.call(this, options);
	        this._initDirection();
	    },

	    _initDirection: function() {
	        var options = this.options;
	        if (options.vertical) {
	            this.groupAxis = X;
	            this.elementAxis = Y;
	            this.groupSizeField = WIDTH;
	            this.elementSizeField = HEIGHT;
	            this.groupSpacing = options.spacing;
	            this.elementSpacing = options.vSpacing;
	        } else {
	            this.groupAxis = Y;
	            this.elementAxis = X;
	            this.groupSizeField = HEIGHT;
	            this.elementSizeField = WIDTH;
	            this.groupSpacing = options.vSpacing;
	            this.elementSpacing = options.spacing;
	        }
	    },

	    reflow: function(targetBox) {
	        this.box = targetBox.clone();
	        this.reflowChildren();
	    },

	    reflowChildren: function() {
	        var this$1 = this;

	        var ref = this;
	        var box = ref.box;
	        var elementAxis = ref.elementAxis;
	        var groupAxis = ref.groupAxis;
	        var elementSizeField = ref.elementSizeField;
	        var groupSizeField = ref.groupSizeField;
	        var ref$1 = this.groupOptions();
	        var groups = ref$1.groups;
	        var groupsSize = ref$1.groupsSize;
	        var maxGroupElementsSize = ref$1.maxGroupElementsSize;
	        var groupsCount = groups.length;
	        var groupsStart = box[groupAxis + 1] + this.alignStart(groupsSize, box[groupSizeField]());

	        if (groupsCount) {
	            var groupStart = groupsStart;

	            for (var groupIdx = 0; groupIdx < groupsCount; groupIdx++) {
	                var group = groups[groupIdx];
	                var groupElements = group.groupElements;
	                var elementStart = box[elementAxis + 1];
	                var groupElementsCount = groupElements.length;

	                for (var idx = 0; idx < groupElementsCount; idx++) {
	                    var element = groupElements[idx];
	                    var elementSize$$1 = this$1.elementSize(element);
	                    var groupElementStart = groupStart + this$1.alignStart(elementSize$$1[groupSizeField], group.groupSize);

	                    var elementBox = new Box();
	                    elementBox[groupAxis + 1] = groupElementStart;
	                    elementBox[groupAxis + 2] = groupElementStart + elementSize$$1[groupSizeField];
	                    elementBox[elementAxis + 1] = elementStart;
	                    elementBox[elementAxis + 2] = elementStart + elementSize$$1[elementSizeField];

	                    element.reflow(elementBox);

	                    elementStart += elementSize$$1[elementSizeField] + this$1.elementSpacing;
	                }
	                groupStart += group.groupSize + this$1.groupSpacing;
	            }
	            box[groupAxis + 1] = groupsStart;
	            box[groupAxis + 2] = groupsStart + groupsSize;
	            box[elementAxis + 2] = box[elementAxis + 1] + maxGroupElementsSize;
	        }
	    },

	    alignStart: function(size, maxSize) {
	        var start = 0;
	        var align = this.options.align;
	        if (align === RIGHT || align === BOTTOM) {
	            start = maxSize - size;
	        } else if (align === CENTER) {
	            start = (maxSize - size) / 2;
	        }
	        return start;
	    },

	    groupOptions: function() {
	        var this$1 = this;

	        var ref = this;
	        var box = ref.box;
	        var children = ref.children;
	        var elementSizeField = ref.elementSizeField;
	        var groupSizeField = ref.groupSizeField;
	        var elementSpacing = ref.elementSpacing;
	        var groupSpacing = ref.groupSpacing;
	        var maxSize = round(box[elementSizeField]());
	        var childrenCount = children.length;
	        var groups = [];

	        var groupSize = 0;
	        var groupElementsSize = 0;
	        var groupsSize = 0;
	        var maxGroupElementsSize = 0;
	        var groupElements = [];

	        for (var idx = 0; idx < childrenCount; idx++) {
	            var element = children[idx];
	            if (!element.box) {
	                element.reflow(box);
	            }

	            var elementSize$$1 = this$1.elementSize(element);
	            if (this$1.options.wrap && round(groupElementsSize + elementSpacing + elementSize$$1[elementSizeField]) > maxSize) {
	                groups.push({
	                    groupElements: groupElements,
	                    groupSize: groupSize,
	                    groupElementsSize: groupElementsSize
	                });
	                maxGroupElementsSize = Math.max(maxGroupElementsSize, groupElementsSize);
	                groupsSize += groupSpacing + groupSize;
	                groupSize = 0;
	                groupElementsSize = 0;
	                groupElements = [];
	            }
	            groupSize = Math.max(groupSize, elementSize$$1[groupSizeField]);
	            if (groupElementsSize > 0) {
	                groupElementsSize += elementSpacing;
	            }
	            groupElementsSize += elementSize$$1[elementSizeField];
	            groupElements.push(element);
	        }

	        groups.push({
	            groupElements: groupElements,
	            groupSize: groupSize,
	            groupElementsSize: groupElementsSize
	        });
	        maxGroupElementsSize = Math.max(maxGroupElementsSize, groupElementsSize);
	        groupsSize += groupSize;

	        return {
	            groups: groups,
	            groupsSize: groupsSize,
	            maxGroupElementsSize: maxGroupElementsSize
	        };
	    },

	    elementSize: function(element) {
	        return {
	            width: element.box.width(),
	            height: element.box.height()
	        };
	    },

	    createVisual: function() {}
	});

	setDefaultOptions(FloatElement, {
	    vertical: true,
	    wrap: true,
	    vSpacing: 0,
	    spacing: 0
	});

	var DrawingText = drawing.Text;

	var Text = ChartElement.extend({
	    init: function(content, options) {
	        ChartElement.fn.init.call(this, options);

	        this.content = content;

	        // Calculate size
	        this.reflow(new Box());
	    },

	    reflow: function(targetBox) {
	        var options = this.options;
	        var size = options.size = util.measureText(this.content, { font: options.font });

	        this.baseline = size.baseline;

	        this.box = new Box(targetBox.x1, targetBox.y1,
	                targetBox.x1 + size.width, targetBox.y1 + size.height);
	    },

	    createVisual: function() {
	        var ref = this.options;
	        var font = ref.font;
	        var color = ref.color;
	        var opacity = ref.opacity;
	        var cursor = ref.cursor;

	        this.visual = new DrawingText(this.content, this.box.toRect().topLeft(), {
	            font: font,
	            fill: { color: color, opacity: opacity },
	            cursor: cursor
	        });
	    }
	});

	setDefaultOptions(Text, {
	    font: DEFAULT_FONT,
	    color: BLACK
	});

	function rectToBox(rect) {
	    var origin = rect.origin;
	    var bottomRight = rect.bottomRight();

	    return new Box(origin.x, origin.y, bottomRight.x, bottomRight.y);
	}

	var ROWS_SPLIT_REGEX = /\n/m;

	var TextBox = BoxElement.extend({
	    init: function(content, options, data) {
	        BoxElement.fn.init.call(this, options);
	        this.content = content;
	        this.data = data;

	        this._initContainer();
	        if (this.options._autoReflow !== false) {
	            this.reflow(new Box());
	        }
	    },

	    _initContainer: function() {
	        var options = this.options;
	        var rows = String(this.content).split(ROWS_SPLIT_REGEX);
	        var floatElement = new FloatElement({ vertical: true, align: options.align, wrap: false });
	        var textOptions = deepExtend({ }, options, { opacity: 1, animation: null });

	        this.container = floatElement;
	        this.append(floatElement);

	        for (var rowIdx = 0; rowIdx < rows.length; rowIdx++) {
	            var text = new Text(rows[rowIdx].trim(), textOptions);
	            floatElement.append(text);
	        }
	    },

	    reflow: function(targetBox) {
	        var options = this.options;
	        var visualFn = options.visual;
	        this.container.options.align = options.align;

	        if (visualFn && !this._boxReflow) {
	            var visualBox = targetBox;
	            if (!visualBox.hasSize()) {
	                this._boxReflow = true;
	                this.reflow(visualBox);
	                this._boxReflow = false;
	                visualBox = this.box;
	            }
	            var visual = this.visual = visualFn(this.visualContext(visualBox));

	            if (visual) {
	                visualBox = rectToBox(visual.clippedBBox() || new Rect());

	                visual.options.zIndex = options.zIndex;
	            }

	            this.box = this.contentBox = this.paddingBox = visualBox;
	        } else {
	            BoxElement.fn.reflow.call(this, targetBox);

	            if (options.rotation) {
	                var margin = getSpacing(options.margin);
	                var box = this.box.unpad(margin);

	                this.targetBox = targetBox;
	                this.normalBox = box.clone();

	                box = this.rotate();
	                box.translate(margin.left - margin.right, margin.top - margin.bottom);

	                this.rotatedBox = box.clone();

	                box.pad(margin);
	            }
	        }
	    },

	    createVisual: function() {
	        var options = this.options;

	        this.visual = new Group({
	            transform: this.rotationTransform(),
	            zIndex: options.zIndex,
	            noclip: options.noclip
	        });

	        if (this.hasBox()) {
	            var box = Path.fromRect(this.paddingBox.toRect(), this.visualStyle());
	            this.visual.append(box);
	        }
	    },

	    renderVisual: function() {
	        if (!this.options.visible) {
	            return;
	        }

	        if (this.options.visual) {
	            var visual = this.visual;
	            if (visual && !defined(visual.options.noclip)) {
	                visual.options.noclip = this.options.noclip;
	            }
	            this.addVisual();
	            this.createAnimation();
	        } else {
	            BoxElement.fn.renderVisual.call(this);
	        }
	    },

	    visualContext: function(targetBox) {
	        var this$1 = this;

	        var context = {
	            text: this.content,
	            rect: targetBox.toRect(),
	            sender: this.getSender(),
	            options: this.options,
	            createVisual: function () {
	                this$1._boxReflow = true;
	                this$1.reflow(targetBox);
	                this$1._boxReflow = false;
	                return this$1.getDefaultVisual();
	            }
	        };
	        if (this.data) {
	            $.extend(context, this.data);
	        }

	        return context;
	    },

	    getDefaultVisual: function() {
	        this.createVisual();
	        this.renderChildren();
	        var visual = this.visual;
	        delete this.visual;
	        return visual;
	    },

	    rotate: function() {
	        var options = this.options;
	        this.box.rotate(options.rotation);
	        this.align(this.targetBox, X, options.align);
	        this.align(this.targetBox, Y, options.vAlign);
	        return this.box;
	    },

	    rotationTransform: function() {
	        var rotation = this.options.rotation;
	        if (!rotation) {
	            return null;
	        }

	        var ref = this.normalBox.center();
	        var cx = ref.x;
	        var cy = ref.y;
	        var boxCenter = this.rotatedBox.center();

	        return geometryTransform()
	                   .translate(boxCenter.x - cx, boxCenter.y - cy)
	                   .rotate(rotation, [ cx, cy ]);
	    }
	});

	var Title = ChartElement.extend({
	    init: function(options) {
	        ChartElement.fn.init.call(this, options);

	        this.append(
	            new TextBox(this.options.text, $.extend({}, this.options, {
	                vAlign: this.options.position
	            }))
	        );
	    },

	    reflow: function(targetBox) {
	        ChartElement.fn.reflow.call(this, targetBox);
	        this.box.snapTo(targetBox, X);
	    }
	});

	Title.buildTitle = function(options, parent, defaultOptions) {
	    var titleOptions = options;

	    if (typeof options === "string") {
	        titleOptions = { text: options };
	    }

	    titleOptions = $.extend({ visible: true }, defaultOptions, titleOptions);

	    var title;
	    if (titleOptions && titleOptions.visible && titleOptions.text) {
	        title = new Title(titleOptions);
	        parent.append(title);
	    }

	    return title;
	};

	setDefaultOptions(Title, {
	    color: BLACK,
	    position: TOP,
	    align: CENTER,
	    margin: getSpacing(5),
	    padding: getSpacing(5)
	});

	var AxisLabel = TextBox.extend({
	    init: function(value, text, index, dataItem, options) {
	        TextBox.fn.init.call(this, text, options);

	        this.text = text;
	        this.value = value;
	        this.index = index;
	        this.dataItem = dataItem;
	        this.reflow(new Box());
	    },

	    visualContext: function(targetBox) {
	        var context = TextBox.fn.visualContext.call(this, targetBox);

	        context.value = this.value;
	        context.dataItem = this.dataItem;
	        context.format = this.options.format;
	        context.culture = this.options.culture;

	        return context;
	    },

	    click: function(widget, e) {

	        widget.trigger(AXIS_LABEL_CLICK, {
	            element: eventElement(e),
	            value: this.value,
	            text: this.text,
	            index: this.index,
	            dataItem: this.dataItem,
	            axis: this.parent.options
	        });
	    },

	    rotate: function() {
	        if (this.options.alignRotation !== CENTER) {
	            var box = this.normalBox.toRect();
	            var transform = this.rotationTransform();

	            this.box = rectToBox(box.bbox(transform.matrix()));
	        } else {
	            TextBox.fn.rotate.call(this);
	        }

	        return this.box;
	    },

	    rotationTransform: function() {
	        var options = this.options;
	        var rotation = options.rotation;
	        if (!rotation) {
	            return null;
	        }

	        if (options.alignRotation === CENTER) {
	            return TextBox.fn.rotationTransform.call(this);
	        }

	        var rotationMatrix = geometryTransform().rotate(rotation).matrix();
	        var box = this.normalBox.toRect();
	        var rect = this.targetBox.toRect();

	        var rotationOrigin = options.rotationOrigin || TOP;
	        var alignAxis = rotationOrigin === TOP || rotationOrigin === BOTTOM ? X : Y;
	        var distanceAxis = rotationOrigin === TOP || rotationOrigin === BOTTOM ? Y : X;
	        var axisAnchor = rotationOrigin === TOP || rotationOrigin === LEFT ? rect.origin : rect.bottomRight();

	        var topLeft = box.topLeft().transformCopy(rotationMatrix);
	        var topRight = box.topRight().transformCopy(rotationMatrix);
	        var bottomRight = box.bottomRight().transformCopy(rotationMatrix);
	        var bottomLeft = box.bottomLeft().transformCopy(rotationMatrix);
	        var rotatedBox = Rect.fromPoints(topLeft, topRight, bottomRight, bottomLeft);

	        var translate = {};
	        translate[distanceAxis] = rect.origin[distanceAxis] - rotatedBox.origin[distanceAxis];

	        var distanceLeft = Math.abs(topLeft[distanceAxis] + translate[distanceAxis] - axisAnchor[distanceAxis]);
	        var distanceRight = Math.abs(topRight[distanceAxis] + translate[distanceAxis] - axisAnchor[distanceAxis]);

	        var alignStart, alignEnd;

	        if (round(distanceLeft, DEFAULT_PRECISION) === round(distanceRight, DEFAULT_PRECISION)) {
	            alignStart = topLeft;
	            alignEnd = topRight;
	        } else if (distanceRight < distanceLeft) {
	            alignStart = topRight;
	            alignEnd = bottomRight;
	        } else {
	            alignStart = topLeft;
	            alignEnd = bottomLeft;
	        }

	        var alignCenter = alignStart[alignAxis] + (alignEnd[alignAxis] - alignStart[alignAxis]) / 2;
	        translate[alignAxis] = rect.center()[alignAxis] - alignCenter;

	        return geometryTransform()
	            .translate(translate.x, translate.y)
	            .rotate(rotation);
	    }
	});

	setDefaultOptions(AxisLabel, {
	    _autoReflow: false
	});

	var DEFAULT_ICON_SIZE = 7;
	var DEFAULT_LABEL_COLOR = "#fff";

	var Note = BoxElement.extend({
	    init: function(fields, options, chartService) {
	        BoxElement.fn.init.call(this, options);

	        this.fields = fields;
	        this.chartService = chartService;

	        this.render();
	    },

	    hide: function() {
	        this.options.visible = false;
	    },

	    show: function() {
	        this.options.visible = true;
	    },

	    render: function() {
	        var this$1 = this;

	        var options = this.options;

	        if (options.visible) {
	            var label = options.label;
	            var icon = options.icon;
	            var box = new Box();
	            var childAlias = function () { return this$1; };
	            var size = icon.size;
	            var text = this.fields.text;
	            var width, height;

	            if (defined(label) && label.visible) {
	                var noteTemplate = getTemplate(label);
	                if (noteTemplate) {
	                    text = noteTemplate(this.fields);
	                } else if (label.format) {
	                    text = this.chartService.format.auto(label.format, text);
	                }

	                if (!label.color) {
	                    label.color = label.position === INSIDE ? DEFAULT_LABEL_COLOR : icon.background;
	                }

	                this.label = new TextBox(text, deepExtend({}, label));
	                this.label.aliasFor = childAlias;

	                if (label.position === INSIDE && !defined(size)) {
	                    if (icon.type === CIRCLE) {
	                        size = Math.max(this.label.box.width(), this.label.box.height());
	                    } else {
	                        width = this.label.box.width();
	                        height = this.label.box.height();
	                    }
	                    box.wrap(this.label.box);
	                }
	            }

	            icon.width = width || size || DEFAULT_ICON_SIZE;
	            icon.height = height || size || DEFAULT_ICON_SIZE;

	            var marker = new ShapeElement(deepExtend({}, icon));
	            marker.aliasFor = childAlias;

	            this.marker = marker;
	            this.append(marker);

	            if (this.label) {
	                this.append(this.label);
	            }

	            marker.reflow(new Box());
	            this.wrapperBox = box.wrap(marker.box);
	        }
	    },

	    reflow: function(targetBox) {
	        var ref = this;
	        var options = ref.options;
	        var label = ref.label;
	        var marker = ref.marker;
	        var wrapperBox = ref.wrapperBox;
	        var center = targetBox.center();
	        var length = options.line.length;
	        var position = options.position;

	        // TODO: Review
	        if (options.visible) {
	            var lineStart, box, contentBox;

	            if (inArray(position, [ LEFT, RIGHT ])) {
	                if (position === LEFT) {
	                    contentBox = wrapperBox.alignTo(targetBox, position).translate(-length, targetBox.center().y - wrapperBox.center().y);

	                    if (options.line.visible) {
	                        lineStart = [ targetBox.x1, center.y ];
	                        this.linePoints = [
	                            lineStart,
	                            [ contentBox.x2, center.y ]
	                        ];
	                        box = contentBox.clone().wrapPoint(lineStart);
	                    }
	                } else {
	                    contentBox = wrapperBox.alignTo(targetBox, position).translate(length, targetBox.center().y - wrapperBox.center().y);

	                    if (options.line.visible) {
	                        lineStart = [ targetBox.x2, center.y ];
	                        this.linePoints = [
	                            lineStart,
	                            [ contentBox.x1, center.y ]
	                        ];
	                        box = contentBox.clone().wrapPoint(lineStart);
	                    }
	                }
	            } else {
	                if (position === BOTTOM) {
	                    contentBox = wrapperBox.alignTo(targetBox, position).translate(targetBox.center().x - wrapperBox.center().x, length);

	                    if (options.line.visible) {
	                        lineStart = [ center.x, targetBox.y2 ];
	                        this.linePoints = [
	                            lineStart,
	                            [ center.x, contentBox.y1 ]
	                        ];
	                        box = contentBox.clone().wrapPoint(lineStart);
	                    }
	                } else {
	                    contentBox = wrapperBox.alignTo(targetBox, position).translate(targetBox.center().x - wrapperBox.center().x, -length);

	                    if (options.line.visible) {
	                        lineStart = [ center.x, targetBox.y1 ];
	                        this.linePoints = [
	                            lineStart,
	                            [ center.x, contentBox.y2 ]
	                        ];
	                        box = contentBox.clone().wrapPoint(lineStart);
	                    }
	                }
	            }

	            if (marker) {
	                marker.reflow(contentBox);
	            }

	            if (label) {
	                label.reflow(contentBox);
	                if (marker) {
	                    if (options.label.position === OUTSIDE) {
	                        label.box.alignTo(marker.box, position);
	                    }
	                    label.reflow(label.box);
	                }
	            }

	            this.contentBox = contentBox;
	            this.targetBox = targetBox;
	            this.box = box || contentBox;
	        }
	    },

	    createVisual: function() {
	        BoxElement.fn.createVisual.call(this);
	        this.visual.options.noclip = this.options.noclip;

	        if (this.options.visible) {
	            this.createLine();
	        }
	    },

	    renderVisual: function() {
	        var this$1 = this;

	        var options = this.options;
	        var customVisual = options.visual;
	        if (options.visible && customVisual) {
	            this.visual = customVisual($.extend(this.fields, {
	                sender: this.getSender(),
	                rect: this.targetBox.toRect(),
	                options: {
	                    background: options.background,
	                    border: options.background,
	                    icon: options.icon,
	                    label: options.label,
	                    line: options.line,
	                    position: options.position,
	                    visible: options.visible
	                },
	                createVisual: function () {
	                    this$1.createVisual();
	                    this$1.renderChildren();
	                    var defaultVisual = this$1.visual;
	                    delete this$1.visual;
	                    return defaultVisual;
	                }
	            }));
	            this.addVisual();
	        } else {
	            BoxElement.fn.renderVisual.call(this);
	        }
	    },

	    createLine: function() {
	        var options = this.options.line;

	        if (this.linePoints) {
	            var path = Path.fromPoints(this.linePoints, {
	                stroke: {
	                    color: options.color,
	                    width: options.width,
	                    dashType: options.dashType
	                }
	            });

	            alignPathToPixel(path);
	            this.visual.append(path);
	        }
	    },

	    click: function(widget, e) {
	        var args = this.eventArgs(e);

	        if (!widget.trigger(NOTE_CLICK, args)) {
	            e.preventDefault();
	        }
	    },

	    over: function(widget, e) {
	        var args = this.eventArgs(e);

	        if (!widget.trigger(NOTE_HOVER, args)) {
	            e.preventDefault();
	        }
	    },

	    out: function(widget, e) {
	        var args = this.eventArgs(e);

	        widget.trigger(NOTE_LEAVE, args);
	    },

	    eventArgs: function(e) {
	        var options = this.options;

	        return $.extend(this.fields, {
	            element: eventElement(e),
	            text: defined(options.label) ? options.label.text : "",
	            visual: this.visual
	        });
	    }
	});

	setDefaultOptions(Note, {
	    icon: {
	        visible: true,
	        type: CIRCLE
	    },
	    label: {
	        position: INSIDE,
	        visible: true,
	        align: CENTER,
	        vAlign: CENTER
	    },
	    line: {
	        visible: true
	    },
	    visible: true,
	    position: TOP,
	    zIndex: 2
	});

	function createAxisTick(options, tickOptions) {
	    var tickX = options.tickX;
	    var tickY = options.tickY;
	    var position = options.position;

	    var tick = new Path({
	        stroke: {
	            width: tickOptions.width,
	            color: tickOptions.color
	        }
	    });

	    if (options.vertical) {
	        tick.moveTo(tickX, position)
	            .lineTo(tickX + tickOptions.size, position);
	    } else {
	        tick.moveTo(position, tickY)
	            .lineTo(position, tickY + tickOptions.size);
	    }

	    alignPathToPixel(tick);

	    return tick;
	}

	function createAxisGridLine(options, gridLine) {
	    var lineStart = options.lineStart;
	    var lineEnd = options.lineEnd;
	    var position = options.position;

	    var line = new Path({
	        stroke: {
	            width: gridLine.width,
	            color: gridLine.color,
	            dashType: gridLine.dashType
	        }
	    });

	    if (options.vertical) {
	        line.moveTo(lineStart, position)
	            .lineTo(lineEnd, position);
	    } else {
	        line.moveTo(position, lineStart)
	            .lineTo(position, lineEnd);
	    }

	    alignPathToPixel(line);

	    return line;
	}

	var Axis = ChartElement.extend({
	    init: function(options, chartService) {
	        if (chartService === void 0) { chartService = new ChartService(); }

	        ChartElement.fn.init.call(this, options);

	        this.chartService = chartService;

	        if (!this.options.visible) {
	            this.options = deepExtend({}, this.options, {
	                labels: {
	                    visible: false
	                },
	                line: {
	                    visible: false
	                },
	                margin: 0,
	                majorTickSize: 0,
	                minorTickSize: 0
	            });
	        }

	        this.options.minorTicks = deepExtend({}, {
	            color: this.options.line.color,
	            width: this.options.line.width,
	            visible: this.options.minorTickType !== NONE
	        }, this.options.minorTicks, {
	            size: this.options.minorTickSize,
	            align: this.options.minorTickType
	        });

	        this.options.majorTicks = deepExtend({}, {
	            color: this.options.line.color,
	            width: this.options.line.width,
	            visible: this.options.majorTickType !== NONE
	        }, this.options.majorTicks, {
	            size: this.options.majorTickSize,
	            align: this.options.majorTickType
	        });

	        this.initFields();

	        if (!this.options._deferLabels) {
	            this.createLabels();
	        }

	        this.createTitle();
	        this.createNotes();
	    },

	    initFields: function() {
	    },

	    labelsRange: function() {
	        return {
	            min: this.options.labels.skip,
	            max: this.labelsCount()
	        };
	    },

	    createLabels: function() {
	        var this$1 = this;

	        var options = this.options;
	        var align = options.vertical ? RIGHT : CENTER;
	        var labelOptions = deepExtend({ }, options.labels, {
	            align: align,
	            zIndex: options.zIndex
	        });
	        var step = Math.max(1, labelOptions.step);

	        this.clearLabels();

	        if (labelOptions.visible) {
	            var range = this.labelsRange();
	            var rotation = labelOptions.rotation;

	            if (isObject(rotation)) {
	                labelOptions.alignRotation = rotation.align;
	                labelOptions.rotation = rotation.angle;
	            }

	            if (labelOptions.rotation === "auto") {
	                labelOptions.rotation = 0;
	                options.autoRotateLabels = true;
	            }

	            for (var idx = range.min; idx < range.max; idx += step) {
	                var label = this$1.createAxisLabel(idx, labelOptions);
	                if (label) {
	                    this$1.append(label);
	                    this$1.labels.push(label);
	                }
	            }
	        }
	    },

	    clearLabels: function() {
	        this.children = grep(this.children, function (child) { return !(child instanceof AxisLabel); });
	        this.labels = [];
	    },

	    clearTitle: function() {
	        var this$1 = this;

	        if (this.title) {
	            this.children = grep(this.children, function (child) { return child !== this$1.title; });
	            this.title = undefined;
	        }
	    },

	    clear: function() {
	        this.clearLabels();
	        this.clearTitle();
	    },

	    lineBox: function() {
	        var ref = this;
	        var options = ref.options;
	        var box = ref.box;
	        var vertical = options.vertical;
	        var mirror = options.labels.mirror;
	        var axisX = mirror ? box.x1 : box.x2;
	        var axisY = mirror ? box.y2 : box.y1;
	        var lineWidth = options.line.width || 0;

	        return vertical ?
	            new Box(axisX, box.y1, axisX, box.y2 - lineWidth) :
	            new Box(box.x1, axisY, box.x2 - lineWidth, axisY);
	    },

	    createTitle: function() {
	        var options = this.options;
	        var titleOptions = deepExtend({
	            rotation: options.vertical ? -90 : 0,
	            text: "",
	            zIndex: 1,
	            visualSize: true
	        }, options.title);

	        if (titleOptions.visible && titleOptions.text) {
	            var title = new TextBox(titleOptions.text, titleOptions);
	            this.append(title);
	            this.title = title;
	        }
	    },

	    createNotes: function() {
	        var this$1 = this;

	        var options = this.options;
	        var notes = options.notes;
	        var items = notes.data || [];

	        this.notes = [];

	        for (var i = 0; i < items.length; i++) {
	            var item = deepExtend({}, notes, items[i]);
	            item.value = this$1.parseNoteValue(item.value);

	            var note = new Note({
	                value: item.value,
	                text: item.label.text,
	                dataItem: item
	            }, item, this$1.chartService);

	            if (note.options.visible) {
	                if (defined(note.options.position)) {
	                    if (options.vertical && !inArray(note.options.position, [ LEFT, RIGHT ])) {
	                        note.options.position = options.reverse ? LEFT : RIGHT;
	                    } else if (!options.vertical && !inArray(note.options.position, [ TOP, BOTTOM ])) {
	                        note.options.position = options.reverse ? BOTTOM : TOP;
	                    }
	                } else {
	                    if (options.vertical) {
	                        note.options.position = options.reverse ? LEFT : RIGHT;
	                    } else {
	                        note.options.position = options.reverse ? BOTTOM : TOP;
	                    }
	                }
	                this$1.append(note);
	                this$1.notes.push(note);
	            }
	        }
	    },

	    parseNoteValue: function(value) {
	        return value;
	    },

	    renderVisual: function() {
	        ChartElement.fn.renderVisual.call(this);

	        this.createPlotBands();
	    },

	    createVisual: function() {
	        ChartElement.fn.createVisual.call(this);

	        this.createBackground();
	        this.createLine();
	    },

	    gridLinesVisual: function() {
	        var gridLines = this._gridLines;
	        if (!gridLines) {
	            gridLines = this._gridLines = new Group({
	                zIndex: -2
	            });
	            this.appendVisual(this._gridLines);
	        }

	        return gridLines;
	    },

	    createTicks: function(lineGroup) {
	        var options = this.options;
	        var lineBox = this.lineBox();
	        var mirror = options.labels.mirror;
	        var majorUnit = options.majorTicks.visible ? options.majorUnit : 0;
	        var tickLineOptions = {
	            // TODO
	            // _alignLines: options._alignLines,
	            vertical: options.vertical
	        };

	        function render(tickPositions, tickOptions, skipUnit) {
	            var count = tickPositions.length;
	            var step = Math.max(1, tickOptions.step);

	            if (tickOptions.visible) {
	                for (var i = tickOptions.skip; i < count; i += step) {
	                    if (defined(skipUnit) && (i % skipUnit === 0)) {
	                        continue;
	                    }

	                    tickLineOptions.tickX = mirror ? lineBox.x2 : lineBox.x2 - tickOptions.size;
	                    tickLineOptions.tickY = mirror ? lineBox.y1 - tickOptions.size : lineBox.y1;
	                    tickLineOptions.position = tickPositions[i];

	                    lineGroup.append(createAxisTick(tickLineOptions, tickOptions));
	                }
	            }
	        }

	        render(this.getMajorTickPositions(), options.majorTicks);
	        render(this.getMinorTickPositions(), options.minorTicks, majorUnit / options.minorUnit);
	    },

	    createLine: function() {
	        var options = this.options;
	        var line = options.line;
	        var lineBox = this.lineBox();

	        if (line.width > 0 && line.visible) {
	            var path = new Path({
	                stroke: {
	                    width: line.width,
	                    color: line.color,
	                    dashType: line.dashType
	                }

	                /* TODO
	                zIndex: line.zIndex,
	                */
	            });

	            path.moveTo(lineBox.x1, lineBox.y1)
	                .lineTo(lineBox.x2, lineBox.y2);

	            if (options._alignLines) {
	                alignPathToPixel(path);
	            }

	            var group = this._lineGroup = new Group();
	            group.append(path);

	            this.visual.append(group);
	            this.createTicks(group);
	        }
	    },

	    getActualTickSize: function() {
	        var options = this.options;
	        var tickSize = 0;

	        if (options.majorTicks.visible && options.minorTicks.visible) {
	            tickSize = Math.max(options.majorTicks.size, options.minorTicks.size);
	        } else if (options.majorTicks.visible) {
	            tickSize = options.majorTicks.size;
	        } else if (options.minorTicks.visible) {
	            tickSize = options.minorTicks.size;
	        }

	        return tickSize;
	    },

	    createBackground: function() {
	        var ref = this;
	        var options = ref.options;
	        var box = ref.box;
	        var background = options.background;

	        if (background) {
	            this._backgroundPath = Path.fromRect(box.toRect(), {
	                fill: {
	                    color: background
	                },
	                stroke: null
	            });

	            this.visual.append(this._backgroundPath);
	        }
	    },

	    createPlotBands: function() {
	        var this$1 = this;

	        var options = this.options;
	        var plotBands = options.plotBands || [];
	        var vertical = options.vertical;
	        var plotArea = this.plotArea;

	        if (plotBands.length === 0) {
	            return;
	        }

	        var group = this._plotbandGroup = new Group({
	            zIndex: -1
	        });

	        var altAxis = grep(this.pane.axes, function (axis) { return axis.options.vertical !== this$1.options.vertical; })[0];

	        for (var idx = 0; idx < plotBands.length; idx++) {
	            var item = plotBands[idx];
	            var slotX = (void 0), slotY = (void 0);

	            if (vertical) {
	                slotX = (altAxis || plotArea.axisX).lineBox();
	                slotY = this$1.getSlot(item.from, item.to, true);
	            } else {
	                slotX = this$1.getSlot(item.from, item.to, true);
	                slotY = (altAxis || plotArea.axisY).lineBox();
	            }

	            if (slotX.width() !== 0 && slotY.height() !== 0) {
	                var bandRect = new Rect(
	                    [ slotX.x1, slotY.y1 ],
	                    [ slotX.width(), slotY.height() ]
	                );

	                var path = Path.fromRect(bandRect, {
	                    fill: {
	                        color: item.color,
	                        opacity: item.opacity
	                    },
	                    stroke: null
	                });

	                group.append(path);
	            }
	        }

	        this.appendVisual(group);
	    },

	    createGridLines: function(altAxis) {
	        var options = this.options;
	        var minorGridLines = options.minorGridLines;
	        var majorGridLines = options.majorGridLines;
	        var minorUnit = options.minorUnit;
	        var vertical = options.vertical;
	        var axisLineVisible = altAxis.options.line.visible;
	        var majorUnit = majorGridLines.visible ? options.majorUnit : 0;
	        var lineBox = altAxis.lineBox();
	        var linePos = lineBox[vertical ? "y1" : "x1"];
	        var lineOptions = {
	            lineStart: lineBox[vertical ? "x1" : "y1"],
	            lineEnd: lineBox[vertical ? "x2" : "y2"],
	            vertical: vertical
	        };
	        var majorTicks = [];

	        var container = this.gridLinesVisual();

	        function render(tickPositions, gridLine, skipUnit) {
	            var count = tickPositions.length;
	            var step = Math.max(1, gridLine.step);

	            if (gridLine.visible) {
	                for (var i = gridLine.skip; i < count; i += step) {
	                    var pos = round(tickPositions[i]);
	                    if (!inArray(pos, majorTicks)) {
	                        if (i % skipUnit !== 0 && (!axisLineVisible || linePos !== pos)) {
	                            lineOptions.position = pos;
	                            container.append(createAxisGridLine(lineOptions, gridLine));

	                            majorTicks.push(pos);
	                        }
	                    }
	                }
	            }
	        }

	        render(this.getMajorTickPositions(), majorGridLines);
	        render(this.getMinorTickPositions(), minorGridLines, majorUnit / minorUnit);

	        return container.children;
	    },

	    reflow: function(box) {
	        var ref = this;
	        var options = ref.options;
	        var labels = ref.labels;
	        var title = ref.title;
	        var vertical = options.vertical;
	        var count = labels.length;
	        var sizeFn = vertical ? WIDTH : HEIGHT;
	        var titleSize = title ? title.box[sizeFn]() : 0;
	        var space = this.getActualTickSize() + options.margin + titleSize;
	        var rootBox = (this.getRoot() || {}).box || box;
	        var boxSize = rootBox[sizeFn]();
	        var maxLabelSize = 0;

	        for (var i = 0; i < count; i++) {
	            var labelSize = labels[i].box[sizeFn]();
	            if (labelSize + space <= boxSize) {
	                maxLabelSize = Math.max(maxLabelSize, labelSize);
	            }
	        }

	        if (vertical) {
	            this.box = new Box(
	                box.x1, box.y1,
	                box.x1 + maxLabelSize + space, box.y2
	            );
	        } else {
	            this.box = new Box(
	                box.x1, box.y1,
	                box.x2, box.y1 + maxLabelSize + space
	            );
	        }

	        this.arrangeTitle();
	        this.arrangeLabels();
	        this.arrangeNotes();
	    },

	    getLabelsTickPositions: function() {
	        return this.getMajorTickPositions();
	    },

	    labelTickIndex: function(label) {
	        return label.index;
	    },

	    arrangeLabels: function() {
	        var this$1 = this;

	        var ref = this;
	        var options = ref.options;
	        var labels = ref.labels;
	        var labelsBetweenTicks = this.labelsBetweenTicks();
	        var vertical = options.vertical;
	        var lineBox = this.lineBox();
	        var mirror = options.labels.mirror;
	        var tickPositions = this.getLabelsTickPositions();
	        var labelOffset = this.getActualTickSize() + options.margin;

	        for (var idx = 0; idx < labels.length; idx++) {
	            var label = labels[idx];
	            var tickIx = this$1.labelTickIndex(label);
	            var labelSize = vertical ? label.box.height() : label.box.width();
	            var labelPos = tickPositions[tickIx] - (labelSize / 2);
	            var labelBox = (void 0), firstTickPosition = (void 0), nextTickPosition = (void 0);

	            if (vertical) {
	                if (labelsBetweenTicks) {
	                    firstTickPosition = tickPositions[tickIx];
	                    nextTickPosition = tickPositions[tickIx + 1];

	                    var middle = firstTickPosition + (nextTickPosition - firstTickPosition) / 2;
	                    labelPos = middle - (labelSize / 2);
	                }

	                var labelX = lineBox.x2;

	                if (mirror) {
	                    labelX += labelOffset;
	                    label.options.rotationOrigin = LEFT;
	                } else {
	                    labelX -= labelOffset + label.box.width();
	                    label.options.rotationOrigin = RIGHT;
	                }

	                labelBox = label.box.move(labelX, labelPos);
	            } else {
	                if (labelsBetweenTicks) {
	                    firstTickPosition = tickPositions[tickIx];
	                    nextTickPosition = tickPositions[tickIx + 1];
	                } else {
	                    firstTickPosition = labelPos;
	                    nextTickPosition = labelPos + labelSize;
	                }

	                var labelY = lineBox.y1;

	                if (mirror) {
	                    labelY -= labelOffset + label.box.height();
	                    label.options.rotationOrigin = BOTTOM;
	                } else {
	                    labelY += labelOffset;
	                    label.options.rotationOrigin = TOP;
	                }

	                labelBox = new Box(firstTickPosition, labelY,
	                                nextTickPosition, labelY + label.box.height());
	            }

	            label.reflow(labelBox);
	        }
	    },

	    autoRotateLabels: function() {
	        if (this.options.autoRotateLabels && !this.options.vertical) {
	            var tickPositions = this.getMajorTickPositions();
	            var labels = this.labels;
	            var angle;

	            for (var idx = 0; idx < labels.length; idx++) {
	                var width = Math.abs(tickPositions[idx + 1] - tickPositions[idx]);
	                var labelBox = labels[idx].box;

	                if (labelBox.width() > width) {
	                    if (labelBox.height() > width) {
	                        angle = -90;
	                        break;
	                    }
	                    angle = -45;
	                }
	            }

	            if (angle) {
	                for (var idx$1 = 0; idx$1 < labels.length; idx$1++) {
	                    labels[idx$1].options.rotation = angle;
	                    labels[idx$1].reflow(new Box());
	                }
	                return true;
	            }
	        }
	    },

	    arrangeTitle: function() {
	        var ref = this;
	        var options = ref.options;
	        var title = ref.title;
	        var mirror = options.labels.mirror;
	        var vertical = options.vertical;

	        if (title) {
	            if (vertical) {
	                title.options.align = mirror ? RIGHT : LEFT;
	                title.options.vAlign = title.options.position;
	            } else {
	                title.options.align = title.options.position;
	                title.options.vAlign = mirror ? TOP : BOTTOM;
	            }

	            title.reflow(this.box);
	        }
	    },

	    arrangeNotes: function() {
	        var this$1 = this;

	        for (var idx = 0; idx < this.notes.length; idx++) {
	            var item = this$1.notes[idx];
	            var value = item.options.value;
	            var slot = (void 0);

	            if (defined(value)) {
	                if (this$1.shouldRenderNote(value)) {
	                    item.show();
	                } else {
	                    item.hide();
	                }

	                slot = this$1.noteSlot(value);
	            } else {
	                item.hide();
	            }

	            item.reflow(slot || this$1.lineBox());
	        }
	    },

	    noteSlot: function(value) {
	        return this.getSlot(value);
	    },

	    alignTo: function(secondAxis) {
	        var lineBox = secondAxis.lineBox();
	        var vertical = this.options.vertical;
	        var pos = vertical ? Y : X;

	        this.box.snapTo(lineBox, pos);
	        if (vertical) {
	            this.box.shrink(0, this.lineBox().height() - lineBox.height());
	        } else {
	            this.box.shrink(this.lineBox().width() - lineBox.width(), 0);
	        }
	        this.box[pos + 1] -= this.lineBox()[pos + 1] - lineBox[pos + 1];
	        this.box[pos + 2] -= this.lineBox()[pos + 2] - lineBox[pos + 2];
	    },

	    axisLabelText: function(value, dataItem, options) {
	        var tmpl = getTemplate(options);
	        var text = value;

	        if (tmpl) {
	            text = tmpl({ value: value, dataItem: dataItem, format: options.format, culture: options.culture });
	        } else if (options.format) {
	            text = this.chartService.format.localeAuto(options.format, [ value ], options.culture);
	        }

	        return text;
	    },

	    slot: function(from , to, limit) {
	        var slot = this.getSlot(from, to, limit);
	        if (slot) {
	            return slot.toRect();
	        }
	    },

	    contentBox: function() {
	        var box = this.box.clone();
	        var labels = this.labels;
	        if (labels.length) {
	            var axis = this.options.vertical ? Y : X;
	            if (this.chartService.isPannable(axis)) {
	                var offset = this.maxLabelOffset();
	                box[axis + 1] -= offset.start;
	                box[axis + 2] += offset.end;
	            } else {
	                if (labels[0].options.visible) {
	                    box.wrap(labels[0].box);
	                }
	                var lastLabel = labels[labels.length - 1];
	                if (lastLabel.options.visible) {
	                    box.wrap(lastLabel.box);
	                }
	            }
	        }

	        return box;
	    },

	    maxLabelOffset: function() {
	        var this$1 = this;

	        var ref = this.options;
	        var vertical = ref.vertical;
	        var reverse = ref.reverse;
	        var labelsBetweenTicks = this.labelsBetweenTicks();
	        var tickPositions = this.getLabelsTickPositions();
	        var offsetField = vertical ? Y : X;
	        var labels = this.labels;
	        var startPosition = reverse ? 1 : 0;
	        var endPosition = reverse ? 0 : 1;
	        var maxStartOffset = 0;
	        var maxEndOffset = 0;

	        for (var idx = 0; idx < labels.length; idx++) {
	            var label = labels[idx];
	            var tickIx = this$1.labelTickIndex(label);
	            var startTick = (void 0), endTick = (void 0);

	            if (labelsBetweenTicks) {
	                startTick = tickPositions[tickIx + startPosition];
	                endTick = tickPositions[tickIx + endPosition];
	            } else {
	                startTick = endTick = tickPositions[tickIx];
	            }

	            maxStartOffset = Math.max(maxStartOffset, startTick - label.box[offsetField + 1]);
	            maxEndOffset = Math.max(maxEndOffset, label.box[offsetField + 2] - endTick);
	        }

	        return {
	            start: maxStartOffset,
	            end: maxEndOffset
	        };
	    },

	    limitRange: function(from, to, min, max, offset) {
	        var options = this.options;

	        if ((from < min && offset < 0 && (!defined(options.min) || options.min <= min)) || (max < to && offset > 0 && (!defined(options.max) || max <= options.max))) {
	            return null;
	        }

	        if ((to < min && offset > 0) || (max < from && offset < 0)) {
	            return {
	                min: from,
	                max: to
	            };
	        }

	        var rangeSize = to - from;
	        var minValue = from;
	        var maxValue = to;

	        if (from < min && offset < 0) {
	            minValue = limitValue(from, min, max);
	            maxValue = limitValue(from + rangeSize, min + rangeSize, max);
	        } else if (to > max && offset > 0) {
	            maxValue = limitValue(to, min, max);
	            minValue = limitValue(to - rangeSize, min, max - rangeSize);
	        }

	        return {
	            min: minValue,
	            max: maxValue
	        };
	    },

	    valueRange: function() {
	        return {
	            min: this.seriesMin,
	            max: this.seriesMax
	        };
	    },

	    labelsBetweenTicks: function() {
	        return !this.options.justified;
	    },

	    prepareUserOptions: function() {
	    }
	});

	setDefaultOptions(Axis, {
	    labels: {
	        visible: true,
	        rotation: 0,
	        mirror: false,
	        step: 1,
	        skip: 0
	    },
	    line: {
	        width: 1,
	        color: BLACK,
	        visible: true
	    },
	    title: {
	        visible: true,
	        position: CENTER
	    },
	    majorTicks: {
	        align: OUTSIDE,
	        size: 4,
	        skip: 0,
	        step: 1
	    },
	    minorTicks: {
	        align: OUTSIDE,
	        size: 3,
	        skip: 0,
	        step: 1
	    },
	    axisCrossingValue: 0,
	    majorTickType: OUTSIDE,
	    minorTickType: NONE,
	    majorGridLines: {
	        skip: 0,
	        step: 1
	    },
	    minorGridLines: {
	        visible: false,
	        width: 1,
	        color: BLACK,
	        skip: 0,
	        step: 1
	    },
	    // TODO: Move to line or labels options
	    margin: 5,
	    visible: true,
	    reverse: false,
	    justified: true,
	    notes: {
	        label: {
	            text: ""
	        }
	    },

	    _alignLines: true,
	    _deferLabels: false
	});

	var MILLISECONDS = "milliseconds";
	var SECONDS = "seconds";
	var MINUTES = "minutes";
	var HOURS = "hours";
	var DAYS = "days";
	var WEEKS = "weeks";
	var MONTHS = "months";
	var YEARS = "years";

	var TIME_PER_MILLISECOND = 1;
	var TIME_PER_SECOND = 1000;
	var TIME_PER_MINUTE = 60 * TIME_PER_SECOND;
	var TIME_PER_HOUR = 60 * TIME_PER_MINUTE;
	var TIME_PER_DAY = 24 * TIME_PER_HOUR;
	var TIME_PER_WEEK = 7 * TIME_PER_DAY;
	var TIME_PER_MONTH = 31 * TIME_PER_DAY;
	var TIME_PER_YEAR = 365 * TIME_PER_DAY;
	var TIME_PER_UNIT = {
	    "years": TIME_PER_YEAR,
	    "months": TIME_PER_MONTH,
	    "weeks": TIME_PER_WEEK,
	    "days": TIME_PER_DAY,
	    "hours": TIME_PER_HOUR,
	    "minutes": TIME_PER_MINUTE,
	    "seconds": TIME_PER_SECOND,
	    "milliseconds": TIME_PER_MILLISECOND
	};

	function absoluteDateDiff(a, b) {
	    var diff = a.getTime() - b;
	    var offsetDiff = a.getTimezoneOffset() - b.getTimezoneOffset();

	    return diff - (offsetDiff * TIME_PER_MINUTE);
	}

	function addTicks(date, ticks) {
	    return new Date(date.getTime() + ticks);
	}

	function toDate(value) {
	    var result;

	    if (value instanceof Date) {
	        result = value;
	    } else if (value) {
	        result = new Date(value);
	    }

	    return result;
	}

	function startOfWeek(date, weekStartDay) {
	    if (weekStartDay === void 0) { weekStartDay = 0; }

	    var daysToSubtract = 0;
	    var day = date.getDay();

	    if (!isNaN(day)) {
	        while (day !== weekStartDay) {
	            if (day === 0) {
	                day = 6;
	            } else {
	                day--;
	            }

	            daysToSubtract++;
	        }
	    }

	    return addTicks(date, -daysToSubtract * TIME_PER_DAY);
	}

	function adjustDST(date, hours) {
	    if (hours === 0 && date.getHours() === 23) {
	        date.setHours(date.getHours() + 2);
	        return true;
	    }

	    return false;
	}

	function addHours(date, hours) {
	    var roundedDate = new Date(date);

	    roundedDate.setMinutes(0, 0, 0);

	    var tzDiff = (date.getTimezoneOffset() - roundedDate.getTimezoneOffset()) * TIME_PER_MINUTE;

	    return addTicks(roundedDate, tzDiff + hours * TIME_PER_HOUR);
	}

	function addDuration(dateValue, value, unit, weekStartDay) {
	    var result = dateValue;

	    if (dateValue) {
	        var date = toDate(dateValue);
	        var hours = date.getHours();

	        if (unit === YEARS) {
	            result = new Date(date.getFullYear() + value, 0, 1);
	            adjustDST(result, 0);
	        } else if (unit === MONTHS) {
	            result = new Date(date.getFullYear(), date.getMonth() + value, 1);
	            adjustDST(result, hours);
	        } else if (unit === WEEKS) {
	            result = addDuration(startOfWeek(date, weekStartDay), value * 7, DAYS);
	            adjustDST(result, hours);
	        } else if (unit === DAYS) {
	            result = new Date(date.getFullYear(), date.getMonth(), date.getDate() + value);
	            adjustDST(result, hours);
	        } else if (unit === HOURS) {
	            result = addHours(date, value);
	        } else if (unit === MINUTES) {
	            result = addTicks(date, value * TIME_PER_MINUTE);

	            if (result.getSeconds() > 0) {
	                result.setSeconds(0);
	            }
	        } else if (unit === SECONDS) {
	            result = addTicks(date, value * TIME_PER_SECOND);
	        } else if (unit === MILLISECONDS) {
	            result = addTicks(date, value);
	        }

	        if (unit !== MILLISECONDS && result.getMilliseconds() > 0) {
	            result.setMilliseconds(0);
	        }
	    }

	    return result;
	}

	function floorDate(date, unit, weekStartDay) {
	    return addDuration(toDate(date), 0, unit, weekStartDay);
	}

	function ceilDate(dateValue, unit, weekStartDay) {
	    var date = toDate(dateValue);

	    if (date && floorDate(date, unit, weekStartDay).getTime() === date.getTime()) {
	        return date;
	    }

	    return addDuration(date, 1, unit, weekStartDay);
	}

	function dateComparer(a, b) {
	    if (a && b) {
	        return a.getTime() - b.getTime();
	    }

	    return -1;
	}

	function dateDiff(a, b) {
	    return a.getTime() - b;
	}

	function toTime(value) {
	    if (isArray(value)) {
	        var result = [];
	        for (var idx = 0; idx < value.length; idx++) {
	            result.push(toTime(value[idx]));
	        }

	        return result;
	    } else if (value) {
	        return toDate(value).getTime();
	    }
	}

	function dateEquals(a, b) {
	    if (a && b) {
	        return toTime(a) === toTime(b);
	    }

	    return a === b;
	}

	function timeIndex(date, start, baseUnit) {
	    return absoluteDateDiff(date, start) / TIME_PER_UNIT[baseUnit];
	}

	function dateIndex(value, start, baseUnit, baseUnitStep) {
	    var date = toDate(value);
	    var startDate = toDate(start);
	    var index;

	    if (baseUnit === MONTHS) {
	        index = (date.getMonth() - startDate.getMonth() + (date.getFullYear() - startDate.getFullYear()) * 12) +
	            timeIndex(date, new Date(date.getFullYear(), date.getMonth()), DAYS) / new Date(date.getFullYear(), date.getMonth() + 1, 0).getDate();
	    } else if (baseUnit === YEARS) {
	        index = date.getFullYear() - startDate.getFullYear() + dateIndex(date, new Date(date.getFullYear(), 0), MONTHS, 1) / 12;
	    } else if (baseUnit === DAYS || baseUnit === WEEKS) {
	        index = timeIndex(date, startDate, baseUnit);
	    } else {
	        index = dateDiff(date, start) / TIME_PER_UNIT[baseUnit];
	    }

	    return index / baseUnitStep;
	}

	function duration(a, b, unit) {
	    var diff;

	    if (unit === YEARS) {
	        diff = b.getFullYear() - a.getFullYear();
	    } else if (unit === MONTHS) {
	        diff = duration(a, b, YEARS) * 12 + b.getMonth() - a.getMonth();
	    } else if (unit === DAYS) {
	        diff = Math.floor(dateDiff(b, a) / TIME_PER_DAY);
	    } else {
	        diff = Math.floor(dateDiff(b, a) / TIME_PER_UNIT[unit]);
	    }

	    return diff;
	}

	function lteDateIndex(date, sortedDates) {
	    var low = 0;
	    var high = sortedDates.length - 1;
	    var index;

	    while (low <= high) {
	        index = Math.floor((low + high) / 2);
	        var currentDate = sortedDates[index];

	        if (currentDate < date) {
	            low = index + 1;
	            continue;
	        }

	        if (currentDate > date) {
	            high = index - 1;
	            continue;
	        }

	        while (dateEquals(sortedDates[index - 1], date)) {
	            index--;
	        }

	        return index;
	    }

	    if (sortedDates[index] <= date) {
	        return index;
	    }

	    return index - 1;
	}

	function parseDate(intlService, date) {
	    var result;
	    if (isString(date)) {
	        result = intlService.parseDate(date) || toDate(date);
	    } else {
	        result = toDate(date);
	    }
	    return result;
	}

	function parseDates(intlService, dates) {
	    if (isArray(dates)) {
	        var result = [];
	        for (var idx = 0; idx < dates.length; idx++) {
	            result.push(parseDate(intlService, dates[idx]));
	        }

	        return result;
	    }

	    return parseDate(intlService, dates);
	}

	var MIN_CATEGORY_POINTS_RANGE = 0.01;

	function indexOf(value, arr) {
	    if (value instanceof Date) {
	        var length = arr.length;
	        for (var idx = 0; idx < length; idx++) {
	            if (dateEquals(arr[idx], value)) {
	                return idx;
	            }
	        }

	        return -1;
	    }

	    return arr.indexOf(value);
	}

	var CategoryAxis = Axis.extend({
	    initFields: function() {
	        this._ticks = {};
	    },

	    categoriesHash: function() {
	        return "";
	    },

	    clone: function() {
	        var copy = new CategoryAxis($.extend({}, this.options), this.chartService);
	        copy.createLabels();

	        return copy;
	    },

	    initUserOptions: function(options) {
	        var categories = options.categories || [];
	        var definedMin = defined(options.min);
	        var definedMax = defined(options.max);
	        options.srcCategories = options.categories = categories;

	        if ((definedMin || definedMax) && categories.length) {
	            var min = definedMin ? Math.floor(options.min) : 0;
	            var max;

	            if (definedMax) {
	                max = options.justified ? Math.floor(options.max) + 1 : Math.ceil(options.max);
	            } else {
	                max = categories.length;
	            }

	            options.categories = options.categories.slice(min, max);
	        }

	        return options;
	    },

	    rangeIndices: function() {
	        var options = this.options;
	        var length = options.categories.length || 1;
	        var min = isNumber(options.min) ? options.min % 1 : 0;
	        var max;

	        if (isNumber(options.max) && options.max % 1 !== 0 && options.max < this.totalRange().max) {
	            max = length - (1 - options.max % 1);
	        } else {
	            max = length - (options.justified ? 1 : 0);
	        }

	        return {
	            min: min,
	            max: max
	        };
	    },

	    totalRangeIndices: function(limit) {
	        var options = this.options;
	        var min = isNumber(options.min) ? options.min : 0;
	        var max;

	        if (isNumber(options.max)) {
	            max = options.max;
	        } else if (isNumber(options.min)) {
	            max = min + options.categories.length;
	        } else {
	            max = this.totalRange().max || 1;
	        }

	        if (limit) {
	            var totalRange = this.totalRange();
	            min = limitValue(min, 0, totalRange.max);
	            max = limitValue(max, 0, totalRange.max);
	        }

	        return {
	            min: min,
	            max: max
	        };
	    },

	    range: function() {
	        var options = this.options;
	        var min = isNumber(options.min) ? options.min : 0;
	        var max = isNumber(options.max) ? options.max : this.totalRange().max;

	        return {
	            min: min,
	            max: max
	        };
	    },

	    roundedRange: function() {
	        return this.range();
	    },

	    totalRange: function() {
	        var options = this.options;
	        return { min: 0, max: Math.max(this._seriesMax || 0, options.srcCategories.length) - (options.justified ? 1 : 0) };
	    },

	    scaleOptions: function() {
	        var ref = this.rangeIndices();
	        var min = ref.min;
	        var max = ref.max;
	        var lineBox = this.lineBox();
	        var size = this.options.vertical ? lineBox.height() : lineBox.width();
	        var scale = size / ((max - min) || 1);

	        return {
	            scale: scale * (this.options.reverse ? -1 : 1),
	            box: lineBox,
	            min: min,
	            max: max
	        };
	    },

	    arrangeLabels: function() {
	        Axis.fn.arrangeLabels.call(this);
	        this.hideOutOfRangeLabels();
	    },

	    hideOutOfRangeLabels: function() {
	        var ref = this;
	        var box = ref.box;
	        var labels = ref.labels;

	        if (labels.length) {
	            var valueAxis = this.options.vertical ? Y : X;
	            var start = box[valueAxis + 1];
	            var end = box[valueAxis + 2];
	            var firstLabel = labels[0];
	            var lastLabel = last(labels);

	            if (firstLabel.box[valueAxis + 1] > end || firstLabel.box[valueAxis + 2] < start) {
	                firstLabel.options.visible = false;
	            }
	            if (lastLabel.box[valueAxis + 1] > end || lastLabel.box[valueAxis + 2] < start) {
	                lastLabel.options.visible = false;
	            }
	        }
	    },

	    getMajorTickPositions: function() {
	        return this.getTicks().majorTicks;
	    },

	    getMinorTickPositions: function() {
	        return this.getTicks().minorTicks;
	    },

	    getLabelsTickPositions: function() {
	        return this.getTicks().labelTicks;
	    },

	    tickIndices: function(stepSize) {
	        var ref = this.rangeIndices();
	        var min = ref.min;
	        var max = ref.max;
	        var limit = Math.ceil(max);
	        var current = Math.floor(min);
	        var indices = [];

	        while (current <= limit) {
	            indices.push(current);
	            current += stepSize;
	        }

	        return indices;
	    },

	    getTickPositions: function(stepSize) {
	        var ref = this.options;
	        var vertical = ref.vertical;
	        var reverse = ref.reverse;
	        var ref$1 = this.scaleOptions();
	        var scale = ref$1.scale;
	        var box = ref$1.box;
	        var min = ref$1.min;
	        var pos = box[(vertical ? Y : X) + (reverse ? 2 : 1)];
	        var indices = this.tickIndices(stepSize);
	        var positions = [];

	        for (var idx = 0; idx < indices.length; idx++) {
	            positions.push(pos + round(scale * (indices[idx] - min), COORD_PRECISION));
	        }

	        return positions;
	    },

	    getTicks: function() {
	        var options = this.options;
	        var cache = this._ticks;
	        var range = this.rangeIndices();
	        var lineBox = this.lineBox();
	        var hash = lineBox.getHash() + range.min + "," + range.max + options.reverse + options.justified;

	        if (cache._hash !== hash) {
	            var hasMinor = options.minorTicks.visible || options.minorGridLines.visible;
	            cache._hash = hash;
	            cache.labelTicks = this.getTickPositions(1);
	            cache.majorTicks = this.filterOutOfRangePositions(cache.labelTicks, lineBox);
	            cache.minorTicks = hasMinor ? this.filterOutOfRangePositions(this.getTickPositions(0.5), lineBox) : [];
	        }

	        return cache;
	    },

	    filterOutOfRangePositions: function(positions, lineBox) {
	        if (!positions.length) {
	            return positions;
	        }

	        var axis = this.options.vertical ? Y : X;
	        var inRange = function (position) { return lineBox[axis + 1] <= position && position <= lineBox[axis + 2]; };

	        var end = positions.length - 1;
	        var startIndex = 0;
	        while (!inRange(positions[startIndex]) && startIndex <= end) {
	            startIndex++;
	        }

	        var endIndex = end;

	        while (!inRange(positions[endIndex]) && endIndex >= 0) {
	            endIndex--;
	        }

	        return positions.slice(startIndex, endIndex + 1);
	    },

	    getSlot: function(from, to, limit) {
	        var options = this.options;
	        var reverse = options.reverse;
	        var justified = options.justified;
	        var vertical = options.vertical;
	        var ref = this.scaleOptions();
	        var scale = ref.scale;
	        var box = ref.box;
	        var min = ref.min;
	        var valueAxis = vertical ? Y : X;
	        var lineStart = box[valueAxis + (reverse ? 2 : 1)];
	        var slotBox = box.clone();
	        var singleSlot = !defined(to);

	        var start = valueOrDefault(from, 0);
	        var end = valueOrDefault(to, start);
	        end = Math.max(end - 1, start);

	        // Fixes transient bug caused by iOS 6.0 JIT
	        // (one can never be too sure)
	        end = Math.max(start, end);

	        var p1 = lineStart + (start - min) * scale;
	        var p2 = lineStart + (end + 1 - min) * scale;

	        if (singleSlot && justified) {
	            p2 = p1;
	        }

	        if (limit) {
	            p1 = limitValue(p1, box[valueAxis + 1], box[valueAxis + 2]);
	            p2 = limitValue(p2, box[valueAxis + 1], box[valueAxis + 2]);
	        }

	        slotBox[valueAxis + 1] = reverse ? p2 : p1;
	        slotBox[valueAxis + 2] = reverse ? p1 : p2;

	        return slotBox;
	    },

	    limitSlot: function(slot) {
	        var vertical = this.options.vertical;
	        var valueAxis = vertical ? Y : X;
	        var lineBox = this.lineBox();
	        var limittedSlot = slot.clone();

	        limittedSlot[valueAxis + 1] = limitValue(slot[valueAxis + 1], lineBox[valueAxis + 1], lineBox[valueAxis + 2]);
	        limittedSlot[valueAxis + 2] = limitValue(slot[valueAxis + 2], lineBox[valueAxis + 1], lineBox[valueAxis + 2]);

	        return limittedSlot;
	    },

	    slot: function(from, to, limit) {
	        var min = Math.floor(this.options.min || 0);
	        var start = from;
	        var end = to;

	        if (typeof start === "string") {
	            start = this.categoryIndex(start);
	        } else if (isNumber(start)) {
	            start -= min;
	        }

	        if (typeof end === "string") {
	            end = this.categoryIndex(end);
	        } else if (isNumber(end)) {
	            end -= min;
	        }

	        return Axis.fn.slot.call(this, start, end, limit);
	    },

	    pointCategoryIndex: function(point) {
	        var ref = this.options;
	        var reverse = ref.reverse;
	        var justified = ref.justified;
	        var vertical = ref.vertical;
	        var valueAxis = vertical ? Y : X;
	        var ref$1 = this.scaleOptions();
	        var scale = ref$1.scale;
	        var box = ref$1.box;
	        var min = ref$1.min;
	        var max = ref$1.max;
	        var startValue = reverse ? max : min;
	        var lineStart = box[valueAxis + 1];
	        var lineEnd = box[valueAxis + 2];
	        var pos = point[valueAxis];

	        if (pos < lineStart || pos > lineEnd) {
	            return null;
	        }

	        var value = startValue + (pos - lineStart) / scale;
	        var diff = value % 1;

	        if (justified) {
	            value = Math.round(value);
	        } else if (diff === 0 && value > 0) {
	            value--;
	        }

	        return Math.floor(value);
	    },

	    getCategory: function(point) {
	        var index = this.pointCategoryIndex(point);

	        if (index === null) {
	            return null;
	        }

	        return this.options.categories[index];
	    },

	    categoryIndex: function(value) {
	        return this.totalIndex(value) - Math.floor(this.options.min || 0);
	    },

	    categoryAt: function(index, total) {
	        var options = this.options;

	        return (total ? options.srcCategories : options.categories)[index];
	    },

	    categoriesCount: function() {
	        return (this.options.categories || []).length;
	    },

	    translateRange: function(delta) {
	        var options = this.options;
	        var lineBox = this.lineBox();
	        var size = options.vertical ? lineBox.height() : lineBox.width();
	        var range = options.categories.length;
	        var scale = size / range;
	        var offset = round(delta / scale, DEFAULT_PRECISION);

	        return {
	            min: offset,
	            max: range + offset
	        };
	    },

	    zoomRange: function(rate) {
	        var rangeIndices = this.totalRangeIndices();
	        var ref = this.totalRange();
	        var totalMin = ref.min;
	        var totalMax = ref.max;
	        var min = limitValue(rangeIndices.min + rate, totalMin, totalMax);
	        var max = limitValue(rangeIndices.max - rate, totalMin, totalMax);

	        if (max - min > 0) {
	            return {
	                min: min,
	                max: max
	            };
	        }
	    },

	    scaleRange: function(scale) {
	        var range = this.options.categories.length;
	        var delta = scale * range;

	        return {
	            min: -delta,
	            max: range + delta
	        };
	    },

	    labelsCount: function() {
	        var labelsRange = this.labelsRange();

	        return labelsRange.max - labelsRange.min;
	    },

	    labelsRange: function() {
	        var options = this.options;
	        var justified = options.justified;
	        var labelOptions = options.labels;
	        var ref = this.totalRangeIndices(true);
	        var min = ref.min;
	        var max = ref.max;
	        var start = Math.floor(min);

	        if (!justified) {
	            min = Math.floor(min);
	            max = Math.ceil(max);
	        } else {
	            min = Math.ceil(min);
	            max = Math.floor(max);
	        }

	        var skip;

	        if (min > labelOptions.skip) {
	            skip = labelOptions.skip + labelOptions.step * Math.ceil((min - labelOptions.skip) / labelOptions.step);
	        } else {
	            skip = labelOptions.skip;
	        }

	        return {
	            min: skip - start,
	            max: (options.categories.length ? max + (justified ? 1 : 0) : 0) - start
	        };
	    },

	    createAxisLabel: function(index, labelOptions) {
	        var options = this.options;
	        var dataItem = options.dataItems ? options.dataItems[index] : null;
	        var category = valueOrDefault(options.categories[index], "");
	        var text = this.axisLabelText(category, dataItem, labelOptions);

	        return new AxisLabel(category, text, index, dataItem, labelOptions);
	    },

	    shouldRenderNote: function(value) {
	        var range = this.totalRangeIndices();

	        return Math.floor(range.min) <= value && value <= Math.ceil(range.max);
	    },

	    noteSlot: function(value) {
	        var options = this.options;
	        var index = value - Math.floor(options.min || 0);
	        return this.getSlot(index);
	    },

	    arrangeNotes: function() {
	        Axis.fn.arrangeNotes.call(this);
	        this.hideOutOfRangeNotes();
	    },

	    hideOutOfRangeNotes: function() {
	        var ref = this;
	        var notes = ref.notes;
	        var box = ref.box;
	        if (notes && notes.length) {
	            var valueAxis = this.options.vertical ? Y : X;
	            var start = box[valueAxis + 1];
	            var end = box[valueAxis + 2];

	            for (var idx = 0; idx < notes.length; idx++) {
	                var note = notes[idx];
	                if (note.box && (end < note.box[valueAxis + 1] || note.box[valueAxis + 2] < start)) {
	                    note.hide();
	                }
	            }
	        }
	    },

	    pan: function(delta) {
	        var range = this.totalRangeIndices(true);
	        var ref = this.scaleOptions();
	        var scale = ref.scale;
	        var offset = round(delta / scale, DEFAULT_PRECISION);
	        var totalRange = this.totalRange();
	        var min = range.min + offset;
	        var max = range.max + offset;

	        return this.limitRange(min, max, 0, totalRange.max, offset);
	    },

	    pointsRange: function(start, end) {
	        var ref = this.options;
	        var reverse = ref.reverse;
	        var vertical = ref.vertical;
	        var valueAxis = vertical ? Y : X;
	        var range = this.totalRangeIndices(true);
	        var ref$1 = this.scaleOptions();
	        var scale = ref$1.scale;
	        var box = ref$1.box;
	        var lineStart = box[valueAxis + (reverse ? 2 : 1)];

	        var diffStart = start[valueAxis] - lineStart;
	        var diffEnd = end[valueAxis] - lineStart;

	        var min = range.min + diffStart / scale;
	        var max = range.min + diffEnd / scale;
	        var rangeMin = Math.min(min, max);
	        var rangeMax = Math.max(min, max);

	        if (rangeMax - rangeMin >= MIN_CATEGORY_POINTS_RANGE) {
	            return {
	                min: rangeMin,
	                max: rangeMax
	            };
	        }
	    },

	    valueRange: function() {
	        return this.range();
	    },

	    totalIndex: function(value) {
	        var options = this.options;
	        var index = this._categoriesMap ?
	            this._categoriesMap.get(value) : indexOf(value, options.srcCategories);

	        return index;
	    },

	    currentRangeIndices: function() {
	        var options = this.options;
	        var min = 0;

	        if (isNumber(options.min)) {
	            min = Math.floor(options.min);
	        }

	        var max;
	        if (isNumber(options.max)) {
	            max = options.justified ? Math.floor(options.max) : Math.ceil(options.max) - 1;
	        } else {
	            max = this.totalCount() - 1;
	        }

	        return {
	            min: min,
	            max: max
	        };
	    },

	    mapCategories: function() {
	        if (!this._categoriesMap) {
	            var map$$1 = this._categoriesMap = new HashMap();
	            var srcCategories = this.options.srcCategories;
	            for (var idx = 0; idx < srcCategories.length; idx++) {
	                map$$1.set(srcCategories[idx], idx);
	            }
	        }
	    },

	    totalCount: function() {
	        return Math.max(this.options.srcCategories.length, this._seriesMax || 0);
	    }
	});

	setDefaultOptions(CategoryAxis, {
	    type: "category",
	    vertical: false,
	    majorGridLines: {
	        visible: false,
	        width: 1,
	        color: BLACK
	    },
	    labels: {
	        zIndex: 1
	    },
	    justified: false,
	    _deferLabels: true
	});

	var COORDINATE_LIMIT = 300000;

	var DateLabelFormats = {
	    milliseconds: "HH:mm:ss.fff",
	    seconds: "HH:mm:ss",
	    minutes: "HH:mm",
	    hours: "HH:mm",
	    days: "M/d",
	    weeks: "M/d",
	    months: "MMM 'yy",
	    years: "yyyy"
	};

	var ZERO_THRESHOLD = 0.2;

	var AUTO = "auto";
	var BASE_UNITS = [
	    MILLISECONDS, SECONDS, MINUTES, HOURS, DAYS, WEEKS, MONTHS, YEARS
	];
	var FIT = "fit";

	function categoryRange(categories) {
	    var range = categories._range;
	    if (!range) {
	        range = categories._range = sparseArrayLimits(categories);
	        range.min = toDate(range.min);
	        range.max = toDate(range.max);
	    }

	    return range;
	}

	var EmptyDateRange = Class.extend({
	    init: function(options) {
	        this.options = options;
	    },

	    displayIndices: function() {
	        return {
	            min: 0,
	            max: 1
	        };
	    },

	    displayRange: function() {
	        return {};
	    },

	    total: function() {
	        return {};
	    },

	    valueRange: function() {
	        return {};
	    },

	    valueIndex: function() {
	        return -1;
	    },

	    values: function() {
	        return [];
	    },

	    totalIndex: function() {
	        return -1;
	    },

	    valuesCount: function() {
	        return 0;
	    },

	    totalCount: function() {
	        return 0;
	    },

	    dateAt: function() {
	        return null;
	    }
	});

	var DateRange = Class.extend({
	    init: function(start, end, options) {
	        this.options = options;
	        options.baseUnitStep = options.baseUnitStep || 1;

	        var roundToBaseUnit = options.roundToBaseUnit;
	        var justified = options.justified;

	        this.start = addDuration(start, 0, options.baseUnit, options.weekStartDay);
	        var lowerEnd = this.roundToTotalStep(end);
	        var expandEnd = !justified && dateEquals(end, lowerEnd) && !options.justifyEnd;

	        this.end = this.roundToTotalStep(end, !justified, expandEnd ? 1 : 0);

	        var min = options.min || start;
	        this.valueStart = this.roundToTotalStep(min);
	        this.displayStart = roundToBaseUnit ? this.valueStart : min;

	        var max = options.max;
	        if (!max) {
	            this.valueEnd = lowerEnd;
	            this.displayEnd = roundToBaseUnit || expandEnd ? this.end : end;
	        } else {
	            this.valueEnd = this.roundToTotalStep(max, false, !justified && dateEquals(max, this.roundToTotalStep(max)) ? -1 : 0);
	            this.displayEnd = roundToBaseUnit ? this.roundToTotalStep(max, !justified) : options.max;
	        }

	        if (this.valueEnd < this.valueStart) {
	            this.valueEnd = this.valueStart;
	        }
	        if (this.displayEnd <= this.displayStart) {
	            this.displayEnd = this.roundToTotalStep(this.displayStart, false, 1);
	        }
	    },

	    displayRange: function() {
	        return {
	            min: this.displayStart,
	            max: this.displayEnd
	        };
	    },

	    displayIndices: function() {
	        if (!this._indices) {
	            var options = this.options;

	            var baseUnit = options.baseUnit;
	            var baseUnitStep = options.baseUnitStep;

	            var minIdx = dateIndex(this.displayStart, this.valueStart, baseUnit, baseUnitStep);
	            var maxIdx = dateIndex(this.displayEnd, this.valueStart, baseUnit, baseUnitStep);

	            this._indices = { min: minIdx, max: maxIdx };
	        }

	        return this._indices;
	    },

	    total: function() {
	        return {
	            min: this.start,
	            max: this.end
	        };
	    },

	    totalCount: function() {
	        var last$$1 = this.totalIndex(this.end);

	        return last$$1 + (this.options.justified ? 1 : 0);
	    },

	    valueRange: function() {
	        return {
	            min: this.valueStart,
	            max: this.valueEnd
	        };
	    },

	    valueIndex: function(value) {
	        var options = this.options;
	        return Math.floor(dateIndex(value, this.valueStart, options.baseUnit, options.baseUnitStep));
	    },

	    totalIndex: function(value) {
	        var options = this.options;
	        return Math.floor(dateIndex(value, this.start, options.baseUnit, options.baseUnitStep));
	    },

	    dateIndex: function(value) {
	        var options = this.options;
	        return dateIndex(value, this.valueStart, options.baseUnit, options.baseUnitStep);
	    },

	    valuesCount: function() {
	        var maxIdx = this.valueIndex(this.valueEnd);

	        return maxIdx + 1;
	    },

	    values: function() {
	        var values = this._values;
	        if (!values) {
	            var options = this.options;
	            var range = this.valueRange();
	            this._values = values = [];

	            for (var date = range.min; date <= range.max;) {
	                values.push(date);
	                date = addDuration(date, options.baseUnitStep, options.baseUnit, options.weekStartDay);
	            }
	        }

	        return values;
	    },

	    dateAt: function(index, total) {
	        var options = this.options;

	        return addDuration(total ? this.start : this.valueStart, options.baseUnitStep * index, options.baseUnit, options.weekStartDay);
	    },

	    roundToTotalStep: function(value, upper, next) {
	        var ref = this.options;
	        var baseUnit = ref.baseUnit;
	        var baseUnitStep = ref.baseUnitStep;
	        var weekStartDay = ref.weekStartDay;
	        var start = this.start;

	        var step = dateIndex(value, start, baseUnit, baseUnitStep);
	        var roundedStep = upper ? Math.ceil(step) : Math.floor(step);

	        if (next) {
	            roundedStep += next;
	        }

	        return addDuration(start, roundedStep * baseUnitStep, baseUnit, weekStartDay);
	    }
	});

	function autoBaseUnit(options, startUnit, startStep) {
	    var categoryLimits = categoryRange(options.categories);
	    var span = (options.max || categoryLimits.max) - (options.min || categoryLimits.min);
	    var autoBaseUnitSteps = options.autoBaseUnitSteps;
	    var maxDateGroups = options.maxDateGroups;
	    var autoUnit = options.baseUnit === FIT;
	    var autoUnitIx = startUnit ? BASE_UNITS.indexOf(startUnit) : 0;
	    var baseUnit = autoUnit ? BASE_UNITS[autoUnitIx++] : options.baseUnit;
	    var units = span / TIME_PER_UNIT[baseUnit];
	    var totalUnits = units;
	    var unitSteps, step, nextStep;

	    while (!step || units >= maxDateGroups) {
	        unitSteps = unitSteps || autoBaseUnitSteps[baseUnit].slice(0);

	        do {
	            nextStep = unitSteps.shift();
	        } while (nextStep && startUnit === baseUnit && nextStep < startStep);

	        if (nextStep) {
	            step = nextStep;
	            units = totalUnits / step;
	        } else if (baseUnit === last(BASE_UNITS)) {
	            step = Math.ceil(totalUnits / maxDateGroups);
	            break;
	        } else if (autoUnit) {
	            baseUnit = BASE_UNITS[autoUnitIx++] || last(BASE_UNITS);
	            totalUnits = span / TIME_PER_UNIT[baseUnit];
	            unitSteps = null;
	        } else {
	            if (units > maxDateGroups) {
	                step = Math.ceil(totalUnits / maxDateGroups);
	            }
	            break;
	        }
	    }

	    options.baseUnitStep = step;
	    options.baseUnit = baseUnit;
	}

	function defaultBaseUnit(options) {
	    var categories = options.categories;
	    var count = defined(categories) ? categories.length : 0;
	    var minDiff = MAX_VALUE;
	    var lastCategory, unit;

	    for (var categoryIx = 0; categoryIx < count; categoryIx++) {
	        var category = categories[categoryIx];

	        if (category && lastCategory) {
	            var diff = absoluteDateDiff(category, lastCategory);
	            if (diff > 0) {
	                minDiff = Math.min(minDiff, diff);

	                if (minDiff >= TIME_PER_YEAR) {
	                    unit = YEARS;
	                } else if (minDiff >= TIME_PER_MONTH - TIME_PER_DAY * 3) {
	                    unit = MONTHS;
	                } else if (minDiff >= TIME_PER_WEEK) {
	                    unit = WEEKS;
	                } else if (minDiff >= TIME_PER_DAY) {
	                    unit = DAYS;
	                } else if (minDiff >= TIME_PER_HOUR) {
	                    unit = HOURS;
	                } else if (minDiff >= TIME_PER_MINUTE) {
	                    unit = MINUTES;
	                } else {
	                    unit = SECONDS;
	                }
	            }
	        }

	        lastCategory = category;
	    }

	    options.baseUnit = unit || DAYS;
	}

	function initUnit(options) {
	    var baseUnit = (options.baseUnit || "").toLowerCase();
	    var useDefault = baseUnit !== FIT && !inArray(baseUnit, BASE_UNITS);

	    if (useDefault) {
	        defaultBaseUnit(options);
	    }

	    if (baseUnit === FIT || options.baseUnitStep === AUTO) {
	        autoBaseUnit(options);
	    }

	    return options;
	}

	var DateCategoryAxis = CategoryAxis.extend({
	    clone: function() {
	        var copy = new DateCategoryAxis($.extend({}, this.options), this.chartService);
	        copy.createLabels();

	        return copy;
	    },

	    categoriesHash: function() {
	        var start = this.dataRange.total().min;
	        return this.options.baseUnit + this.options.baseUnitStep + start;
	    },

	    initUserOptions: function(options) {
	        return options;
	    },

	    initFields: function() {
	        CategoryAxis.fn.initFields.call(this);

	        var chartService = this.chartService;
	        var intlService = chartService.intl;
	        var options = this.options;

	        var categories = options.categories || [];
	        if (!categories._parsed) {
	            categories = parseDates(intlService, categories);
	            categories._parsed = true;
	        }

	        options = deepExtend({
	            roundToBaseUnit: true
	        }, options, {
	            categories: categories,
	            min: parseDate(intlService, options.min),
	            max: parseDate(intlService, options.max)
	        });

	        if (chartService.panning && chartService.isPannable(options.vertical ? Y : X)) {
	            options.roundToBaseUnit = false;
	        }

	        options.userSetBaseUnit = options.userSetBaseUnit || options.baseUnit;
	        options.userSetBaseUnitStep = options.userSetBaseUnitStep || options.baseUnitStep;

	        this.options = options;
	        options.srcCategories = categories;

	        if (categories.length > 0) {
	            var range = categoryRange(categories);
	            var maxDivisions = options.maxDivisions;

	            this.dataRange = new DateRange(range.min, range.max, initUnit(options));

	            if (maxDivisions) {
	                var dataRange = this.dataRange.displayRange();

	                var divisionOptions = $.extend({}, options, {
	                    justified: true,
	                    roundToBaseUnit: false,
	                    baseUnit: 'fit',
	                    min: dataRange.min,
	                    max: dataRange.max,
	                    maxDateGroups: maxDivisions
	                });

	                var dataRangeOptions = this.dataRange.options;

	                autoBaseUnit(divisionOptions, dataRangeOptions.baseUnit, dataRangeOptions.baseUnitStep);

	                this.divisionRange = new DateRange(range.min, range.max, divisionOptions);
	            } else {
	                this.divisionRange = this.dataRange;
	            }

	        } else {
	            options.baseUnit = options.baseUnit || DAYS;
	            this.dataRange = this.divisionRange = new EmptyDateRange(options);
	        }
	    },

	    tickIndices: function(stepSize) {
	        var ref = this;
	        var dataRange = ref.dataRange;
	        var divisionRange = ref.divisionRange;
	        var valuesCount = divisionRange.valuesCount();

	        if (!this.options.maxDivisions || !valuesCount) {
	            return CategoryAxis.fn.tickIndices.call(this, stepSize);
	        }

	        var indices = [];
	        var values = divisionRange.values();
	        var offset = 0;

	        if (!this.options.justified) {
	            values = values.concat(divisionRange.dateAt(valuesCount));
	            offset = 0.5;//align ticks to the center of not justified categories
	        }

	        for (var idx = 0; idx < values.length; idx++) {
	            indices.push(dataRange.dateIndex(values[idx]) + offset);
	            if (stepSize !== 1 && idx >= 1) {
	                var last$$1 = indices.length - 1;
	                indices.splice(idx, 0, indices[last$$1 - 1] + (indices[last$$1] - indices[last$$1 - 1]) * stepSize);
	            }
	        }

	        return indices;
	    },

	    shouldRenderNote: function(value) {
	        var range = this.range();
	        var categories = this.options.categories || [];

	        return dateComparer(value, range.min) >= 0 && dateComparer(value, range.max) <= 0 && categories.length;
	    },

	    parseNoteValue: function(value) {
	        return parseDate(this.chartService.intl, value);
	    },

	    noteSlot: function(value) {
	        return this.getSlot(value);
	    },

	    translateRange: function(delta) {
	        var options = this.options;
	        var baseUnit = options.baseUnit;
	        var weekStartDay = options.weekStartDay;
	        var vertical = options.vertical;
	        var lineBox = this.lineBox();
	        var size = vertical ? lineBox.height() : lineBox.width();
	        var range = this.range();
	        var scale = size / (range.max - range.min);
	        var offset = round(delta / scale, DEFAULT_PRECISION);

	        if (range.min && range.max) {
	            var from = addTicks(options.min || range.min, offset);
	            var to = addTicks(options.max || range.max, offset);

	            range = {
	                min: addDuration(from, 0, baseUnit, weekStartDay),
	                max: addDuration(to, 0, baseUnit, weekStartDay)
	            };
	        }

	        return range;
	    },

	    scaleRange: function(delta) {
	        var rounds = Math.abs(delta);
	        var result = this.range();
	        var from = result.min;
	        var to = result.max;

	        if (from && to) {
	            while (rounds--) {
	                var range = dateDiff(from, to);
	                var step = Math.round(range * 0.1);
	                if (delta < 0) {
	                    from = addTicks(from, step);
	                    to = addTicks(to, -step);
	                } else {
	                    from = addTicks(from, -step);
	                    to = addTicks(to, step);
	                }
	            }

	            result = { min: from, max: to };
	        }

	        return result;
	    },

	    labelsRange: function() {
	        return {
	            min: this.options.labels.skip,
	            max: this.divisionRange.valuesCount()
	        };
	    },

	    pan: function(delta) {
	        if (this.isEmpty()) {
	            return null;
	        }

	        var options = this.options;
	        var lineBox = this.lineBox();
	        var size = options.vertical ? lineBox.height() : lineBox.width();
	        var ref = this.dataRange.displayRange();
	        var min = ref.min;
	        var max = ref.max;
	        var totalLimits = this.dataRange.total();
	        var scale = size / (max - min);
	        var offset = round(delta / scale, DEFAULT_PRECISION) * (options.reverse ? -1 : 1);
	        var from = addTicks(min, offset);
	        var to = addTicks(max, offset);

	        var panRange = this.limitRange(toTime(from), toTime(to), toTime(totalLimits.min), toTime(totalLimits.max), offset);

	        if (panRange) {
	            panRange.min = toDate(panRange.min);
	            panRange.max = toDate(panRange.max);
	            panRange.baseUnit = options.baseUnit;
	            panRange.baseUnitStep = options.baseUnitStep || 1;
	            panRange.userSetBaseUnit = options.userSetBaseUnit;
	            panRange.userSetBaseUnitStep = options.userSetBaseUnitStep;

	            return panRange;
	        }
	    },

	    pointsRange: function(start, end) {
	        if (this.isEmpty()) {
	            return null;
	        }

	        var pointsRange = CategoryAxis.fn.pointsRange.call(this, start, end);
	        var datesRange = this.dataRange.displayRange();
	        var indicesRange = this.dataRange.displayIndices();
	        var scale = dateDiff(datesRange.max, datesRange.min) / (indicesRange.max - indicesRange.min);
	        var options = this.options;

	        var min = addTicks(datesRange.min, pointsRange.min * scale);
	        var max = addTicks(datesRange.min, pointsRange.max * scale);

	        return {
	            min: min,
	            max: max,
	            baseUnit: options.userSetBaseUnit || options.baseUnit,
	            baseUnitStep: options.userSetBaseUnitStep || options.baseUnitStep
	        };
	    },

	    zoomRange: function(delta) {
	        if (this.isEmpty()) {
	            return null;
	        }

	        var options = this.options;
	        var fit = options.userSetBaseUnit === FIT;
	        var totalLimits = this.dataRange.total();
	        var ref = this.dataRange.displayRange();
	        var rangeMin = ref.min;
	        var rangeMax = ref.max;
	        var ref$1 = this.dataRange.options;
	        var weekStartDay = ref$1.weekStartDay;
	        var baseUnit = ref$1.baseUnit;
	        var baseUnitStep = ref$1.baseUnitStep;
	        var min = addDuration(rangeMin, delta * baseUnitStep, baseUnit, weekStartDay);
	        var max = addDuration(rangeMax, -delta * baseUnitStep, baseUnit, weekStartDay);

	        if (fit) {
	            var autoBaseUnitSteps = options.autoBaseUnitSteps;
	            var maxDateGroups = options.maxDateGroups;

	            var maxDiff = last(autoBaseUnitSteps[baseUnit]) * maxDateGroups * TIME_PER_UNIT[baseUnit];
	            var rangeDiff = dateDiff(rangeMax, rangeMin);
	            var diff = dateDiff(max, min);
	            var baseUnitIndex = BASE_UNITS.indexOf(baseUnit);
	            var autoBaseUnitStep, ticks;

	            if (diff < TIME_PER_UNIT[baseUnit] && baseUnit !== MILLISECONDS) {
	                baseUnit = BASE_UNITS[baseUnitIndex - 1];
	                autoBaseUnitStep = last(autoBaseUnitSteps[baseUnit]);
	                ticks = (rangeDiff - (maxDateGroups - 1) * autoBaseUnitStep * TIME_PER_UNIT[baseUnit]) / 2;
	                min = addTicks(rangeMin, ticks);
	                max = addTicks(rangeMax, -ticks);

	            } else if (diff > maxDiff && baseUnit !== YEARS) {
	                var stepIndex = 0;

	                do {
	                    baseUnitIndex++;
	                    baseUnit = BASE_UNITS[baseUnitIndex];
	                    stepIndex = 0;
	                    ticks = 2 * TIME_PER_UNIT[baseUnit];
	                    do {
	                        autoBaseUnitStep = autoBaseUnitSteps[baseUnit][stepIndex];
	                        stepIndex++;
	                    } while (stepIndex < autoBaseUnitSteps[baseUnit].length && ticks * autoBaseUnitStep < rangeDiff);
	                } while (baseUnit !== YEARS && ticks * autoBaseUnitStep < rangeDiff);

	                ticks = (ticks * autoBaseUnitStep - rangeDiff) / 2;
	                if (ticks > 0) {
	                    min = addTicks(rangeMin, -ticks);
	                    max = addTicks(rangeMax, ticks);
	                    min = addTicks(min, limitValue(max, totalLimits.min, totalLimits.max) - max);
	                    max = addTicks(max, limitValue(min, totalLimits.min, totalLimits.max) - min);
	                }
	            }
	        }

	        if (min < totalLimits.min) {
	            min = totalLimits.min;
	        }
	        if (max > totalLimits.max) {
	            max = totalLimits.max;
	        }

	        if (min && max && dateDiff(max, min) > 0) {
	            return {
	                min: min,
	                max: max,
	                baseUnit: options.userSetBaseUnit || options.baseUnit,
	                baseUnitStep: options.userSetBaseUnitStep || options.baseUnitStep
	            };
	        }
	    },

	    range: function() {
	        return this.dataRange.displayRange();
	    },

	    createAxisLabel: function(index, labelOptions) {
	        var options = this.options;
	        var dataItem = options.dataItems && !options.maxDivisions ? options.dataItems[index] : null;
	        var date = this.divisionRange.dateAt(index);
	        var unitFormat = labelOptions.dateFormats[this.divisionRange.options.baseUnit];

	        labelOptions.format = labelOptions.format || unitFormat;
	        var text = this.axisLabelText(date, dataItem, labelOptions);
	        if (text) {
	            return new AxisLabel(date, text, index, dataItem, labelOptions);
	        }
	    },

	    categoryIndex: function(value) {
	        return this.dataRange.valueIndex(value);
	    },

	    slot: function(from, to, limit) {
	        var dateRange = this.dataRange;
	        var start = from;
	        var end = to;

	        if (start instanceof Date) {
	            start = dateRange.dateIndex(start);
	        }

	        if (end instanceof Date) {
	            end = dateRange.dateIndex(end);
	        }

	        var slot = this.getSlot(start, end, limit);
	        if (slot) {
	            return slot.toRect();
	        }
	    },

	    getSlot: function(a, b, limit) {
	        var start = a;
	        var end = b;

	        if (typeof start === OBJECT) {
	            start = this.categoryIndex(start);
	        }

	        if (typeof end === OBJECT) {
	            end = this.categoryIndex(end);
	        }

	        return CategoryAxis.fn.getSlot.call(this, start, end, limit);
	    },

	    valueRange: function() {
	        var options = this.options;
	        var range = categoryRange(options.srcCategories);

	        return {
	            min: toDate(range.min),
	            max: toDate(range.max)
	        };
	    },

	    categoryAt: function(index, total) {
	        return this.dataRange.dateAt(index, total);
	    },

	    categoriesCount: function() {
	        return this.dataRange.valuesCount();
	    },

	    rangeIndices: function() {
	        return this.dataRange.displayIndices();
	    },

	    labelsBetweenTicks: function() {
	        return !this.divisionRange.options.justified;
	    },

	    prepareUserOptions: function() {
	        if (this.isEmpty()) {
	            return;
	        }

	        this.options.categories = this.dataRange.values();
	    },

	    getCategory: function(point) {
	        var index = this.pointCategoryIndex(point);

	        if (index === null) {
	            return null;
	        }

	        return this.dataRange.dateAt(index);
	    },

	    totalIndex: function(value) {
	        return this.dataRange.totalIndex(value);
	    },

	    currentRangeIndices: function() {
	        var range = this.dataRange.valueRange();
	        return {
	            min: this.dataRange.totalIndex(range.min),
	            max: this.dataRange.totalIndex(range.max)
	        };
	    },

	    totalRange: function() {
	        return this.dataRange.total();
	    },

	    totalCount: function() {
	        return this.dataRange.totalCount();
	    },

	    isEmpty: function() {
	        return !this.options.srcCategories.length;
	    },

	    roundedRange: function() {
	        if (this.options.roundToBaseUnit !== false || this.isEmpty()) {
	            return this.range();
	        }

	        var options = this.options;
	        var datesRange = categoryRange(options.srcCategories);

	        var dateRange = new DateRange(datesRange.min, datesRange.max, $.extend({}, options, {
	            justified: false,
	            roundToBaseUnit: true,
	            justifyEnd: options.justified
	        }));

	        return dateRange.displayRange();
	    }
	});

	setDefaultOptions(DateCategoryAxis, {
	    type: DATE,
	    labels: {
	        dateFormats: DateLabelFormats
	    },
	    autoBaseUnitSteps: {
	        milliseconds: [ 1, 10, 100 ],
	        seconds: [ 1, 2, 5, 15, 30 ],
	        minutes: [ 1, 2, 5, 15, 30 ],
	        hours: [ 1, 2, 3 ],
	        days: [ 1, 2, 3 ],
	        weeks: [ 1, 2 ],
	        months: [ 1, 2, 3, 6 ],
	        years: [ 1, 2, 3, 5, 10, 25, 50 ]
	    },
	    maxDateGroups: 10
	});

	function autoAxisMin(min, max, narrow) {
	    if (!min && !max) {
	        return 0;
	    }

	    var axisMin;

	    if (min >= 0 && max >= 0) {
	        var minValue = min === max ? 0 : min;

	        var diff = (max - minValue) / max;
	        if (narrow === false || (!narrow && diff > ZERO_THRESHOLD)) {
	            return 0;
	        }

	        axisMin = Math.max(0, minValue - ((max - minValue) / 2));
	    } else {
	        axisMin = min;
	    }

	    return axisMin;
	}

	function autoAxisMax(min, max, narrow) {
	    if (!min && !max) {
	        return 1;
	    }

	    var axisMax;

	    if (min <= 0 && max <= 0) {
	        var maxValue = min === max ? 0 : max;

	        var diff = Math.abs((maxValue - min) / maxValue);
	        if (narrow === false || (!narrow && diff > ZERO_THRESHOLD)) {
	            return 0;
	        }

	        axisMax = Math.min(0, maxValue - ((min - maxValue) / 2));
	    } else {
	        axisMax = max;
	    }

	    return axisMax;
	}

	function floor(value, step) {
	    return round(Math.floor(value / step) * step, DEFAULT_PRECISION);
	}

	function ceil(value, step) {
	    return round(Math.ceil(value / step) * step, DEFAULT_PRECISION);
	}

	function limitCoordinate(value) {
	    return Math.max(Math.min(value, COORDINATE_LIMIT), -COORDINATE_LIMIT);
	}

	var MIN_VALUE_RANGE = Math.pow(10, -DEFAULT_PRECISION + 1);

	var NumericAxis = Axis.extend({
	    init: function(seriesMin, seriesMax, options, chartService) {
	        Axis.fn.init.call(this, $.extend({}, options, {
	            seriesMin: seriesMin,
	            seriesMax: seriesMax
	        }), chartService);
	    },

	    initUserOptions: function(options) {
	        var autoOptions = autoAxisOptions(options.seriesMin, options.seriesMax, options);
	        this.totalOptions = totalAxisOptions(autoOptions, options);

	        return axisOptions(autoOptions, options);
	    },

	    initFields: function() {
	        this.totalMin = this.totalOptions.min;
	        this.totalMax = this.totalOptions.max;
	        this.totalMajorUnit = this.totalOptions.majorUnit;
	        this.seriesMin = this.options.seriesMin;
	        this.seriesMax = this.options.seriesMax;
	    },

	    clone: function() {
	        return new NumericAxis(
	            this.seriesMin,
	            this.seriesMax,
	            $.extend({}, this.options),
	            this.chartService
	        );
	    },

	    startValue: function() {
	        return 0;
	    },

	    range: function() {
	        var options = this.options;
	        return { min: options.min, max: options.max };
	    },

	    getDivisions: function(stepValue) {
	        if (stepValue === 0) {
	            return 1;
	        }

	        var options = this.options;
	        var range = options.max - options.min;

	        return Math.floor(round(range / stepValue, COORD_PRECISION)) + 1;
	    },

	    getTickPositions: function(unit, skipUnit) {
	        var options = this.options;
	        var vertical = options.vertical;
	        var reverse = options.reverse;
	        var lineBox = this.lineBox();
	        var lineSize = vertical ? lineBox.height() : lineBox.width();
	        var range = options.max - options.min;
	        var scale = lineSize / range;
	        var step = unit * scale;
	        var divisions = this.getDivisions(unit);
	        var dir = (vertical ? -1 : 1) * (reverse ? -1 : 1);
	        var startEdge = dir === 1 ? 1 : 2;
	        var positions = [];
	        var pos = lineBox[(vertical ? Y : X) + startEdge];
	        var skipStep = 0;

	        if (skipUnit) {
	            skipStep = skipUnit / unit;
	        }

	        for (var idx = 0; idx < divisions; idx++) {
	            if (idx % skipStep !== 0) {
	                positions.push(round(pos, COORD_PRECISION));
	            }

	            pos = pos + step * dir;
	        }

	        return positions;
	    },

	    getMajorTickPositions: function() {
	        return this.getTickPositions(this.options.majorUnit);
	    },

	    getMinorTickPositions: function() {
	        return this.getTickPositions(this.options.minorUnit);
	    },

	    getSlot: function(a, b, limit) {
	        if (limit === void 0) { limit = false; }

	        var options = this.options;
	        var vertical = options.vertical;
	        var reverse = options.reverse;
	        var valueAxis = vertical ? Y : X;
	        var lineBox = this.lineBox();
	        var lineStart = lineBox[valueAxis + (reverse ? 2 : 1)];
	        var lineSize = vertical ? lineBox.height() : lineBox.width();
	        var dir = reverse ? -1 : 1;
	        var step = dir * (lineSize / (options.max - options.min));
	        var slotBox = new Box(lineBox.x1, lineBox.y1, lineBox.x1, lineBox.y1);

	        var start = a;
	        var end = b;

	        if (!defined(start)) {
	            start = end || 0;
	        }

	        if (!defined(end)) {
	            end = start || 0;
	        }

	        if (limit) {
	            start = Math.max(Math.min(start, options.max), options.min);
	            end = Math.max(Math.min(end, options.max), options.min);
	        }

	        var p1, p2;

	        if (vertical) {
	            p1 = options.max - Math.max(start, end);
	            p2 = options.max - Math.min(start, end);
	        } else {
	            p1 = Math.min(start, end) - options.min;
	            p2 = Math.max(start, end) - options.min;
	        }

	        slotBox[valueAxis + 1] = limitCoordinate(lineStart + step * (reverse ? p2 : p1));
	        slotBox[valueAxis + 2] = limitCoordinate(lineStart + step * (reverse ? p1 : p2));

	        return slotBox;
	    },

	    getValue: function(point) {
	        var options = this.options;
	        var vertical = options.vertical;
	        var reverse = options.reverse;
	        var max = Number(options.max);
	        var min = Number(options.min);
	        var valueAxis = vertical ? Y : X;
	        var lineBox = this.lineBox();
	        var lineStart = lineBox[valueAxis + (reverse ? 2 : 1)];
	        var lineSize = vertical ? lineBox.height() : lineBox.width();
	        var dir = reverse ? -1 : 1;
	        var offset = dir * (point[valueAxis] - lineStart);
	        var step = (max - min) / lineSize;
	        var valueOffset = offset * step;

	        if (offset < 0 || offset > lineSize) {
	            return null;
	        }

	        var value = vertical ?
	                max - valueOffset :
	                min + valueOffset;

	        return round(value, DEFAULT_PRECISION);
	    },

	    translateRange: function(delta) {
	        var options = this.options;
	        var vertical = options.vertical;
	        var reverse = options.reverse;
	        var max = options.max;
	        var min = options.min;
	        var lineBox = this.lineBox();
	        var size = vertical ? lineBox.height() : lineBox.width();
	        var range = max - min;
	        var scale = size / range;
	        var offset = round(delta / scale, DEFAULT_PRECISION);

	        if ((vertical || reverse) && !(vertical && reverse )) {
	            offset = -offset;
	        }

	        return {
	            min: min + offset,
	            max: max + offset,
	            offset: offset
	        };
	    },

	    scaleRange: function(delta) {
	        var options = this.options;
	        var offset = -delta * options.majorUnit;

	        return {
	            min: options.min - offset,
	            max: options.max + offset
	        };
	    },

	    labelsCount: function() {
	        return this.getDivisions(this.options.majorUnit);
	    },

	    createAxisLabel: function(index, labelOptions) {
	        var options = this.options;
	        var value = round(options.min + (index * options.majorUnit), DEFAULT_PRECISION);
	        var text = this.axisLabelText(value, null, labelOptions);

	        return new AxisLabel(value, text, index, null, labelOptions);
	    },

	    shouldRenderNote: function(value) {
	        var range = this.range();
	        return range.min <= value && value <= range.max;
	    },

	    pan: function(delta) {
	        var range = this.translateRange(delta);
	        return this.limitRange(range.min, range.max, this.totalMin, this.totalMax, range.offset);
	    },

	    pointsRange: function(start, end) {
	        var startValue = this.getValue(start);
	        var endValue = this.getValue(end);
	        var min = Math.min(startValue, endValue);
	        var max = Math.max(startValue, endValue);

	        if (this.isValidRange(min, max)) {
	            return {
	                min: min,
	                max: max
	            };
	        }
	    },

	    zoomRange: function(delta) {
	        var ref = this;
	        var totalMin = ref.totalMin;
	        var totalMax = ref.totalMax;
	        var newRange = this.scaleRange(delta);
	        var min = limitValue(newRange.min, totalMin, totalMax);
	        var max = limitValue(newRange.max, totalMin, totalMax);

	        if (this.isValidRange(min, max)) {
	            return {
	                min: min,
	                max: max
	            };
	        }
	    },

	    isValidRange: function(min, max) {
	        return max - min > MIN_VALUE_RANGE;
	    }
	});

	function autoAxisOptions(seriesMin, seriesMax, options) {
	    var narrowRange = options.narrowRange;

	    var autoMin = autoAxisMin(seriesMin, seriesMax, narrowRange);
	    var autoMax = autoAxisMax(seriesMin, seriesMax, narrowRange);

	    var majorUnit = autoMajorUnit(autoMin, autoMax);
	    var autoOptions = {
	        majorUnit: majorUnit
	    };

	    if (options.roundToMajorUnit !== false) {
	        if (autoMin < 0 && remainderClose(autoMin, majorUnit, 1 / 3)) {
	            autoMin -= majorUnit;
	        }

	        if (autoMax > 0 && remainderClose(autoMax, majorUnit, 1 / 3)) {
	            autoMax += majorUnit;
	        }
	    }

	    autoOptions.min = floor(autoMin, majorUnit);
	    autoOptions.max = ceil(autoMax, majorUnit);

	    return autoOptions;
	}

	function totalAxisOptions(autoOptions, options) {
	    return {
	        min: defined(options.min) ? Math.min(autoOptions.min, options.min) : autoOptions.min,
	        max: defined(options.max) ? Math.max(autoOptions.max, options.max) : autoOptions.max,
	        majorUnit: autoOptions.majorUnit
	    };
	}

	function clearNullValues(options, fields) {
	    for (var idx = 0; idx < fields.length; idx++) {
	        var field = fields[idx];
	        if (options[field] === null) {
	            options[field] = undefined;
	        }
	    }
	}

	function axisOptions(autoOptions, userOptions) {
	    var options = userOptions;
	    var userSetMin, userSetMax;

	    if (userOptions) {
	        clearNullValues(userOptions, [ 'min', 'max' ]);

	        userSetMin = defined(userOptions.min);
	        userSetMax = defined(userOptions.max);

	        var userSetLimits = userSetMin || userSetMax;

	        if (userSetLimits) {
	            if (userOptions.min === userOptions.max) {
	                if (userOptions.min > 0) {
	                    userOptions.min = 0;
	                } else {
	                    userOptions.max = 1;
	                }
	            }
	        }

	        if (userOptions.majorUnit) {
	            autoOptions.min = floor(autoOptions.min, userOptions.majorUnit);
	            autoOptions.max = ceil(autoOptions.max, userOptions.majorUnit);
	        } else if (userSetLimits) {
	            options = deepExtend(autoOptions, userOptions);

	            // Determine an auto major unit after min/max have been set
	            autoOptions.majorUnit = autoMajorUnit(options.min, options.max);
	        }
	    }

	    autoOptions.minorUnit = (options.majorUnit || autoOptions.majorUnit) / 5;

	    var result = deepExtend(autoOptions, options);
	    if (result.min >= result.max) {
	        if (userSetMin && !userSetMax) {
	            result.max = result.min + result.majorUnit;
	        } else if (!userSetMin && userSetMax) {
	            result.min = result.max - result.majorUnit;
	        }
	    }

	    return result;
	}

	function remainderClose(value, divisor, ratio) {
	    var remainder = round(Math.abs(value % divisor), DEFAULT_PRECISION);
	    var threshold = divisor * (1 - ratio);

	    return remainder === 0 || remainder > threshold;
	}

	setDefaultOptions(NumericAxis, {
	    type: "numeric",
	    min: 0,
	    max: 1,
	    vertical: true,
	    majorGridLines: {
	        visible: true,
	        width: 1,
	        color: BLACK
	    },
	    labels: {
	        format: "#.####################"
	    },
	    zIndex: 1
	});

	var DateValueAxis = Axis.extend({
	    init: function(seriesMin, seriesMax, axisOptions, chartService) {
	        var min = toDate(seriesMin);
	        var max = toDate(seriesMax);

	        var intlService = chartService.intl;
	        var options = axisOptions || {};
	        options = deepExtend(options || {}, {
	            min: parseDate(intlService, options.min),
	            max: parseDate(intlService, options.max),
	            axisCrossingValue: parseDates(intlService, options.axisCrossingValues || options.axisCrossingValue)
	        });
	        options = applyDefaults(min, max, options);

	        Axis.fn.init.call(this, options, chartService);

	        this.intlService = intlService;
	        this.seriesMin = min;
	        this.seriesMax = max;
	        this.totalMin = toTime(floorDate(toTime(min) - 1, options.baseUnit));
	        this.totalMax = toTime(ceilDate(toTime(max) + 1, options.baseUnit));
	    },

	    clone: function() {
	        return new DateValueAxis(this.seriesMin, this.seriesMax, $.extend({}, this.options), this.chartService);
	    },

	    range: function() {
	        var options = this.options;
	        return { min: options.min, max: options.max };
	    },

	    getDivisions: function(stepValue) {
	        var options = this.options;

	        return Math.floor(
	            duration(options.min, options.max, options.baseUnit) / stepValue + 1
	        );
	    },

	    getTickPositions: function(step) {
	        var options = this.options;
	        var vertical = options.vertical;
	        var lineBox = this.lineBox();
	        var dir = (vertical ? -1 : 1) * (options.reverse ? -1 : 1);
	        var startEdge = dir === 1 ? 1 : 2;
	        var start = lineBox[(vertical ? Y : X) + startEdge];
	        var divisions = this.getDivisions(step);
	        var timeRange = dateDiff(options.max, options.min);
	        var lineSize = vertical ? lineBox.height() : lineBox.width();
	        var scale = lineSize / timeRange;

	        var positions = [ start ];
	        for (var i = 1; i < divisions; i++) {
	            var date = addDuration(options.min, i * step, options.baseUnit);
	            var pos = start + dateDiff(date, options.min) * scale * dir;

	            positions.push(round(pos, COORD_PRECISION));
	        }

	        return positions;
	    },

	    getMajorTickPositions: function() {
	        return this.getTickPositions(this.options.majorUnit);
	    },

	    getMinorTickPositions: function() {
	        return this.getTickPositions(this.options.minorUnit);
	    },

	    getSlot: function(a, b, limit) {
	        return NumericAxis.prototype.getSlot.call(
	            this, parseDate(this.intlService, a), parseDate(this.intlService, b), limit
	        );
	    },

	    getValue: function(point) {
	        var value = NumericAxis.prototype.getValue.call(this, point);

	        return value !== null ? toDate(value) : null;
	    },

	    labelsCount: function() {
	        return this.getDivisions(this.options.majorUnit);
	    },

	    createAxisLabel: function(index, labelOptions) {
	        var options = this.options;
	        var offset = index * options.majorUnit;
	        var date = options.min;

	        if (offset > 0) {
	            date = addDuration(date, offset, options.baseUnit);
	        }

	        var unitFormat = labelOptions.dateFormats[options.baseUnit];
	        labelOptions.format = labelOptions.format || unitFormat;

	        var text = this.axisLabelText(date, null, labelOptions);
	        return new AxisLabel(date, text, index, null, labelOptions);
	    },

	    translateRange: function(delta, exact) {
	        var options = this.options;
	        var baseUnit = options.baseUnit;
	        var weekStartDay = options.weekStartDay;
	        var lineBox = this.lineBox();
	        var size = options.vertical ? lineBox.height() : lineBox.width();
	        var range = this.range();
	        var scale = size / dateDiff(range.max, range.min);
	        var offset = round(delta / scale, DEFAULT_PRECISION) * (options.reverse ? -1 : 1);
	        var from = addTicks(options.min, offset);
	        var to = addTicks(options.max, offset);

	        if (!exact) {
	            from = addDuration(from, 0, baseUnit, weekStartDay);
	            to = addDuration(to, 0, baseUnit, weekStartDay);
	        }

	        return {
	            min: from,
	            max: to,
	            offset: offset
	        };
	    },

	    scaleRange: function(delta) {
	        var ref = this.options;
	        var from = ref.min;
	        var to = ref.max;
	        var rounds = Math.abs(delta);

	        while (rounds--) {
	            var range = dateDiff(from, to);
	            var step = Math.round(range * 0.1);
	            if (delta < 0) {
	                from = addTicks(from, step);
	                to = addTicks(to, -step);
	            } else {
	                from = addTicks(from, -step);
	                to = addTicks(to, step);
	            }
	        }

	        return { min: from, max: to };
	    },

	    shouldRenderNote: function(value) {
	        var range = this.range();

	        return dateComparer(value, range.min) >= 0 && dateComparer(value, range.max) <= 0;
	    },

	    pan: function(delta) {
	        var range = this.translateRange(delta, true);
	        var limittedRange = this.limitRange(toTime(range.min), toTime(range.max), this.totalMin, this.totalMax, range.offset);

	        if (limittedRange) {
	            return {
	                min: toDate(limittedRange.min),
	                max: toDate(limittedRange.max)
	            };
	        }
	    },

	    pointsRange: function(start, end) {
	        var startValue = this.getValue(start);
	        var endValue = this.getValue(end);
	        var min = Math.min(startValue, endValue);
	        var max = Math.max(startValue, endValue);

	        return {
	            min: toDate(min),
	            max: toDate(max)
	        };
	    },

	    zoomRange: function(delta) {
	        var range = this.scaleRange(delta);
	        var min = toDate(limitValue(toTime(range.min), this.totalMin, this.totalMax));
	        var max = toDate(limitValue(toTime(range.max), this.totalMin, this.totalMax));

	        return {
	            min: min,
	            max: max
	        };
	    }
	});

	function timeUnits(delta) {
	    var unit = HOURS;

	    if (delta >= TIME_PER_YEAR) {
	        unit = YEARS;
	    } else if (delta >= TIME_PER_MONTH) {
	        unit = MONTHS;
	    } else if (delta >= TIME_PER_WEEK) {
	        unit = WEEKS;
	    } else if (delta >= TIME_PER_DAY) {
	        unit = DAYS;
	    }

	    return unit;
	}

	function applyDefaults(seriesMin, seriesMax, options) {
	    var min = options.min || seriesMin;
	    var max = options.max || seriesMax;
	    var baseUnit = options.baseUnit || (max && min ? timeUnits(absoluteDateDiff(max, min)) : HOURS);
	    var baseUnitTime = TIME_PER_UNIT[baseUnit];
	    var autoMin = floorDate(toTime(min) - 1, baseUnit) || toDate(max);
	    var autoMax = ceilDate(toTime(max) + 1, baseUnit);
	    var userMajorUnit = options.majorUnit ? options.majorUnit : undefined;
	    var majorUnit = userMajorUnit || ceil(
	                        autoMajorUnit(autoMin.getTime(), autoMax.getTime()),
	                        baseUnitTime
	                    ) / baseUnitTime;
	    var actualUnits = duration(autoMin, autoMax, baseUnit);
	    var totalUnits = ceil(actualUnits, majorUnit);
	    var unitsToAdd = totalUnits - actualUnits;
	    var head = Math.floor(unitsToAdd / 2);
	    var tail = unitsToAdd - head;

	    if (!options.baseUnit) {
	        delete options.baseUnit;
	    }

	    options.baseUnit = options.baseUnit || baseUnit;
	    options.min = options.min || addDuration(autoMin, -head, baseUnit);
	    options.max = options.max || addDuration(autoMax, tail, baseUnit);
	    options.minorUnit = options.minorUnit || majorUnit / 5;
	    options.majorUnit = majorUnit;

	    return options;
	}

	setDefaultOptions(DateValueAxis, {
	    type: DATE,
	    majorGridLines: {
	        visible: true,
	        width: 1,
	        color: BLACK
	    },
	    labels: {
	        dateFormats: DateLabelFormats
	    }
	});

	var DEFAULT_MAJOR_UNIT = 10;

	var LogarithmicAxis = Axis.extend({
	    init: function(seriesMin, seriesMax, options, chartService) {

	        var axisOptions = deepExtend({ majorUnit: DEFAULT_MAJOR_UNIT, min: seriesMin, max: seriesMax }, options);
	        var base = axisOptions.majorUnit;
	        var autoMax = autoAxisMax$1(seriesMax, base);
	        var autoMin = autoAxisMin$1(seriesMin, seriesMax, axisOptions);
	        var range = initRange(autoMin, autoMax, axisOptions, options);

	        axisOptions.max = range.max;
	        axisOptions.min = range.min;
	        axisOptions.minorUnit = options.minorUnit || round(base - 1, DEFAULT_PRECISION);

	        Axis.fn.init.call(this, axisOptions, chartService);

	        this.totalMin = defined(options.min) ? Math.min(autoMin, options.min) : autoMin;
	        this.totalMax = defined(options.max) ? Math.max(autoMax, options.max) : autoMax;
	        this.logMin = round(log(range.min, base), DEFAULT_PRECISION);
	        this.logMax = round(log(range.max, base), DEFAULT_PRECISION);
	        this.seriesMin = seriesMin;
	        this.seriesMax = seriesMax;

	        this.createLabels();
	    },

	    clone: function() {
	        return new LogarithmicAxis(
	            this.seriesMin,
	            this.seriesMax,
	            $.extend({}, this.options),
	            this.chartService
	        );
	    },

	    startValue: function() {
	        return this.options.min;
	    },

	    getSlot: function(a, b, limit) {
	        var ref = this;
	        var options = ref.options;
	        var logMin = ref.logMin;
	        var logMax = ref.logMax;
	        var reverse = options.reverse;
	        var vertical = options.vertical;
	        var base = options.majorUnit;
	        var valueAxis = vertical ? Y : X;
	        var lineBox = this.lineBox();
	        var lineStart = lineBox[valueAxis + (reverse ? 2 : 1)];
	        var lineSize = vertical ? lineBox.height() : lineBox.width();
	        var dir = reverse ? -1 : 1;
	        var step = dir * (lineSize / (logMax - logMin));
	        var slotBox = new Box(lineBox.x1, lineBox.y1, lineBox.x1, lineBox.y1);
	        var start = a;
	        var end = b;

	        if (!defined(start)) {
	            start = end || 1;
	        }

	        if (!defined(end)) {
	            end = start || 1;
	        }

	        if (start <= 0 || end <= 0) {
	            return null;
	        }

	        if (limit) {
	            start = Math.max(Math.min(start, options.max), options.min);
	            end = Math.max(Math.min(end, options.max), options.min);
	        }

	        start = log(start, base);
	        end = log(end, base);

	        var p1, p2;

	        if (vertical) {
	            p1 = logMax - Math.max(start, end);
	            p2 = logMax - Math.min(start, end);
	        } else {
	            p1 = Math.min(start, end) - logMin;
	            p2 = Math.max(start, end) - logMin;
	        }

	        slotBox[valueAxis + 1] = limitCoordinate(lineStart + step * (reverse ? p2 : p1));
	        slotBox[valueAxis + 2] = limitCoordinate(lineStart + step * (reverse ? p1 : p2));

	        return slotBox;
	    },

	    getValue: function(point) {
	        var ref = this;
	        var options = ref.options;
	        var logMin = ref.logMin;
	        var logMax = ref.logMax;
	        var reverse = options.reverse;
	        var vertical = options.vertical;
	        var base = options.majorUnit;
	        var lineBox = this.lineBox();
	        var dir = vertical === reverse ? 1 : -1;
	        var startEdge = dir === 1 ? 1 : 2;
	        var lineSize = vertical ? lineBox.height() : lineBox.width();
	        var step = ((logMax - logMin) / lineSize);
	        var valueAxis = vertical ? Y : X;
	        var lineStart = lineBox[valueAxis + startEdge];
	        var offset = dir * (point[valueAxis] - lineStart);
	        var valueOffset = offset * step;

	        if (offset < 0 || offset > lineSize) {
	            return null;
	        }

	        var value = logMin + valueOffset;

	        return round(Math.pow(base, value), DEFAULT_PRECISION);
	    },

	    range: function() {
	        var options = this.options;
	        return { min: options.min, max: options.max };
	    },

	    scaleRange: function(delta) {
	        var base = this.options.majorUnit;
	        var offset = -delta;

	        return {
	            min: Math.pow(base, this.logMin - offset),
	            max: Math.pow(base, this.logMax + offset)
	        };
	    },

	    translateRange: function(delta) {
	        var ref = this;
	        var options = ref.options;
	        var logMin = ref.logMin;
	        var logMax = ref.logMax;
	        var reverse = options.reverse;
	        var vertical = options.vertical;
	        var base = options.majorUnit;
	        var lineBox = this.lineBox();
	        var size = vertical ? lineBox.height() : lineBox.width();
	        var scale = size / (logMax - logMin);
	        var offset = round(delta / scale, DEFAULT_PRECISION);

	        if ((vertical || reverse) && !(vertical && reverse )) {
	            offset = -offset;
	        }

	        return {
	            min: Math.pow(base, logMin + offset),
	            max: Math.pow(base, logMax + offset),
	            offset: offset
	        };
	    },

	    labelsCount: function() {
	        var floorMax = Math.floor(this.logMax);
	        var count = Math.floor(floorMax - this.logMin) + 1;

	        return count;
	    },

	    getMajorTickPositions: function() {
	        var ticks = [];

	        this.traverseMajorTicksPositions(function (position) {
	            ticks.push(position);
	        }, { step: 1, skip: 0 });

	        return ticks;
	    },

	    createTicks: function(lineGroup) {
	        var options = this.options;
	        var majorTicks = options.majorTicks;
	        var minorTicks = options.minorTicks;
	        var vertical = options.vertical;
	        var mirror = options.labels.mirror;
	        var lineBox = this.lineBox();
	        var ticks = [];
	        var tickLineOptions = {
	            // TODO
	            // _alignLines: options._alignLines,
	            vertical: vertical
	        };

	        function render(tickPosition, tickOptions) {
	            tickLineOptions.tickX = mirror ? lineBox.x2 : lineBox.x2 - tickOptions.size;
	            tickLineOptions.tickY = mirror ? lineBox.y1 - tickOptions.size : lineBox.y1;
	            tickLineOptions.position = tickPosition;

	            lineGroup.append(createAxisTick(tickLineOptions, tickOptions));
	        }

	        if (majorTicks.visible) {
	            this.traverseMajorTicksPositions(render, majorTicks);
	        }

	        if (minorTicks.visible) {
	            this.traverseMinorTicksPositions(render, minorTicks);
	        }

	        return ticks;
	    },

	    createGridLines: function(altAxis) {
	        var options = this.options;
	        var minorGridLines = options.minorGridLines;
	        var majorGridLines = options.majorGridLines;
	        var vertical = options.vertical;
	        var lineBox = altAxis.lineBox();
	        var lineOptions = {
	            lineStart: lineBox[vertical ? "x1" : "y1"],
	            lineEnd: lineBox[vertical ? "x2" : "y2"],
	            vertical: vertical
	        };
	        var majorTicks = [];

	        var container = this.gridLinesVisual();
	        function render(tickPosition, gridLine) {
	            if (!inArray(tickPosition, majorTicks)) {
	                lineOptions.position = tickPosition;
	                container.append(createAxisGridLine(lineOptions, gridLine));

	                majorTicks.push(tickPosition);
	            }
	        }

	        if (majorGridLines.visible) {
	            this.traverseMajorTicksPositions(render, majorGridLines);
	        }

	        if (minorGridLines.visible) {
	            this.traverseMinorTicksPositions(render, minorGridLines);
	        }

	        return container.children;
	    },

	    traverseMajorTicksPositions: function(callback, tickOptions) {
	        var ref = this._lineOptions();
	        var lineStart = ref.lineStart;
	        var step = ref.step;
	        var ref$1 = this;
	        var logMin = ref$1.logMin;
	        var logMax = ref$1.logMax;

	        for (var power = Math.ceil(logMin) + tickOptions.skip; power <= logMax; power += tickOptions.step) {
	            var position = round(lineStart + step * (power - logMin), DEFAULT_PRECISION);
	            callback(position, tickOptions);
	        }
	    },

	    traverseMinorTicksPositions: function(callback, tickOptions) {
	        var this$1 = this;

	        var ref = this.options;
	        var min = ref.min;
	        var max = ref.max;
	        var minorUnit = ref.minorUnit;
	        var base = ref.majorUnit;
	        var ref$1 = this._lineOptions();
	        var lineStart = ref$1.lineStart;
	        var step = ref$1.step;
	        var ref$2 = this;
	        var logMin = ref$2.logMin;
	        var logMax = ref$2.logMax;
	        var start = Math.floor(logMin);

	        for (var power = start; power < logMax; power++) {
	            var minorOptions = this$1._minorIntervalOptions(power);
	            for (var idx = tickOptions.skip; idx < minorUnit; idx += tickOptions.step) {
	                var value = minorOptions.value + idx * minorOptions.minorStep;
	                if (value > max) {
	                    break;
	                }
	                if (value >= min) {
	                    var position = round(lineStart + step * (log(value, base) - logMin), DEFAULT_PRECISION);
	                    callback(position, tickOptions);
	                }
	            }
	        }
	    },

	    createAxisLabel: function(index, labelOptions) {
	        var power = Math.ceil(this.logMin + index);
	        var value = Math.pow(this.options.majorUnit, power);
	        var text = this.axisLabelText(value, null, labelOptions);

	        return new AxisLabel(value, text, index, null, labelOptions);
	    },

	    shouldRenderNote: function(value) {
	        var range = this.range();
	        return range.min <= value && value <= range.max;
	    },

	    pan: function(delta) {
	        var range = this.translateRange(delta);
	        return this.limitRange(range.min, range.max, this.totalMin, this.totalMax, range.offset);
	    },

	    pointsRange: function(start, end) {
	        var startValue = this.getValue(start);
	        var endValue = this.getValue(end);
	        var min = Math.min(startValue, endValue);
	        var max = Math.max(startValue, endValue);

	        return {
	            min: min,
	            max: max
	        };
	    },

	    zoomRange: function(delta) {
	        var ref = this;
	        var options = ref.options;
	        var totalMin = ref.totalMin;
	        var totalMax = ref.totalMax;
	        var newRange = this.scaleRange(delta);
	        var min = limitValue(newRange.min, totalMin, totalMax);
	        var max = limitValue(newRange.max, totalMin, totalMax);
	        var base = options.majorUnit;
	        var acceptOptionsRange = max > min && options.min && options.max && (round(log(options.max, base) - log(options.min, base), DEFAULT_PRECISION) < 1);
	        var acceptNewRange = !(options.min === totalMin && options.max === totalMax) && round(log(max, base) - log(min, base), DEFAULT_PRECISION) >= 1;

	        if (acceptOptionsRange || acceptNewRange) {
	            return {
	                min: min,
	                max: max
	            };
	        }
	    },

	    _minorIntervalOptions: function(power) {
	        var ref = this.options;
	        var minorUnit = ref.minorUnit;
	        var base = ref.majorUnit;
	        var value = Math.pow(base, power);
	        var nextValue = Math.pow(base, power + 1);
	        var difference = nextValue - value;
	        var minorStep = difference / minorUnit;

	        return {
	            value: value,
	            minorStep: minorStep
	        };
	    },

	    _lineOptions: function() {
	        var ref = this.options;
	        var reverse = ref.reverse;
	        var vertical = ref.vertical;
	        var valueAxis = vertical ? Y : X;
	        var lineBox = this.lineBox();
	        var dir = vertical === reverse ? 1 : -1;
	        var startEdge = dir === 1 ? 1 : 2;
	        var lineSize = vertical ? lineBox.height() : lineBox.width();
	        var step = dir * (lineSize / (this.logMax - this.logMin));
	        var lineStart = lineBox[valueAxis + startEdge];

	        return {
	            step: step,
	            lineStart: lineStart,
	            lineBox: lineBox
	        };
	    }
	});

	function initRange(autoMin, autoMax, axisOptions, options) {
	    var min = axisOptions.min;
	    var max = axisOptions.max;

	    if (defined(axisOptions.axisCrossingValue) && axisOptions.axisCrossingValue <= 0) {
	        throwNegativeValuesError();
	    }

	    if (!defined(options.max)) {
	        max = autoMax;
	    } else if (options.max <= 0) {
	        throwNegativeValuesError();
	    }

	    if (!defined(options.min)) {
	        min = autoMin;
	    } else if (options.min <= 0) {
	        throwNegativeValuesError();
	    }

	    return {
	        min: min,
	        max: max
	    };
	}

	function autoAxisMin$1(min, max, options) {
	    var base = options.majorUnit;
	    var autoMin = min;
	    if (min <= 0) {
	        autoMin = max <= 1 ? Math.pow(base, -2) : 1;
	    } else if (!options.narrowRange) {
	        autoMin = Math.pow(base, Math.floor(log(min, base)));
	    }
	    return autoMin;
	}

	function autoAxisMax$1(max, base) {
	    var logMaxRemainder = round(log(max, base), DEFAULT_PRECISION) % 1;
	    var autoMax;
	    if (max <= 0) {
	        autoMax = base;
	    } else if (logMaxRemainder !== 0 && (logMaxRemainder < 0.3 || logMaxRemainder > 0.9)) {
	        autoMax = Math.pow(base, log(max, base) + 0.2);
	    } else {
	        autoMax = Math.pow(base, Math.ceil(log(max, base)));
	    }

	    return autoMax;
	}

	function throwNegativeValuesError() {
	    throw new Error("Non positive values cannot be used for a logarithmic axis");
	}

	function log(y, x) {
	    return Math.log(y) / Math.log(x);
	}

	setDefaultOptions(LogarithmicAxis, {
	    type: "log",
	    majorUnit: DEFAULT_MAJOR_UNIT,
	    minorUnit: 1,
	    axisCrossingValue: 1,
	    vertical: true,
	    majorGridLines: {
	        visible: true,
	        width: 1,
	        color: BLACK
	    },
	    zIndex: 1,
	    _deferLabels: true
	});

	var GridLinesMixin = {
	    createGridLines: function(altAxis) {
	        var options = this.options;
	        var radius = Math.abs(this.box.center().y - altAxis.lineBox().y1);
	        var gridLines = [];
	        var skipMajor = false;
	        var majorAngles, minorAngles;

	        if (options.majorGridLines.visible) {
	            majorAngles = this.majorGridLineAngles(altAxis);
	            skipMajor = true;

	            gridLines = this.renderMajorGridLines(
	                majorAngles, radius, options.majorGridLines
	            );
	        }

	        if (options.minorGridLines.visible) {
	            minorAngles = this.minorGridLineAngles(altAxis, skipMajor);

	            append(gridLines, this.renderMinorGridLines(
	                minorAngles, radius, options.minorGridLines, altAxis, skipMajor
	            ));
	        }

	        return gridLines;
	    },

	    renderMajorGridLines: function(angles, radius, options) {
	        return this.renderGridLines(angles, radius, options);
	    },

	    renderMinorGridLines: function(angles, radius, options, altAxis, skipMajor) {
	        var radiusCallback = this.radiusCallback && this.radiusCallback(radius, altAxis, skipMajor);
	        return this.renderGridLines(angles, radius, options, radiusCallback);
	    },

	    renderGridLines: function(angles, radius, options, radiusCallback) {
	        var style = {
	            stroke: {
	                width: options.width,
	                color: options.color,
	                dashType: options.dashType
	            }
	        };

	        var center = this.box.center();
	        var circle = new Circle([ center.x, center.y ], radius);
	        var container = this.gridLinesVisual();

	        for (var i = 0; i < angles.length; i++) {
	            var line = new Path(style);
	            if (radiusCallback) {
	                circle.radius = radiusCallback(angles[i]);
	            }

	            line.moveTo(circle.center)
	                .lineTo(circle.pointAt(angles[i] + 180));

	            container.append(line);
	        }

	        return container.children;
	    },

	    gridLineAngles: function(altAxis, size, skip, step, skipAngles) {
	        var this$1 = this;

	        var divs = this.intervals(size, skip, step, skipAngles);
	        var options = altAxis.options;
	        var altAxisVisible = options.visible && (options.line || {}).visible !== false;

	        return map(divs, function (d) {
	            var alpha = this$1.intervalAngle(d);

	            if (!altAxisVisible || alpha !== 90) {
	                return alpha;
	            }
	        });
	    }
	};

	var RadarCategoryAxis = CategoryAxis.extend({
	    range: function() {
	        return { min: 0, max: this.options.categories.length };
	    },

	    reflow: function(box) {
	        this.box = box;
	        this.reflowLabels();
	    },

	    lineBox: function() {
	        return this.box;
	    },

	    reflowLabels: function() {
	        var this$1 = this;

	        var ref = this;
	        var labels = ref.labels;
	        var labelOptions = ref.options.labels;
	        var skip = labelOptions.skip || 0;
	        var step = labelOptions.step || 1;
	        var measureBox = new Box();

	        for (var i = 0; i < labels.length; i++) {
	            labels[i].reflow(measureBox);
	            var labelBox = labels[i].box;

	            labels[i].reflow(this$1.getSlot(skip + i * step).adjacentBox(
	                0, labelBox.width(), labelBox.height()
	            ));
	        }
	    },

	    intervals: function(size, skipOption, stepOption, skipAngles) {
	        if (skipAngles === void 0) { skipAngles = false; }

	        var options = this.options;
	        var categories = options.categories.length;
	        var divCount = categories / size || 1;
	        var divAngle = 360 / divCount;
	        var skip = skipOption || 0;
	        var step = stepOption || 1;
	        var divs = [];
	        var angle = 0;

	        for (var i = skip; i < divCount; i += step) {
	            if (options.reverse) {
	                angle = 360 - i * divAngle;
	            } else {
	                angle = i * divAngle;
	            }

	            angle = round(angle, COORD_PRECISION) % 360;

	            if (!(skipAngles && inArray(angle, skipAngles))) {
	                divs.push(angle);
	            }
	        }

	        return divs;
	    },

	    majorIntervals: function() {
	        return this.intervals(1);
	    },

	    minorIntervals: function() {
	        return this.intervals(0.5);
	    },

	    intervalAngle: function(interval) {
	        return (360 + interval + this.options.startAngle) % 360;
	    },

	    majorAngles: function() {
	        var this$1 = this;

	        return map(this.majorIntervals(), function (interval) { return this$1.intervalAngle(interval); });
	    },

	    createLine: function() {
	        return [];
	    },

	    majorGridLineAngles: function(altAxis) {
	        var majorGridLines = this.options.majorGridLines;
	        return this.gridLineAngles(altAxis, 1, majorGridLines.skip, majorGridLines.step);
	    },

	    minorGridLineAngles: function(altAxis, skipMajor) {
	        var ref = this.options;
	        var minorGridLines = ref.minorGridLines;
	        var majorGridLines = ref.majorGridLines;
	        var majorGridLineAngles = skipMajor ? this.intervals(1, majorGridLines.skip, majorGridLines.step) : null;

	        return this.gridLineAngles(altAxis, 0.5, minorGridLines.skip, minorGridLines.step, majorGridLineAngles);
	    },

	    radiusCallback: function(radius, altAxis, skipMajor) {
	        if (altAxis.options.type !== ARC) {
	            var minorAngle = rad(360 / (this.options.categories.length * 2));
	            var minorRadius = Math.cos(minorAngle) * radius;
	            var majorAngles = this.majorAngles();

	            var radiusCallback = function(angle) {
	                if (!skipMajor && inArray(angle, majorAngles)) {
	                    return radius;
	                }

	                return minorRadius;
	            };
	            return radiusCallback;
	        }
	    },

	    createPlotBands: function() {
	        var this$1 = this;

	        var plotBands = this.options.plotBands || [];

	        var group = this._plotbandGroup = new Group({
	            zIndex: -1
	        });

	        for (var i = 0; i < plotBands.length; i++) {
	            var band = plotBands[i];
	            var slot = this$1.plotBandSlot(band);
	            var singleSlot = this$1.getSlot(band.from);

	            var head = band.from - Math.floor(band.from);
	            slot.startAngle += head * singleSlot.angle;

	            var tail = Math.ceil(band.to) - band.to;
	            slot.angle -= (tail + head) * singleSlot.angle;

	            var ring = ShapeBuilder.current.createRing(slot, {
	                fill: {
	                    color: band.color,
	                    opacity: band.opacity
	                },
	                stroke: {
	                    opacity: band.opacity
	                }
	            });
	            group.append(ring);
	        }

	        this.appendVisual(group);
	    },

	    plotBandSlot: function(band) {
	        return this.getSlot(band.from, band.to - 1);
	    },

	    getSlot: function(from, to) {
	        var options = this.options;
	        var justified = options.justified;
	        var box = this.box;
	        var divs = this.majorAngles();
	        var totalDivs = divs.length;
	        var slotAngle = 360 / totalDivs;
	        var fromValue = from;

	        if (options.reverse && !justified) {
	            fromValue = (fromValue + 1) % totalDivs;
	        }

	        fromValue = limitValue(Math.floor(fromValue), 0, totalDivs - 1);
	        var slotStart = divs[fromValue];

	        if (justified) {
	            slotStart = slotStart - slotAngle / 2;

	            if (slotStart < 0) {
	                slotStart += 360;
	            }
	        }

	        var toValue = limitValue(Math.ceil(to || fromValue), fromValue, totalDivs - 1);
	        var slots = toValue - fromValue + 1;
	        var angle = slotAngle * slots;

	        return new Ring(box.center(), 0, box.height() / 2, slotStart, angle);
	    },

	    slot: function(from, to) {
	        var slot = this.getSlot(from, to);
	        var startAngle = slot.startAngle + 180;
	        var endAngle = startAngle + slot.angle;

	        return new geometry.Arc([ slot.center.x, slot.center.y ], {
	            startAngle: startAngle,
	            endAngle: endAngle,
	            radiusX: slot.radius,
	            radiusY: slot.radius
	        });
	    },

	    pointCategoryIndex: function(point) {
	        var this$1 = this;

	        var length = this.options.categories.length;
	        var index = null;

	        for (var i = 0; i < length; i++) {
	            var slot = this$1.getSlot(i);
	            if (slot.containsPoint(point)) {
	                index = i;
	                break;
	            }
	        }

	        return index;
	    }
	});

	setDefaultOptions(RadarCategoryAxis, {
	    startAngle: 90,
	    labels: {
	        margin: getSpacing(10)
	    },
	    majorGridLines: {
	        visible: true
	    },
	    justified: true
	});
	deepExtend(RadarCategoryAxis.prototype, GridLinesMixin);

	var PolarAxis = Axis.extend({
	    init: function(options, chartService) {
	        Axis.fn.init.call(this, options, chartService);

	        var instanceOptions = this.options;

	        instanceOptions.minorUnit = instanceOptions.minorUnit || instanceOptions.majorUnit / 2;
	    },

	    getDivisions: function(stepValue) {
	        return NumericAxis.prototype.getDivisions.call(this, stepValue) - 1;
	    },

	    reflow: function(box) {
	        this.box = box;
	        this.reflowLabels();
	    },

	    reflowLabels: function() {
	        var this$1 = this;

	        var ref = this;
	        var options = ref.options;
	        var labels = ref.labels;
	        var labelOptions = ref.options.labels;
	        var skip = labelOptions.skip || 0;
	        var step = labelOptions.step || 1;

	        var measureBox = new Box();
	        var divs = this.intervals(options.majorUnit, skip, step);

	        for (var i = 0; i < labels.length; i++) {
	            labels[i].reflow(measureBox);
	            var labelBox = labels[i].box;

	            labels[i].reflow(this$1.getSlot(divs[i]).adjacentBox(0, labelBox.width(), labelBox.height()));
	        }
	    },

	    lineBox: function() {
	        return this.box;
	    },

	    intervals: function(size, skipOption, stepOption, skipAngles) {
	        if (skipAngles === void 0) { skipAngles = false; }

	        var min = this.options.min;
	        var divisions = this.getDivisions(size);
	        var divs = [];
	        var skip = skipOption || 0;
	        var step = stepOption || 1;

	        for (var i = skip; i < divisions; i += step) {
	            var current = (360 + min + i * size) % 360;
	            if (!(skipAngles && inArray(current, skipAngles))) {
	                divs.push(current);
	            }
	        }

	        return divs;
	    },

	    majorIntervals: function() {
	        return this.intervals(this.options.majorUnit);
	    },

	    minorIntervals: function() {
	        return this.intervals(this.options.minorUnit);
	    },

	    intervalAngle: function(i) {
	        return (540 - i - this.options.startAngle) % 360;
	    },

	    createLine: function() {
	        return [];
	    },

	    majorGridLineAngles: function(altAxis) {
	        var majorGridLines = this.options.majorGridLines;
	        return this.gridLineAngles(altAxis, this.options.majorUnit, majorGridLines.skip, majorGridLines.step);
	    },

	    minorGridLineAngles: function(altAxis, skipMajor) {
	        var options = this.options;
	        var minorGridLines = options.minorGridLines;
	        var majorGridLines = options.majorGridLines;
	        var majorGridLineAngles = skipMajor ? this.intervals(options.majorUnit, majorGridLines.skip, majorGridLines.step) : null;

	        return this.gridLineAngles(altAxis, options.minorUnit, minorGridLines.skip, minorGridLines.step, majorGridLineAngles);
	    },

	    plotBandSlot: function(band) {
	        return this.getSlot(band.from, band.to);
	    },

	    getSlot: function(a, b) {
	        var ref = this;
	        var options = ref.options;
	        var box = ref.box;
	        var startAngle = options.startAngle;
	        var start = limitValue(a, options.min, options.max);
	        var end = limitValue(b || start, start, options.max);

	        if (options.reverse) {
	            start *= -1;
	            end *= -1;
	        }

	        start = (540 - start - startAngle) % 360;
	        end = (540 - end - startAngle) % 360;

	        if (end < start) {
	            var tmp = start;
	            start = end;
	            end = tmp;
	        }

	        return new Ring(box.center(), 0, box.height() / 2, start, end - start);
	    },

	    slot: function(from, to) {
	        if (to === void 0) { to = from; }

	        var options = this.options;
	        var start = 360 - options.startAngle;
	        var slot = this.getSlot(from, to);
	        var min = Math.min(from, to);
	        var max = Math.max(from, to);
	        var startAngle, endAngle;

	        if (options.reverse) {
	            startAngle = min;
	            endAngle = max;
	        } else {
	            startAngle = 360 - max;
	            endAngle = 360 - min;
	        }

	        startAngle = (startAngle + start) % 360;
	        endAngle = (endAngle + start) % 360;

	        return new geometry.Arc([ slot.center.x, slot.center.y ], {
	            startAngle: startAngle,
	            endAngle: endAngle,
	            radiusX: slot.radius,
	            radiusY: slot.radius
	        });
	    },

	    getValue: function(point) {
	        var options = this.options;
	        var center = this.box.center();
	        var dx = point.x - center.x;
	        var dy = point.y - center.y;
	        var theta = Math.round(deg(Math.atan2(dy, dx)));
	        var start = options.startAngle;

	        if (!options.reverse) {
	            theta *= -1;
	            start *= -1;
	        }

	        return (theta + start + 360) % 360;
	    },

	    valueRange: function() {
	        return {
	            min: 0,
	            max: Math.PI * 2
	        };
	    }
	});

	setDefaultOptions(PolarAxis, {
	    type: "polar",
	    startAngle: 0,
	    reverse: false,
	    majorUnit: 60,
	    min: 0,
	    max: 360,
	    labels: {
	        margin: getSpacing(10)
	    },
	    majorGridLines: {
	        color: BLACK,
	        visible: true,
	        width: 1
	    },
	    minorGridLines: {
	        color: "#aaa"
	    }
	});

	deepExtend(PolarAxis.prototype, GridLinesMixin, {
	    createPlotBands: RadarCategoryAxis.prototype.createPlotBands,
	    majorAngles: RadarCategoryAxis.prototype.majorAngles,
	    range: NumericAxis.prototype.range,
	    labelsCount: NumericAxis.prototype.labelsCount,
	    createAxisLabel: NumericAxis.prototype.createAxisLabel
	});

	var RadarNumericAxisMixin = {
	    options: {
	        majorGridLines: {
	            visible: true
	        }
	    },

	    createPlotBands: function() {
	        var this$1 = this;

	        var ref = this.options;
	        var type = ref.majorGridLines.type;
	        var plotBands = ref.plotBands; if (plotBands === void 0) { plotBands = []; }
	        var altAxis = this.plotArea.polarAxis;
	        var majorAngles = altAxis.majorAngles();
	        var center = altAxis.box.center();
	        var group = this._plotbandGroup = new Group({
	            zIndex: -1
	        });

	        for (var i = 0; i < plotBands.length; i++) {
	            var band = plotBands[i];
	            var bandStyle = {
	                fill: {
	                    color: band.color,
	                    opacity: band.opacity
	                },
	                stroke: {
	                    opacity: band.opacity
	                }
	            };

	            var slot = this$1.getSlot(band.from, band.to, true);
	            var ring = new Ring(center, center.y - slot.y2, center.y - slot.y1, 0, 360);

	            var shape = (void 0);
	            if (type === ARC) {
	                shape = ShapeBuilder.current.createRing(ring, bandStyle);
	            } else {
	                shape = Path.fromPoints(this$1.plotBandPoints(ring, majorAngles), bandStyle).close();
	            }

	            group.append(shape);
	        }

	        this.appendVisual(group);
	    },

	    plotBandPoints: function(ring, angles) {
	        var innerPoints = [];
	        var outerPoints = [];
	        var center = [ ring.center.x, ring.center.y ];
	        var innerCircle = new Circle(center, ring.innerRadius);
	        var outerCircle = new Circle(center, ring.radius);

	        for (var i = 0; i < angles.length; i++) {
	            innerPoints.push(innerCircle.pointAt(angles[i] + 180));
	            outerPoints.push(outerCircle.pointAt(angles[i] + 180));
	        }

	        innerPoints.reverse();
	        innerPoints.push(innerPoints[0]);
	        outerPoints.push(outerPoints[0]);

	        return outerPoints.concat(innerPoints);
	    },

	    createGridLines: function(altAxis) {
	        var options = this.options;
	        var majorTicks = this.radarMajorGridLinePositions();
	        var majorAngles = altAxis.majorAngles();
	        var center = altAxis.box.center();
	        var gridLines = [];

	        if (options.majorGridLines.visible) {
	            gridLines = this.renderGridLines(
	                center, majorTicks, majorAngles, options.majorGridLines
	            );
	        }

	        if (options.minorGridLines.visible) {
	            var minorTicks = this.radarMinorGridLinePositions();
	            append(gridLines, this.renderGridLines(
	                center, minorTicks, majorAngles, options.minorGridLines
	            ));
	        }

	        return gridLines;
	    },

	    renderGridLines: function(center, ticks, angles, options) {
	        var style = {
	            stroke: {
	                width: options.width,
	                color: options.color,
	                dashType: options.dashType
	            }
	        };
	        var skip = options.skip; if (skip === void 0) { skip = 0; }
	        var step = options.step; if (step === void 0) { step = 0; }
	        var container = this.gridLinesVisual();

	        for (var tickIx = skip; tickIx < ticks.length; tickIx += step) {
	            var tickRadius = center.y - ticks[tickIx];
	            if (tickRadius > 0) {
	                var circle = new Circle([ center.x, center.y ], tickRadius);
	                if (options.type === ARC) {
	                    container.append(new drawing.Circle(circle, style));
	                } else {
	                    var line = new Path(style);
	                    for (var angleIx = 0; angleIx < angles.length; angleIx++) {
	                        line.lineTo(circle.pointAt(angles[angleIx] + 180));
	                    }

	                    line.close();
	                    container.append(line);
	                }
	            }
	        }

	        return container.children;
	    },

	    getValue: function(point) {
	        var lineBox = this.lineBox();
	        var altAxis = this.plotArea.polarAxis;
	        var majorAngles = altAxis.majorAngles();
	        var center = altAxis.box.center();
	        var radius = point.distanceTo(center);
	        var distance = radius;

	        if (this.options.majorGridLines.type !== ARC && majorAngles.length > 1) {
	            var dx = point.x - center.x;
	            var dy = point.y - center.y;
	            var theta = (deg(Math.atan2(dy, dx)) + 540) % 360;

	            majorAngles.sort(function(a, b) {
	                return angularDistance(a, theta) - angularDistance(b, theta);
	            });

	            // Solve triangle (center, point, axis X) using one side (radius) and two angles.
	            // Angles are derived from triangle (center, point, gridline X)
	            var midAngle = angularDistance(majorAngles[0], majorAngles[1]) / 2;
	            var alpha = angularDistance(theta, majorAngles[0]);
	            var gamma = 90 - midAngle;
	            var beta = 180 - alpha - gamma;

	            distance = radius * (Math.sin(rad(beta)) / Math.sin(rad(gamma)));
	        }

	        return this.axisType().prototype.getValue.call(
	            this, new Point(lineBox.x1, lineBox.y2 - distance)
	        );
	    }
	};

	function angularDistance(a, b) {
	    return 180 - Math.abs(Math.abs(a - b) - 180);
	}

	var RadarNumericAxis = NumericAxis.extend({
	    radarMajorGridLinePositions: function() {
	        return this.getTickPositions(this.options.majorUnit);
	    },

	    radarMinorGridLinePositions: function() {
	        var options = this.options;
	        var minorSkipStep = 0;

	        if (options.majorGridLines.visible) {
	            minorSkipStep = options.majorUnit;
	        }
	        return this.getTickPositions(options.minorUnit, minorSkipStep);
	    },

	    axisType: function() {
	        return NumericAxis;
	    }
	});

	deepExtend(RadarNumericAxis.prototype, RadarNumericAxisMixin);

	var RadarLogarithmicAxis = LogarithmicAxis.extend({
	    radarMajorGridLinePositions: function() {
	        var positions = [];

	        this.traverseMajorTicksPositions(function(position) {
	            positions.push(position);
	        }, this.options.majorGridLines);

	        return positions;
	    },

	    radarMinorGridLinePositions: function() {
	        var positions = [];

	        this.traverseMinorTicksPositions(function(position) {
	            positions.push(position);
	        }, this.options.minorGridLines);

	        return positions;
	    },

	    axisType: function() {
	        return LogarithmicAxis;
	    }
	});

	deepExtend(RadarLogarithmicAxis.prototype, RadarNumericAxisMixin);

	var WEIGHT = 0.333;
	var EXTREMUM_ALLOWED_DEVIATION = 0.01;

	var CurveProcessor = Class.extend({
	    init: function(closed) {

	        this.closed = closed;
	    },

	    process: function(dataPoints) {
	        var this$1 = this;

	        var points = dataPoints.slice(0);
	        var segments = [];
	        var closed = this.closed;
	        var length = points.length;

	        if (length > 2) {
	            this.removeDuplicates(0, points);
	            length = points.length;
	        }

	        if (length < 2 || (length === 2 && points[0].equals(points[1]))) {
	            return segments;
	        }

	        var p0 = points[0];
	        var p1 = points[1];
	        var p2 = points[2];

	        segments.push(new Segment(p0));

	        while (p0.equals(points[length - 1])) {
	            closed = true;
	            points.pop();
	            length--;
	        }

	        if (length === 2) {
	            var tangent = this.tangent(p0,p1, X, Y);

	            last(segments).controlOut(
	                this.firstControlPoint(tangent, p0, p1, X, Y)
	            );

	            segments.push(new Segment(
	                p1,
	                this.secondControlPoint(tangent, p0, p1, X, Y)
	            ));

	            return segments;
	        }

	        var initialControlPoint, lastControlPoint;

	        if (closed) {
	            p0 = points[length - 1]; p1 = points[0]; p2 = points[1];
	            var controlPoints = this.controlPoints(p0, p1, p2);
	            initialControlPoint = controlPoints[1];
	            lastControlPoint = controlPoints[0];
	        } else {
	            var tangent$1 = this.tangent(p0, p1, X,Y);
	            initialControlPoint = this.firstControlPoint(tangent$1, p0, p1, X, Y);
	        }

	        var cp0 = initialControlPoint;
	        for (var idx = 0; idx <= length - 3; idx++) {
	            this$1.removeDuplicates(idx, points);
	            length = points.length;
	            if (idx + 3 <= length) {
	                p0 = points[idx]; p1 = points[idx + 1]; p2 = points[idx + 2];
	                var controlPoints$1 = this$1.controlPoints(p0,p1,p2);

	                last(segments).controlOut(cp0);
	                cp0 = controlPoints$1[1];

	                var cp1 = controlPoints$1[0];
	                segments.push(new Segment(p1, cp1));
	            }
	        }

	        if (closed) {
	            p0 = points[length - 2]; p1 = points[length - 1]; p2 = points[0];
	            var controlPoints$2 = this.controlPoints(p0, p1, p2);

	            last(segments).controlOut(cp0);
	            segments.push(new Segment(
	                p1,
	                controlPoints$2[0]
	            ));

	            last(segments).controlOut(controlPoints$2[1]);
	            segments.push(new Segment(
	                p2,
	                lastControlPoint
	            ));
	        } else {
	            var tangent$2 = this.tangent(p1, p2, X, Y);

	            last(segments).controlOut(cp0);
	            segments.push(new Segment(
	                p2,
	                this.secondControlPoint(tangent$2, p1, p2, X, Y)
	            ));
	        }

	        return segments;
	    },

	    removeDuplicates: function(idx, points) {
	        while (points[idx + 1] && (points[idx].equals(points[idx + 1]) || points[idx + 1].equals(points[idx + 2]))) {
	            points.splice(idx + 1, 1);
	        }
	    },

	    invertAxis: function(p0, p1, p2) {
	        var invertAxis = false;

	        if (p0.x === p1.x) {
	            invertAxis = true;
	        } else if (p1.x === p2.x) {
	            if ((p1.y < p2.y && p0.y <= p1.y) || (p2.y < p1.y && p1.y <= p0.y)) {
	                invertAxis = true;
	            }
	        } else {
	            var fn = this.lineFunction(p0,p1);
	            var y2 = this.calculateFunction(fn, p2.x);
	            if (!(p0.y <= p1.y && p2.y <= y2) &&
	                !(p1.y <= p0.y && p2.y >= y2)) {
	                invertAxis = true;
	            }
	        }

	        return invertAxis;
	    },

	    isLine: function(p0, p1, p2) {
	        var fn = this.lineFunction(p0, p1);
	        var y2 = this.calculateFunction(fn, p2.x);

	        return (p0.x === p1.x && p1.x === p2.x) || round(y2, 1) === round(p2.y, 1);
	    },

	    lineFunction: function(p1, p2) {
	        var a = (p2.y - p1.y) / (p2.x - p1.x);
	        var b = p1.y - a * p1.x;

	        return [ b, a ];
	    },

	    controlPoints: function(p0, p1, p2) {
	        var xField = X;
	        var yField = Y;
	        var restrict = false;
	        var switchOrientation = false;
	        var tangent;

	        if (this.isLine(p0, p1, p2)) {
	            tangent = this.tangent(p0, p1, X, Y);
	        } else {
	            var monotonic = {
	                x: this.isMonotonicByField(p0, p1, p2, X),
	                y: this.isMonotonicByField(p0, p1, p2, Y)
	            };

	            if (monotonic.x && monotonic.y) {
	                tangent = this.tangent(p0, p2, X, Y);
	                restrict = true;
	            } else {
	                if (this.invertAxis(p0, p1, p2)) {
	                    xField = Y;
	                    yField = X;
	                }

	                if (monotonic[xField]) {
	                    tangent = 0;
	                } else {
	                    var sign;
	                    if ((p2[yField] < p0[yField] && p0[yField] <= p1[yField]) ||
	                        (p0[yField] < p2[yField] && p1[yField] <= p0[yField])) {
	                        sign = numberSign((p2[yField] - p0[yField]) * (p1[xField] - p0[xField]));
	                    } else {
	                        sign = -numberSign((p2[xField] - p0[xField]) * (p1[yField] - p0[yField]));
	                    }

	                    tangent = EXTREMUM_ALLOWED_DEVIATION * sign;
	                    switchOrientation = true;
	                }
	            }
	        }

	        var secondControlPoint = this.secondControlPoint(tangent, p0, p1, xField, yField);

	        if (switchOrientation) {
	            var oldXField = xField;
	            xField = yField;
	            yField = oldXField;
	        }

	        var firstControlPoint = this.firstControlPoint(tangent, p1, p2, xField, yField);

	        if (restrict) {
	            this.restrictControlPoint(p0, p1, secondControlPoint, tangent);
	            this.restrictControlPoint(p1, p2, firstControlPoint, tangent);
	        }

	        return [ secondControlPoint, firstControlPoint ];
	    },

	    restrictControlPoint: function(p1, p2, cp, tangent) {
	        if (p1.y < p2.y) {
	            if (p2.y < cp.y) {
	                cp.x = p1.x + (p2.y - p1.y) / tangent;
	                cp.y = p2.y;
	            } else if (cp.y < p1.y) {
	                cp.x = p2.x - (p2.y - p1.y) / tangent;
	                cp.y = p1.y;
	            }
	        } else {
	            if (cp.y < p2.y) {
	                cp.x = p1.x - (p1.y - p2.y) / tangent;
	                cp.y = p2.y;
	            } else if (p1.y < cp.y) {
	                cp.x = p2.x + (p1.y - p2.y) / tangent;
	                cp.y = p1.y;
	            }
	        }
	    },

	    tangent: function(p0, p1, xField, yField) {
	        var x = p1[xField] - p0[xField];
	        var y = p1[yField] - p0[yField];
	        var tangent;

	        if (x === 0) {
	            tangent = 0;
	        } else {
	            tangent = y / x;
	        }

	        return tangent;
	    },

	    isMonotonicByField: function(p0, p1, p2, field) {
	        return (p2[field] > p1[field] && p1[field] > p0[field]) ||
	                    (p2[field] < p1[field] && p1[field] < p0[field]);
	    },

	    firstControlPoint: function(tangent, p0, p3, xField, yField) {
	        var t1 = p0[xField];
	        var t2 = p3[xField];
	        var distance = (t2 - t1) * WEIGHT;

	        return this.point(t1 + distance, p0[yField] + distance * tangent, xField, yField);
	    },

	    secondControlPoint: function(tangent, p0, p3, xField, yField) {
	        var t1 = p0[xField];
	        var t2 = p3[xField];
	        var distance = (t2 - t1) * WEIGHT;

	        return this.point(t2 - distance, p3[yField] - distance * tangent, xField, yField);
	    },

	    point: function(xValue, yValue, xField, yField) {
	        var controlPoint = new geometry.Point();
	        controlPoint[xField] = xValue;
	        controlPoint[yField] = yValue;

	        return controlPoint;
	    },

	    calculateFunction: function(fn, x) {
	        var length = fn.length;
	        var result = 0;

	        for (var i = 0; i < length; i++) {
	            result += Math.pow(x,i) * fn[i];
	        }
	        return result;
	    }
	});

	function numberSign(value) {
	    return value <= 0 ? -1 : 1;
	}

	dataviz.Gradients = GRADIENTS;

	kendo.deepExtend(kendo.dataviz, {
	    constants: constants,
	    services: services,
	    autoMajorUnit: autoMajorUnit,
	    Point: Point,
	    Box: Box,
	    Ring: Ring,
	    Sector: Sector,
	    ShapeBuilder: ShapeBuilder,
	    ShapeElement: ShapeElement,
	    ChartElement: ChartElement,
	    BoxElement: BoxElement,
	    RootElement: RootElement,
	    FloatElement: FloatElement,
	    Text: Text,
	    TextBox: TextBox,
	    Title: Title,
	    AxisLabel: AxisLabel,
	    Axis: Axis,
	    Note: Note,
	    CategoryAxis: CategoryAxis,
	    DateCategoryAxis: DateCategoryAxis,
	    DateValueAxis: DateValueAxis,
	    NumericAxis: NumericAxis,
	    LogarithmicAxis: LogarithmicAxis,
	    PolarAxis: PolarAxis,
	    RadarCategoryAxis: RadarCategoryAxis,
	    RadarNumericAxis: RadarNumericAxis,
	    RadarLogarithmicAxis: RadarLogarithmicAxis,
	    CurveProcessor: CurveProcessor,
	    rectToBox: rectToBox,
	    addClass: addClass,
	    removeClass: removeClass,
	    alignPathToPixel: alignPathToPixel,
	    clockwise: clockwise,
	    convertableToNumber: convertableToNumber,
	    deepExtend: deepExtend,
	    elementStyles: elementStyles,
	    getSpacing: getSpacing,
	    getTemplate: getTemplate,
	    getter: __common_getter_js,
	    grep: grep,
	    hasClasses: hasClasses,
	    HashMap: HashMap,
	    inArray: inArray,
	    interpolateValue: interpolateValue,
	    InstanceObserver: InstanceObserver,
	    isArray: isArray,
	    isFunction: isFunction,
	    isNumber: isNumber,
	    isObject: isObject,
	    isString: isString,
	    map: map,
	    mousewheelDelta: mousewheelDelta,
	    FontLoader: FontLoader,
	    setDefaultOptions: setDefaultOptions,
	    sparseArrayLimits: sparseArrayLimits,
	    styleValue: styleValue,
	    append: append,
	    bindEvents: bindEvents,
	    Class: Class,
	    defined: defined,
	    deg: deg,
	    elementOffset: elementOffset,
	    elementSize: elementSize,
	    eventElement: eventElement,
	    eventCoordinates: eventCoordinates,
	    last: last,
	    limitValue: limitValue,
	    logToConsole: kendo.logToConsole,
	    objectKey: objectKey,
	    rad: rad,
	    round: round,
	    unbindEvents: unbindEvents,
	    valueOrDefault: valueOrDefault,
	    absoluteDateDiff: absoluteDateDiff,
	    addDuration: addDuration,
	    addTicks: addTicks,
	    ceilDate: ceilDate,
	    dateComparer: dateComparer,
	    dateDiff: dateDiff,
	    dateEquals: dateEquals,
	    dateIndex: dateIndex,
	    duration: duration,
	    floorDate: floorDate,
	    lteDateIndex: lteDateIndex,
	    startOfWeek: startOfWeek,
	    toDate: toDate,
	    parseDate: parseDate,
	    parseDates: parseDates,
	    toTime: toTime
	});

	})(window.kendo.jQuery);

	}, __webpack_require__(3));

/***/ })

/******/ });