EnVisageOnline/Main/Source/EnVisage/Scripts/Kendo/Unused/kendo.dataviz.chart.js

10592 lines
481 KiB
JavaScript

/**
* Kendo UI v2016.1.226 (http://www.telerik.com/kendo-ui)
* Copyright 2016 Telerik AD. All rights reserved.
*
* Kendo UI commercial licenses may be obtained at
* http://www.telerik.com/purchase/license-agreement/kendo-ui-complete
* If you do not own a commercial license, this file shall be governed by the trial license terms.
*/
(function (f, define) {
define('util/main', ['kendo.core'], f);
}(function () {
(function () {
var math = Math, kendo = window.kendo, deepExtend = kendo.deepExtend;
var DEG_TO_RAD = math.PI / 180, MAX_NUM = Number.MAX_VALUE, MIN_NUM = -Number.MAX_VALUE, UNDEFINED = 'undefined';
function defined(value) {
return typeof value !== UNDEFINED;
}
function round(value, precision) {
var power = pow(precision);
return math.round(value * power) / power;
}
function pow(p) {
if (p) {
return math.pow(10, p);
} else {
return 1;
}
}
function limitValue(value, min, max) {
return math.max(math.min(value, max), min);
}
function rad(degrees) {
return degrees * DEG_TO_RAD;
}
function deg(radians) {
return radians / DEG_TO_RAD;
}
function isNumber(val) {
return typeof val === 'number' && !isNaN(val);
}
function valueOrDefault(value, defaultValue) {
return defined(value) ? value : defaultValue;
}
function sqr(value) {
return value * value;
}
function objectKey(object) {
var parts = [];
for (var key in object) {
parts.push(key + object[key]);
}
return parts.sort().join('');
}
function hashKey(str) {
var hash = 2166136261;
for (var i = 0; i < str.length; ++i) {
hash += (hash << 1) + (hash << 4) + (hash << 7) + (hash << 8) + (hash << 24);
hash ^= str.charCodeAt(i);
}
return hash >>> 0;
}
function hashObject(object) {
return hashKey(objectKey(object));
}
var now = Date.now;
if (!now) {
now = function () {
return new Date().getTime();
};
}
function arrayLimits(arr) {
var length = arr.length, i, min = MAX_NUM, max = MIN_NUM;
for (i = 0; i < length; i++) {
max = math.max(max, arr[i]);
min = math.min(min, arr[i]);
}
return {
min: min,
max: max
};
}
function arrayMin(arr) {
return arrayLimits(arr).min;
}
function arrayMax(arr) {
return arrayLimits(arr).max;
}
function sparseArrayMin(arr) {
return sparseArrayLimits(arr).min;
}
function sparseArrayMax(arr) {
return sparseArrayLimits(arr).max;
}
function sparseArrayLimits(arr) {
var min = MAX_NUM, max = MIN_NUM;
for (var i = 0, length = arr.length; i < length; i++) {
var n = arr[i];
if (n !== null && isFinite(n)) {
min = math.min(min, n);
max = math.max(max, n);
}
}
return {
min: min === MAX_NUM ? undefined : min,
max: max === MIN_NUM ? undefined : max
};
}
function last(array) {
if (array) {
return array[array.length - 1];
}
}
function append(first, second) {
first.push.apply(first, second);
return first;
}
function renderTemplate(text) {
return kendo.template(text, {
useWithBlock: false,
paramName: 'd'
});
}
function renderAttr(name, value) {
return defined(value) && value !== null ? ' ' + name + '=\'' + value + '\' ' : '';
}
function renderAllAttr(attrs) {
var output = '';
for (var i = 0; i < attrs.length; i++) {
output += renderAttr(attrs[i][0], attrs[i][1]);
}
return output;
}
function renderStyle(attrs) {
var output = '';
for (var i = 0; i < attrs.length; i++) {
var value = attrs[i][1];
if (defined(value)) {
output += attrs[i][0] + ':' + value + ';';
}
}
if (output !== '') {
return output;
}
}
function renderSize(size) {
if (typeof size !== 'string') {
size += 'px';
}
return size;
}
function renderPos(pos) {
var result = [];
if (pos) {
var parts = kendo.toHyphens(pos).split('-');
for (var i = 0; i < parts.length; i++) {
result.push('k-pos-' + parts[i]);
}
}
return result.join(' ');
}
function isTransparent(color) {
return color === '' || color === null || color === 'none' || color === 'transparent' || !defined(color);
}
function arabicToRoman(n) {
var literals = {
1: 'i',
10: 'x',
100: 'c',
2: 'ii',
20: 'xx',
200: 'cc',
3: 'iii',
30: 'xxx',
300: 'ccc',
4: 'iv',
40: 'xl',
400: 'cd',
5: 'v',
50: 'l',
500: 'd',
6: 'vi',
60: 'lx',
600: 'dc',
7: 'vii',
70: 'lxx',
700: 'dcc',
8: 'viii',
80: 'lxxx',
800: 'dccc',
9: 'ix',
90: 'xc',
900: 'cm',
1000: 'm'
};
var values = [
1000,
900,
800,
700,
600,
500,
400,
300,
200,
100,
90,
80,
70,
60,
50,
40,
30,
20,
10,
9,
8,
7,
6,
5,
4,
3,
2,
1
];
var roman = '';
while (n > 0) {
if (n < values[0]) {
values.shift();
} else {
roman += literals[values[0]];
n -= values[0];
}
}
return roman;
}
function romanToArabic(r) {
r = r.toLowerCase();
var digits = {
i: 1,
v: 5,
x: 10,
l: 50,
c: 100,
d: 500,
m: 1000
};
var value = 0, prev = 0;
for (var i = 0; i < r.length; ++i) {
var v = digits[r.charAt(i)];
if (!v) {
return null;
}
value += v;
if (v > prev) {
value -= 2 * prev;
}
prev = v;
}
return value;
}
function memoize(f) {
var cache = Object.create(null);
return function () {
var id = '';
for (var i = arguments.length; --i >= 0;) {
id += ':' + arguments[i];
}
if (id in cache) {
return cache[id];
}
return f.apply(this, arguments);
};
}
function ucs2decode(string) {
var output = [], counter = 0, length = string.length, value, extra;
while (counter < length) {
value = string.charCodeAt(counter++);
if (value >= 55296 && value <= 56319 && counter < length) {
extra = string.charCodeAt(counter++);
if ((extra & 64512) == 56320) {
output.push(((value & 1023) << 10) + (extra & 1023) + 65536);
} else {
output.push(value);
counter--;
}
} else {
output.push(value);
}
}
return output;
}
function ucs2encode(array) {
return array.map(function (value) {
var output = '';
if (value > 65535) {
value -= 65536;
output += String.fromCharCode(value >>> 10 & 1023 | 55296);
value = 56320 | value & 1023;
}
output += String.fromCharCode(value);
return output;
}).join('');
}
deepExtend(kendo, {
util: {
MAX_NUM: MAX_NUM,
MIN_NUM: MIN_NUM,
append: append,
arrayLimits: arrayLimits,
arrayMin: arrayMin,
arrayMax: arrayMax,
defined: defined,
deg: deg,
hashKey: hashKey,
hashObject: hashObject,
isNumber: isNumber,
isTransparent: isTransparent,
last: last,
limitValue: limitValue,
now: now,
objectKey: objectKey,
round: round,
rad: rad,
renderAttr: renderAttr,
renderAllAttr: renderAllAttr,
renderPos: renderPos,
renderSize: renderSize,
renderStyle: renderStyle,
renderTemplate: renderTemplate,
sparseArrayLimits: sparseArrayLimits,
sparseArrayMin: sparseArrayMin,
sparseArrayMax: sparseArrayMax,
sqr: sqr,
valueOrDefault: valueOrDefault,
romanToArabic: romanToArabic,
arabicToRoman: arabicToRoman,
memoize: memoize,
ucs2encode: ucs2encode,
ucs2decode: ucs2decode
}
});
kendo.drawing.util = kendo.util;
kendo.dataviz.util = kendo.util;
}());
return window.kendo;
}, typeof define == 'function' && define.amd ? define : function (a1, a2, a3) {
(a3 || a2)();
}));
(function (f, define) {
define('util/text-metrics', [
'kendo.core',
'util/main'
], f);
}(function () {
(function ($) {
var doc = document, kendo = window.kendo, Class = kendo.Class, util = kendo.util, defined = util.defined;
var LRUCache = Class.extend({
init: function (size) {
this._size = size;
this._length = 0;
this._map = {};
},
put: function (key, value) {
var lru = this, map = lru._map, entry = {
key: key,
value: value
};
map[key] = entry;
if (!lru._head) {
lru._head = lru._tail = entry;
} else {
lru._tail.newer = entry;
entry.older = lru._tail;
lru._tail = entry;
}
if (lru._length >= lru._size) {
map[lru._head.key] = null;
lru._head = lru._head.newer;
lru._head.older = null;
} else {
lru._length++;
}
},
get: function (key) {
var lru = this, entry = lru._map[key];
if (entry) {
if (entry === lru._head && entry !== lru._tail) {
lru._head = entry.newer;
lru._head.older = null;
}
if (entry !== lru._tail) {
if (entry.older) {
entry.older.newer = entry.newer;
entry.newer.older = entry.older;
}
entry.older = lru._tail;
entry.newer = null;
lru._tail.newer = entry;
lru._tail = entry;
}
return entry.value;
}
}
});
var defaultMeasureBox = $('<div style=\'position: absolute !important; top: -4000px !important; width: auto !important; height: auto !important;' + 'padding: 0 !important; margin: 0 !important; border: 0 !important;' + 'line-height: normal !important; visibility: hidden !important; white-space: nowrap!important;\' />')[0];
function zeroSize() {
return {
width: 0,
height: 0,
baseline: 0
};
}
var TextMetrics = Class.extend({
init: function (options) {
this._cache = new LRUCache(1000);
this._initOptions(options);
},
options: { baselineMarkerSize: 1 },
measure: function (text, style, box) {
if (!text) {
return zeroSize();
}
var styleKey = util.objectKey(style), cacheKey = util.hashKey(text + styleKey), cachedResult = this._cache.get(cacheKey);
if (cachedResult) {
return cachedResult;
}
var size = zeroSize();
var measureBox = box ? box : defaultMeasureBox;
var baselineMarker = this._baselineMarker().cloneNode(false);
for (var key in style) {
var value = style[key];
if (defined(value)) {
measureBox.style[key] = value;
}
}
$(measureBox).text(text);
measureBox.appendChild(baselineMarker);
doc.body.appendChild(measureBox);
if ((text + '').length) {
size.width = measureBox.offsetWidth - this.options.baselineMarkerSize;
size.height = measureBox.offsetHeight;
size.baseline = baselineMarker.offsetTop + this.options.baselineMarkerSize;
}
if (size.width > 0 && size.height > 0) {
this._cache.put(cacheKey, size);
}
measureBox.parentNode.removeChild(measureBox);
return size;
},
_baselineMarker: function () {
return $('<div class=\'k-baseline-marker\' ' + 'style=\'display: inline-block; vertical-align: baseline;' + 'width: ' + this.options.baselineMarkerSize + 'px; height: ' + this.options.baselineMarkerSize + 'px;' + 'overflow: hidden;\' />')[0];
}
});
TextMetrics.current = new TextMetrics();
function measureText(text, style, measureBox) {
return TextMetrics.current.measure(text, style, measureBox);
}
function loadFonts(fonts, callback) {
var promises = [];
if (fonts.length > 0 && document.fonts) {
try {
promises = fonts.map(function (font) {
return document.fonts.load(font);
});
} catch (e) {
kendo.logToConsole(e);
}
Promise.all(promises).then(callback, callback);
} else {
callback();
}
}
kendo.util.TextMetrics = TextMetrics;
kendo.util.LRUCache = LRUCache;
kendo.util.loadFonts = loadFonts;
kendo.util.measureText = measureText;
}(window.kendo.jQuery));
}, typeof define == 'function' && define.amd ? define : function (a1, a2, a3) {
(a3 || a2)();
}));
(function (f, define) {
define('util/base64', ['util/main'], f);
}(function () {
(function () {
var kendo = window.kendo, deepExtend = kendo.deepExtend, fromCharCode = String.fromCharCode;
var KEY_STR = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
function encodeBase64(input) {
var output = '';
var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
var i = 0;
input = encodeUTF8(input);
while (i < input.length) {
chr1 = input.charCodeAt(i++);
chr2 = input.charCodeAt(i++);
chr3 = input.charCodeAt(i++);
enc1 = chr1 >> 2;
enc2 = (chr1 & 3) << 4 | chr2 >> 4;
enc3 = (chr2 & 15) << 2 | chr3 >> 6;
enc4 = chr3 & 63;
if (isNaN(chr2)) {
enc3 = enc4 = 64;
} else if (isNaN(chr3)) {
enc4 = 64;
}
output = output + KEY_STR.charAt(enc1) + KEY_STR.charAt(enc2) + KEY_STR.charAt(enc3) + KEY_STR.charAt(enc4);
}
return output;
}
function encodeUTF8(input) {
var output = '';
for (var i = 0; i < input.length; i++) {
var c = input.charCodeAt(i);
if (c < 128) {
output += fromCharCode(c);
} else if (c < 2048) {
output += fromCharCode(192 | c >>> 6);
output += fromCharCode(128 | c & 63);
} else if (c < 65536) {
output += fromCharCode(224 | c >>> 12);
output += fromCharCode(128 | c >>> 6 & 63);
output += fromCharCode(128 | c & 63);
}
}
return output;
}
deepExtend(kendo.util, {
encodeBase64: encodeBase64,
encodeUTF8: encodeUTF8
});
}());
return window.kendo;
}, typeof define == 'function' && define.amd ? define : function (a1, a2, a3) {
(a3 || a2)();
}));
(function (f, define) {
define('mixins/observers', ['kendo.core'], f);
}(function () {
(function ($) {
var math = Math, kendo = window.kendo, deepExtend = kendo.deepExtend, inArray = $.inArray;
var ObserversMixin = {
observers: function () {
this._observers = this._observers || [];
return this._observers;
},
addObserver: function (element) {
if (!this._observers) {
this._observers = [element];
} else {
this._observers.push(element);
}
return this;
},
removeObserver: function (element) {
var observers = this.observers();
var index = inArray(element, observers);
if (index != -1) {
observers.splice(index, 1);
}
return this;
},
trigger: function (methodName, event) {
var observers = this._observers;
var observer;
var idx;
if (observers && !this._suspended) {
for (idx = 0; idx < observers.length; idx++) {
observer = observers[idx];
if (observer[methodName]) {
observer[methodName](event);
}
}
}
return this;
},
optionsChange: function (e) {
this.trigger('optionsChange', e);
},
geometryChange: function (e) {
this.trigger('geometryChange', e);
},
suspend: function () {
this._suspended = (this._suspended || 0) + 1;
return this;
},
resume: function () {
this._suspended = math.max((this._suspended || 0) - 1, 0);
return this;
},
_observerField: function (field, value) {
if (this[field]) {
this[field].removeObserver(this);
}
this[field] = value;
value.addObserver(this);
}
};
deepExtend(kendo, { mixins: { ObserversMixin: ObserversMixin } });
}(window.kendo.jQuery));
return window.kendo;
}, typeof define == 'function' && define.amd ? define : function (a1, a2, a3) {
(a3 || a2)();
}));
(function (f, define) {
define('kendo.dataviz.chart', [
'kendo.color',
'kendo.data',
'kendo.dataviz.core',
'kendo.dataviz.themes',
'kendo.drawing',
'kendo.userevents'
], f);
}(function () {
var __meta__ = {
id: 'dataviz.chart',
name: 'Chart',
category: 'dataviz',
description: 'The Chart widget uses modern browser technologies to render high-quality data visualizations in the browser.',
depends: [
'data',
'userevents',
'drawing',
'dataviz.core',
'dataviz.themes'
],
features: [
{
id: 'dataviz.chart-polar',
name: 'Polar & Radar',
description: 'Support for Polar and Radar charts.',
depends: ['dataviz.chart.polar'],
requireJS: false
},
{
id: 'dataviz.chart-funnel',
name: 'Funnel chart',
description: 'Support for Funnel chart.',
depends: ['dataviz.chart.funnel'],
requireJS: false
},
{
id: 'dataviz.chart-pdf-export',
name: 'PDF export',
description: 'Export Chart as PDF',
depends: ['pdf']
}
]
};
(function ($, undefined) {
var each = $.each, isArray = $.isArray, isPlainObject = $.isPlainObject, map = $.map, math = Math, noop = $.noop, extend = $.extend, proxy = $.proxy, kendo = window.kendo, Class = kendo.Class, Observable = kendo.Observable, DataSource = kendo.data.DataSource, Widget = kendo.ui.Widget, deepExtend = kendo.deepExtend, getter = kendo.getter, isFn = kendo.isFunction, template = kendo.template, dataviz = kendo.dataviz, Axis = dataviz.Axis, AxisLabel = dataviz.AxisLabel, Box2D = dataviz.Box2D, BoxElement = dataviz.BoxElement, ChartElement = dataviz.ChartElement, Color = kendo.drawing.Color, CurveProcessor = dataviz.CurveProcessor, FloatElement = dataviz.FloatElement, Note = dataviz.Note, LogarithmicAxis = dataviz.LogarithmicAxis, NumericAxis = dataviz.NumericAxis, Point2D = dataviz.Point2D, RootElement = dataviz.RootElement, Ring = dataviz.Ring, ShapeElement = dataviz.ShapeElement, ShapeBuilder = dataviz.ShapeBuilder, TextBox = dataviz.TextBox, Title = dataviz.Title, alignPathToPixel = dataviz.alignPathToPixel, autoFormat = dataviz.autoFormat, dateComparer = dataviz.dateComparer, getSpacing = dataviz.getSpacing, inArray = dataviz.inArray, interpolate = dataviz.interpolateValue, mwDelta = dataviz.mwDelta, round = dataviz.round, util = kendo.util, append = util.append, defined = util.defined, last = util.last, limitValue = util.limitValue, sparseArrayLimits = util.sparseArrayLimits, sparseArrayMin = util.sparseArrayMin, sparseArrayMax = util.sparseArrayMax, renderTemplate = util.renderTemplate, valueOrDefault = util.valueOrDefault, geom = dataviz.geometry, draw = dataviz.drawing;
var NS = '.kendoChart', ABOVE = 'above', AREA = 'area', AUTO = 'auto', FIT = 'fit', AXIS_LABEL_CLICK = dataviz.AXIS_LABEL_CLICK, BAR = 'bar', BAR_ALIGN_MIN_WIDTH = 6, BAR_BORDER_BRIGHTNESS = 0.8, BELOW = 'below', BLACK = '#000', BOTH = 'both', BOTTOM = 'bottom', BOX_PLOT = 'boxPlot', BUBBLE = 'bubble', BULLET = 'bullet', CANDLESTICK = 'candlestick', CATEGORY = 'category', CENTER = 'center', CHANGE = 'change', CIRCLE = 'circle', CONTEXTMENU_NS = 'contextmenu' + NS, CLIP = dataviz.CLIP, COLOR = 'color', COLUMN = 'column', COORD_PRECISION = dataviz.COORD_PRECISION, CROSS = 'cross', CSS_PREFIX = 'k-', CUSTOM = 'custom', DATABOUND = 'dataBound', DATE = 'date', DAYS = 'days', DEFAULT_FONT = dataviz.DEFAULT_FONT, DEFAULT_HEIGHT = dataviz.DEFAULT_HEIGHT, DEFAULT_PRECISION = dataviz.DEFAULT_PRECISION, DEFAULT_WIDTH = dataviz.DEFAULT_WIDTH, DEFAULT_ERROR_BAR_WIDTH = 4, DONUT = 'donut', DONUT_SECTOR_ANIM_DELAY = 50, DRAG = 'drag', DRAG_END = 'dragEnd', DRAG_START = 'dragStart', ERROR_LOW_FIELD = 'errorLow', ERROR_HIGH_FIELD = 'errorHigh', X_ERROR_LOW_FIELD = 'xErrorLow', X_ERROR_HIGH_FIELD = 'xErrorHigh', Y_ERROR_LOW_FIELD = 'yErrorLow', Y_ERROR_HIGH_FIELD = 'yErrorHigh', FADEIN = 'fadeIn', FIRST = 'first', FROM = 'from', FUNNEL = 'funnel', GLASS = 'glass', HORIZONTAL = 'horizontal', HORIZONTAL_WATERFALL = 'horizontalWaterfall', HOURS = 'hours', INITIAL_ANIMATION_DURATION = dataviz.INITIAL_ANIMATION_DURATION, INSIDE_BASE = 'insideBase', INSIDE_END = 'insideEnd', INTERPOLATE = 'interpolate', LEAVE = 'leave', LEFT = 'left', LEGEND_ITEM_CLICK = 'legendItemClick', LEGEND_ITEM_HOVER = 'legendItemHover', LINE = 'line', LINE_MARKER_SIZE = 8, LINEAR = 'linear', LOGARITHMIC = 'log', MAX = 'max', MAX_EXPAND_DEPTH = 5, MAX_VALUE = Number.MAX_VALUE, MIN = 'min', MIN_VALUE = -Number.MAX_VALUE, MINUTES = 'minutes', MONTHS = 'months', MOUSELEAVE_NS = 'mouseleave' + NS, MOUSEMOVE_TRACKING = 'mousemove.tracking', MOUSEOVER_NS = 'mouseover' + NS, MOUSEOUT_NS = 'mouseout' + NS, MOUSEMOVE_NS = 'mousemove' + NS, MOUSEMOVE_DELAY = 20, MOUSEWHEEL_DELAY = 150, MOUSEWHEEL_NS = 'DOMMouseScroll' + NS + ' mousewheel' + NS, NOTE_CLICK = dataviz.NOTE_CLICK, NOTE_HOVER = dataviz.NOTE_HOVER, NOTE_TEXT = 'noteText', OBJECT = 'object', OHLC = 'ohlc', OUTSIDE_END = 'outsideEnd', PIE = 'pie', PIE_SECTOR_ANIM_DELAY = 70, PLOT_AREA_CLICK = 'plotAreaClick', POINTER = 'pointer', RANGE_BAR = 'rangeBar', RANGE_COLUMN = 'rangeColumn', RENDER = 'render', RIGHT = 'right', ROUNDED_BEVEL = 'roundedBevel', ROUNDED_GLASS = 'roundedGlass', SCATTER = 'scatter', SCATTER_LINE = 'scatterLine', SECONDS = 'seconds', SELECT_START = 'selectStart', SELECT = 'select', SELECT_END = 'selectEnd', SERIES_CLICK = 'seriesClick', SERIES_HOVER = 'seriesHover', START_SCALE = 0.001, STEP = 'step', SMOOTH = 'smooth', STD_ERR = 'stderr', STD_DEV = 'stddev', STRING = 'string', SUMMARY_FIELD = 'summary', TIME_PER_SECOND = 1000, TIME_PER_MINUTE = 60 * TIME_PER_SECOND, TIME_PER_HOUR = 60 * TIME_PER_MINUTE, TIME_PER_DAY = 24 * TIME_PER_HOUR, TIME_PER_WEEK = 7 * TIME_PER_DAY, TIME_PER_MONTH = 31 * TIME_PER_DAY, TIME_PER_YEAR = 365 * TIME_PER_DAY, 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
}, TO = 'to', TOP = 'top', TOOLTIP_ANIMATION_DURATION = 150, TOOLTIP_OFFSET = 5, TOOLTIP_SHOW_DELAY = 100, TOOLTIP_HIDE_DELAY = 100, TOOLTIP_INVERSE = 'chart-tooltip-inverse', VALUE = 'value', VERTICAL_AREA = 'verticalArea', VERTICAL_BULLET = 'verticalBullet', VERTICAL_LINE = 'verticalLine', WATERFALL = 'waterfall', WEEKS = 'weeks', WHITE = '#fff', X = 'x', Y = 'y', YEARS = 'years', ZERO = 'zero', ZOOM_ACCELERATION = 3, ZOOM_START = 'zoomStart', ZOOM = 'zoom', ZOOM_END = 'zoomEnd', BASE_UNITS = [
SECONDS,
MINUTES,
HOURS,
DAYS,
WEEKS,
MONTHS,
YEARS
], EQUALLY_SPACED_SERIES = [
BAR,
COLUMN,
OHLC,
CANDLESTICK,
BOX_PLOT,
BULLET,
RANGE_COLUMN,
RANGE_BAR,
WATERFALL,
HORIZONTAL_WATERFALL
];
var DateLabelFormats = {
seconds: 'HH:mm:ss',
minutes: 'HH:mm',
hours: 'HH:mm',
days: 'M/d',
weeks: 'M/d',
months: 'MMM \'yy',
years: 'yyyy'
};
var Chart = Widget.extend({
init: function (element, userOptions) {
var chart = this, options, dataSource;
kendo.destroy(element);
Widget.fn.init.call(chart, element);
chart.element.addClass(CSS_PREFIX + this.options.name.toLowerCase()).css('position', 'relative');
if (userOptions) {
dataSource = userOptions.dataSource;
userOptions.dataSource = undefined;
}
options = deepExtend({}, chart.options, userOptions);
chart._originalOptions = deepExtend({}, options);
chart._initTheme(options);
chart._initSurface();
chart.bind(chart.events, chart.options);
chart.wrapper = chart.element;
if (userOptions) {
userOptions.dataSource = dataSource;
}
chart._initDataSource(userOptions);
kendo.notify(chart, dataviz.ui);
},
_initTheme: function (options) {
var chart = this, themes = dataviz.ui.themes || {}, themeName = options.theme, theme = themes[themeName] || themes[themeName.toLowerCase()], themeOptions = themeName && theme ? theme.chart : {}, seriesCopies = [], series = options.series || [], i;
for (i = 0; i < series.length; i++) {
seriesCopies.push($.extend({}, series[i]));
}
options.series = seriesCopies;
resolveAxisAliases(options);
chart._applyDefaults(options, themeOptions);
if (options.seriesColors === null) {
options.seriesColors = undefined;
}
chart.options = deepExtend({}, themeOptions, options);
applySeriesColors(chart.options);
},
_initDataSource: function (userOptions) {
var chart = this, dataSource = (userOptions || {}).dataSource;
chart._dataChangeHandler = proxy(chart._onDataChanged, chart);
chart.dataSource = DataSource.create(dataSource).bind(CHANGE, chart._dataChangeHandler);
chart._bindCategories();
if (dataSource) {
chart._hasDataSource = true;
}
preloadFonts(userOptions, function () {
chart._redraw();
chart._attachEvents();
});
if (dataSource) {
if (chart.options.autoBind) {
chart.dataSource.fetch();
}
}
},
setDataSource: function (dataSource) {
var chart = this;
chart.dataSource.unbind(CHANGE, chart._dataChangeHandler);
chart.dataSource = dataSource = DataSource.create(dataSource);
chart._hasDataSource = true;
chart._hasData = false;
dataSource.bind(CHANGE, chart._dataChangeHandler);
if (chart.options.autoBind) {
dataSource.fetch();
}
},
events: [
DATABOUND,
SERIES_CLICK,
SERIES_HOVER,
AXIS_LABEL_CLICK,
LEGEND_ITEM_CLICK,
LEGEND_ITEM_HOVER,
PLOT_AREA_CLICK,
DRAG_START,
DRAG,
DRAG_END,
ZOOM_START,
ZOOM,
ZOOM_END,
SELECT_START,
SELECT,
SELECT_END,
NOTE_CLICK,
NOTE_HOVER,
RENDER
],
items: function () {
return $();
},
options: {
name: 'Chart',
renderAs: '',
theme: 'default',
chartArea: {},
legend: {
visible: true,
labels: {}
},
categoryAxis: {},
autoBind: true,
seriesDefaults: {
type: COLUMN,
data: [],
highlight: { visible: true },
labels: {},
negativeValues: { visible: false }
},
series: [],
seriesColors: null,
tooltip: { visible: false },
transitions: true,
valueAxis: {},
plotArea: {},
title: {},
xAxis: {},
yAxis: {},
panes: [{}],
pannable: false,
zoomable: false
},
refresh: function () {
var chart = this;
chart._applyDefaults(chart.options);
applySeriesColors(chart.options);
chart._bindSeries();
chart._bindCategories();
chart.trigger(DATABOUND);
chart._redraw();
},
getSize: function () {
return kendo.dimensions(this.element);
},
_resize: function () {
var t = this.options.transitions;
this.options.transitions = false;
this._redraw();
this.options.transitions = t;
},
redraw: function (paneName) {
var chart = this, pane, plotArea;
chart._applyDefaults(chart.options);
applySeriesColors(chart.options);
if (paneName) {
plotArea = chart._model._plotArea;
pane = plotArea.findPane(paneName);
plotArea.redraw(pane);
} else {
chart._redraw();
}
},
getAxis: function (name) {
var axes = this._plotArea.axes;
for (var idx = 0; idx < axes.length; idx++) {
if (axes[idx].options.name === name) {
return new ChartAxis(axes[idx]);
}
}
},
toggleHighlight: function (show, options) {
var plotArea = this._plotArea;
var highlight = this._highlight;
var firstSeries = (plotArea.srcSeries || plotArea.series || [])[0];
var seriesName, categoryName, points;
if (isPlainObject(options)) {
seriesName = options.series;
categoryName = options.category;
} else {
seriesName = categoryName = options;
}
if (firstSeries.type === DONUT) {
points = pointByCategoryName(plotArea.pointsBySeriesName(seriesName), categoryName);
} else if (firstSeries.type === PIE || firstSeries.type === FUNNEL) {
points = pointByCategoryName((plotArea.charts[0] || {}).points, categoryName);
} else {
points = plotArea.pointsBySeriesName(seriesName);
}
if (points) {
for (var idx = 0; idx < points.length; idx++) {
highlight.togglePointHighlight(points[idx], show);
}
}
},
_initSurface: function () {
var surface = this.surface;
var wrap = this._surfaceWrap();
var chartArea = this.options.chartArea;
if (chartArea.width) {
wrap.css('width', chartArea.width);
}
if (chartArea.height) {
wrap.css('height', chartArea.height);
}
if (!surface || surface.options.type !== this.options.renderAs) {
if (surface) {
surface.destroy();
}
this.surface = draw.Surface.create(wrap, { type: this.options.renderAs });
} else {
this.surface.clear();
this.surface.resize();
}
},
_surfaceWrap: function () {
return this.element;
},
_redraw: function () {
var chart = this, model = chart._getModel(), view;
chart._destroyView();
chart._model = model;
chart._plotArea = model._plotArea;
model.renderVisual();
if (this.options.transitions !== false) {
model.traverse(function (element) {
if (element.animation) {
element.animation.setup();
}
});
}
chart._initSurface();
chart.surface.draw(model.visual);
if (this.options.transitions !== false) {
model.traverse(function (element) {
if (element.animation) {
element.animation.play();
}
});
}
chart._tooltip = chart._createTooltip();
chart._highlight = new Highlight(view);
chart._setupSelection();
chart._createPannable();
chart._createZoomSelection();
chart._createMousewheelZoom();
if (!chart._hasDataSource || chart._hasData || !chart.options.autoBind) {
chart.trigger(RENDER);
}
},
exportVisual: function (options) {
var visual;
if (options && (options.width || options.height)) {
var chartArea = this.options.chartArea;
var originalChartArea = this._originalOptions.chartArea;
deepExtend(chartArea, options);
var model = this._getModel();
chartArea.width = originalChartArea.width;
chartArea.height = originalChartArea.height;
model.renderVisual();
visual = model.visual;
} else {
visual = this.surface.exportVisual();
}
return visual;
},
_sharedTooltip: function () {
var chart = this, options = chart.options;
return chart._plotArea instanceof CategoricalPlotArea && options.tooltip.shared;
},
_createPannable: function () {
var options = this.options;
if (options.pannable !== false) {
this._pannable = new Pannable(this._plotArea, options.pannable);
}
},
_createZoomSelection: function () {
var zoomable = this.options.zoomable;
var selection = (zoomable || {}).selection;
if (zoomable !== false && selection !== false) {
this._zoomSelection = new ZoomSelection(this, selection);
}
},
_createMousewheelZoom: function () {
var zoomable = this.options.zoomable;
var mousewheel = (zoomable || {}).mousewheel;
if (zoomable !== false && mousewheel !== false) {
this._mousewheelZoom = new MousewheelZoom(this, mousewheel);
}
},
_createTooltip: function () {
var chart = this, options = chart.options, element = chart.element, tooltip;
if (chart._sharedTooltip()) {
tooltip = new SharedTooltip(element, chart._plotArea, options.tooltip);
} else {
tooltip = new Tooltip(element, options.tooltip);
}
tooltip.bind(LEAVE, proxy(chart._tooltipleave, chart));
return tooltip;
},
_tooltipleave: function () {
var chart = this, plotArea = chart._plotArea, highlight = chart._highlight;
plotArea.hideCrosshairs();
highlight.hide();
},
_applyDefaults: function (options, themeOptions) {
applyAxisDefaults(options, themeOptions);
applySeriesDefaults(options, themeOptions);
},
_getModel: function () {
var chart = this, options = chart.options, model = new RootElement(chart._modelOptions()), plotArea;
model.chart = chart;
Title.buildTitle(options.title, model);
plotArea = model._plotArea = chart._createPlotArea();
if (options.legend.visible) {
model.append(new Legend(plotArea.options.legend));
}
model.append(plotArea);
model.reflow();
return model;
},
_modelOptions: function () {
var chart = this, options = chart.options, element = chart.element, height = math.floor(element.height()), width = math.floor(element.width());
chart._size = null;
return deepExtend({
width: width || DEFAULT_WIDTH,
height: height || DEFAULT_HEIGHT,
transitions: options.transitions
}, options.chartArea);
},
_createPlotArea: function (skipSeries) {
var chart = this, options = chart.options;
return PlotAreaFactory.current.create(skipSeries ? [] : options.series, options);
},
_setupSelection: function () {
var chart = this, plotArea = chart._plotArea, axes = plotArea.axes, selections = chart._selections = [], selection, i, axis, min, max, options;
if (!chart._selectStartHandler) {
chart._selectStartHandler = proxy(chart._selectStart, chart);
chart._selectHandler = proxy(chart._select, chart);
chart._selectEndHandler = proxy(chart._selectEnd, chart);
}
for (i = 0; i < axes.length; i++) {
axis = axes[i];
options = axis.options;
if (axis instanceof CategoryAxis && options.select && !options.vertical) {
min = 0;
max = options.categories.length - 1;
if (axis instanceof DateCategoryAxis) {
min = options.categories[min];
max = options.categories[max];
}
if (!options.justified) {
if (axis instanceof DateCategoryAxis) {
max = addDuration(max, 1, options.baseUnit, options.weekStartDay);
} else {
max++;
}
}
selection = new Selection(chart, axis, deepExtend({
min: min,
max: max
}, options.select));
selection.bind(SELECT_START, chart._selectStartHandler);
selection.bind(SELECT, chart._selectHandler);
selection.bind(SELECT_END, chart._selectEndHandler);
selections.push(selection);
}
}
},
_selectStart: function (e) {
return this.trigger(SELECT_START, e);
},
_select: function (e) {
return this.trigger(SELECT, e);
},
_selectEnd: function (e) {
return this.trigger(SELECT_END, e);
},
_attachEvents: function () {
var chart = this, element = chart.element;
element.on(CONTEXTMENU_NS, proxy(chart._click, chart));
element.on(MOUSEOVER_NS, proxy(chart._mouseover, chart));
element.on(MOUSEOUT_NS, proxy(chart._mouseout, chart));
element.on(MOUSEWHEEL_NS, proxy(chart._mousewheel, chart));
element.on(MOUSELEAVE_NS, proxy(chart._mouseleave, chart));
chart._mousemove = kendo.throttle(proxy(chart._mousemove, chart), MOUSEMOVE_DELAY);
if (chart._shouldAttachMouseMove()) {
element.on(MOUSEMOVE_NS, chart._mousemove);
}
if (kendo.UserEvents) {
chart._userEvents = new kendo.UserEvents(element, {
global: true,
filter: ':not(.k-selector)',
multiTouch: false,
fastTap: true,
tap: proxy(chart._tap, chart),
start: proxy(chart._start, chart),
move: proxy(chart._move, chart),
end: proxy(chart._end, chart)
});
}
},
_mouseout: function (e) {
var chart = this, element = chart._getChartElement(e);
if (element && element.leave) {
element.leave(chart, e);
}
},
_start: function (e) {
var chart = this, events = chart._events;
if (defined(events[DRAG_START] || events[DRAG] || events[DRAG_END])) {
chart._startNavigation(e, DRAG_START);
}
if (chart._pannable) {
chart._pannable.start(e);
}
if (chart._zoomSelection) {
chart._zoomSelection.start(e);
}
},
_move: function (e) {
var chart = this, state = chart._navState, pannable = chart._pannable, axes, ranges = {}, i, currentAxis, axisName, axis, delta;
if (pannable) {
e.preventDefault();
ranges = pannable.move(e);
if (ranges && !chart.trigger(DRAG, {
axisRanges: ranges,
originalEvent: e
})) {
pannable.pan();
}
} else if (state) {
e.preventDefault();
axes = state.axes;
for (i = 0; i < axes.length; i++) {
currentAxis = axes[i];
axisName = currentAxis.options.name;
if (axisName) {
axis = currentAxis.options.vertical ? e.y : e.x;
delta = axis.startLocation - axis.location;
if (delta !== 0) {
ranges[currentAxis.options.name] = currentAxis.translateRange(delta);
}
}
}
state.axisRanges = ranges;
chart.trigger(DRAG, {
axisRanges: ranges,
originalEvent: e
});
}
if (chart._zoomSelection) {
chart._zoomSelection.move(e);
}
},
_end: function (e) {
this._endNavigation(e, DRAG_END);
if (this._zoomSelection) {
var ranges = this._zoomSelection.end(e);
if (ranges && !this.trigger(ZOOM, {
axisRanges: ranges,
originalEvent: e
})) {
this._zoomSelection.zoom();
}
}
if (this._pannable) {
this._pannable.end(e);
}
},
_mousewheel: function (e) {
var chart = this, origEvent = e.originalEvent, prevented, delta = mwDelta(e), totalDelta, state = chart._navState, axes, i, currentAxis, axisName, ranges = {}, mousewheelZoom = chart._mousewheelZoom;
if (mousewheelZoom) {
e.preventDefault();
ranges = mousewheelZoom.updateRanges(delta);
if (ranges && !chart.trigger(ZOOM, {
delta: delta,
axisRanges: ranges,
originalEvent: e
})) {
mousewheelZoom.zoom();
}
} else {
if (!state) {
prevented = chart._startNavigation(origEvent, ZOOM_START);
if (!prevented) {
state = chart._navState;
}
}
if (state) {
totalDelta = state.totalDelta || delta;
state.totalDelta = totalDelta + delta;
axes = chart._navState.axes;
for (i = 0; i < axes.length; i++) {
currentAxis = axes[i];
axisName = currentAxis.options.name;
if (axisName) {
ranges[axisName] = currentAxis.scaleRange(-totalDelta);
}
}
chart.trigger(ZOOM, {
delta: delta,
axisRanges: ranges,
originalEvent: e
});
if (chart._mwTimeout) {
clearTimeout(chart._mwTimeout);
}
chart._mwTimeout = setTimeout(function () {
chart._endNavigation(e, ZOOM_END);
}, MOUSEWHEEL_DELAY);
}
}
},
_startNavigation: function (e, chartEvent) {
var chart = this, coords = chart._eventCoordinates(e), plotArea = chart._model._plotArea, pane = plotArea.findPointPane(coords), axes = plotArea.axes.slice(0), i, currentAxis, inAxis = false, prevented;
if (!pane) {
return;
}
for (i = 0; i < axes.length; i++) {
currentAxis = axes[i];
if (currentAxis.box.containsPoint(coords)) {
inAxis = true;
break;
}
}
if (!inAxis && plotArea.backgroundBox().containsPoint(coords)) {
prevented = chart.trigger(chartEvent, {
axisRanges: axisRanges(axes),
originalEvent: e
});
if (prevented) {
chart._userEvents.cancel();
} else {
chart._suppressHover = true;
chart._unsetActivePoint();
chart._navState = {
pane: pane,
axes: axes
};
}
}
},
_endNavigation: function (e, chartEvent) {
var chart = this;
if (chart._navState) {
chart.trigger(chartEvent, {
axisRanges: chart._navState.axisRanges,
originalEvent: e
});
chart._suppressHover = false;
chart._navState = null;
}
},
_getChartElement: function (e, match) {
var element = this.surface.eventTarget(e);
if (!element) {
return;
}
var chartElement;
while (element && !chartElement) {
chartElement = element.chartElement;
element = element.parent;
}
if (chartElement) {
if (chartElement.aliasFor) {
chartElement = chartElement.aliasFor(e, this._eventCoordinates(e));
}
if (match) {
chartElement = chartElement.closest(match);
}
return chartElement;
}
},
_eventCoordinates: function (e) {
var chart = this, isTouch = defined((e.x || {}).client), clientX = isTouch ? e.x.client : e.clientX, clientY = isTouch ? e.y.client : e.clientY;
return chart._toModelCoordinates(clientX, clientY);
},
_toModelCoordinates: function (clientX, clientY) {
var element = this.element, offset = element.offset(), paddingLeft = parseInt(element.css('paddingLeft'), 10), paddingTop = parseInt(element.css('paddingTop'), 10), win = $(window);
return new Point2D(clientX - offset.left - paddingLeft + win.scrollLeft(), clientY - offset.top - paddingTop + win.scrollTop());
},
_tap: function (e) {
var chart = this, element = chart._getChartElement(e);
if (chart._activePoint === element) {
chart._click(e);
} else {
if (!chart._startHover(e)) {
chart._unsetActivePoint();
}
chart._click(e);
}
},
_click: function (e) {
var chart = this, element = chart._getChartElement(e);
while (element) {
if (element.click) {
element.click(chart, e);
}
element = element.parent;
}
},
_startHover: function (e) {
var chart = this, element = chart._getChartElement(e), tooltip = chart._tooltip, highlight = chart._highlight, tooltipOptions = chart.options.tooltip, point;
if (chart._suppressHover || !highlight || highlight.isHighlighted(element) || chart._sharedTooltip()) {
return;
}
point = chart._getChartElement(e, function (element) {
return element.hover;
});
if (point && !point.hover(chart, e)) {
chart._activePoint = point;
tooltipOptions = deepExtend({}, tooltipOptions, point.options.tooltip);
if (tooltipOptions.visible) {
tooltip.show(point);
}
highlight.show(point);
return point.tooltipTracking;
}
},
_mouseover: function (e) {
var chart = this;
if (chart._startHover(e)) {
$(document).on(MOUSEMOVE_TRACKING, proxy(chart._mouseMoveTracking, chart));
}
},
_mouseMoveTracking: function (e) {
var chart = this, options = chart.options, tooltip = chart._tooltip, highlight = chart._highlight, coords = chart._eventCoordinates(e), point = chart._activePoint, tooltipOptions, seriesPoint;
if (chart._plotArea.box.containsPoint(coords)) {
if (point && point.tooltipTracking && point.series && point.parent.getNearestPoint) {
seriesPoint = point.parent.getNearestPoint(coords.x, coords.y, point.seriesIx);
if (seriesPoint && seriesPoint != point) {
seriesPoint.hover(chart, e);
chart._activePoint = seriesPoint;
tooltipOptions = deepExtend({}, options.tooltip, point.options.tooltip);
if (tooltipOptions.visible) {
tooltip.show(seriesPoint);
}
highlight.show(seriesPoint);
}
}
} else {
$(document).off(MOUSEMOVE_TRACKING);
chart._unsetActivePoint();
}
},
_mousemove: function (e) {
var coords = this._eventCoordinates(e);
this._trackCrosshairs(coords);
if (this._sharedTooltip()) {
this._trackSharedTooltip(coords, e);
}
},
_trackCrosshairs: function (coords) {
var crosshairs = this._plotArea.crosshairs, i, current;
for (i = 0; i < crosshairs.length; i++) {
current = crosshairs[i];
if (current.box.containsPoint(coords)) {
current.showAt(coords);
} else {
current.hide();
}
}
},
_trackSharedTooltip: function (coords, e) {
var chart = this, options = chart.options, plotArea = chart._plotArea, categoryAxis = plotArea.categoryAxis, tooltip = chart._tooltip, tooltipOptions = options.tooltip, highlight = chart._highlight, index, points;
if (plotArea.box.containsPoint(coords)) {
index = categoryAxis.pointCategoryIndex(coords);
if (index !== chart._tooltipCategoryIx) {
points = plotArea.pointsByCategoryIndex(index);
var pointArgs = $.map(points, function (point) {
return point.eventArgs(e);
});
var hoverArgs = pointArgs[0] || {};
hoverArgs.categoryPoints = pointArgs;
if (points.length > 0 && !this.trigger(SERIES_HOVER, hoverArgs)) {
if (tooltipOptions.visible) {
tooltip.showAt(points, coords);
}
highlight.show(points);
} else {
tooltip.hide();
}
chart._tooltipCategoryIx = index;
}
}
},
_mouseleave: function (e) {
var chart = this, plotArea = chart._plotArea, tooltip = chart._tooltip, highlight = chart._highlight, target = e.relatedTarget;
if (!(target && $(target).closest(tooltip.element).length)) {
chart._mousemove.cancel();
plotArea.hideCrosshairs();
highlight.hide();
setTimeout(proxy(tooltip.hide, tooltip), TOOLTIP_HIDE_DELAY);
chart._tooltipCategoryIx = null;
}
},
_unsetActivePoint: function () {
var chart = this, tooltip = chart._tooltip, highlight = chart._highlight;
chart._activePoint = null;
if (tooltip) {
tooltip.hide();
}
if (highlight) {
highlight.hide();
}
},
_onDataChanged: function () {
var chart = this, options = chart.options, series = chart._sourceSeries || options.series, seriesIx, seriesLength = series.length, data = chart.dataSource.view(), grouped = (chart.dataSource.group() || []).length > 0, processedSeries = [], currentSeries;
for (seriesIx = 0; seriesIx < seriesLength; seriesIx++) {
currentSeries = series[seriesIx];
if (chart._isBindable(currentSeries) && grouped) {
append(processedSeries, groupSeries(currentSeries, data));
} else {
processedSeries.push(currentSeries || []);
}
}
chart._sourceSeries = series;
options.series = processedSeries;
applySeriesColors(chart.options);
chart._bindSeries();
chart._bindCategories();
chart._hasData = true;
chart._deferRedraw();
},
_deferRedraw: function () {
var chart = this;
if (kendo.support.vml) {
chart._clearRedrawTimeout();
chart._redrawTimeout = setTimeout(function () {
if (!chart.surface) {
return;
}
chart.trigger(DATABOUND);
chart._redraw();
}, 0);
} else {
chart.trigger(DATABOUND);
chart._redraw();
}
},
_clearRedrawTimeout: function () {
if (this._redrawTimeout) {
clearInterval(this._redrawTimeout);
this._redrawTimeout = null;
}
},
_bindSeries: function () {
var chart = this, data = chart.dataSource.view(), series = chart.options.series, seriesIx, seriesLength = series.length, currentSeries, groupIx, seriesData;
for (seriesIx = 0; seriesIx < seriesLength; seriesIx++) {
currentSeries = series[seriesIx];
if (chart._isBindable(currentSeries)) {
groupIx = currentSeries._groupIx;
seriesData = defined(groupIx) ? (data[groupIx] || {}).items : data;
if (currentSeries.autoBind !== false) {
currentSeries.data = seriesData;
}
}
}
},
_bindCategories: function () {
var chart = this, data = chart.dataSource.view() || [], grouped = (chart.dataSource.group() || []).length > 0, categoriesData = data, options = chart.options, definitions = [].concat(options.categoryAxis), axisIx, axis;
if (grouped) {
if (data.length) {
categoriesData = data[0].items;
}
}
for (axisIx = 0; axisIx < definitions.length; axisIx++) {
axis = definitions[axisIx];
if (axis.autoBind !== false) {
chart._bindCategoryAxis(axis, categoriesData, axisIx);
}
}
},
_bindCategoryAxis: function (axis, data, axisIx) {
var count = (data || []).length, categoryIx, category, row;
if (axis.field) {
axis.categories = [];
for (categoryIx = 0; categoryIx < count; categoryIx++) {
row = data[categoryIx];
category = getField(axis.field, row);
if (categoryIx === 0) {
axis.categories = [category];
axis.dataItems = [row];
} else {
axis.categories.push(category);
axis.dataItems.push(row);
}
}
} else {
this._bindCategoryAxisFromSeries(axis, axisIx);
}
},
_bindCategoryAxisFromSeries: function (axis, axisIx) {
var chart = this, items = [], result, series = chart.options.series, seriesLength = series.length, seriesIx, s, onAxis, data, dataIx, dataLength, dataRow, category, uniqueCategories = {}, getFn, dateAxis;
for (seriesIx = 0; seriesIx < seriesLength; seriesIx++) {
s = series[seriesIx];
onAxis = s.categoryAxis === axis.name || !s.categoryAxis && axisIx === 0;
data = s.data;
dataLength = data.length;
if (s.categoryField && onAxis && dataLength > 0) {
dateAxis = isDateAxis(axis, getField(s.categoryField, data[0]));
getFn = dateAxis ? getDateField : getField;
for (dataIx = 0; dataIx < dataLength; dataIx++) {
dataRow = data[dataIx];
category = getFn(s.categoryField, dataRow);
if (dateAxis || !uniqueCategories[category]) {
items.push([
category,
dataRow
]);
if (!dateAxis) {
uniqueCategories[category] = true;
}
}
}
}
}
if (items.length > 0) {
if (dateAxis) {
items = uniqueDates(items, function (a, b) {
return dateComparer(a[0], b[0]);
});
}
result = transpose(items);
axis.categories = result[0];
axis.dataItems = result[1];
}
},
_isBindable: function (series) {
var valueFields = SeriesBinder.current.valueFields(series), result = true, field, i;
for (i = 0; i < valueFields.length; i++) {
field = valueFields[i];
if (field === VALUE) {
field = 'field';
} else {
field = field + 'Field';
}
if (!defined(series[field])) {
result = false;
break;
}
}
return result;
},
_legendItemClick: function (seriesIndex, pointIndex) {
var chart = this, plotArea = chart._plotArea, currentSeries = (plotArea.srcSeries || plotArea.series)[seriesIndex], originalSeries = (chart._sourceSeries || [])[seriesIndex] || currentSeries, transitionsState, visible, point;
if (inArray(currentSeries.type, [
PIE,
DONUT,
FUNNEL
])) {
point = originalSeries.data[pointIndex];
if (!defined(point.visible)) {
visible = false;
} else {
visible = !point.visible;
}
point.visible = visible;
} else {
visible = !originalSeries.visible;
originalSeries.visible = visible;
currentSeries.visible = visible;
}
if (chart.options.transitions) {
chart.options.transitions = false;
transitionsState = true;
}
chart.redraw();
if (transitionsState) {
chart.options.transitions = true;
}
},
_legendItemHover: function (seriesIndex, pointIndex) {
var chart = this, plotArea = chart._plotArea, highlight = chart._highlight, currentSeries = (plotArea.srcSeries || plotArea.series)[seriesIndex], index, items;
if (inArray(currentSeries.type, [
PIE,
DONUT,
FUNNEL
])) {
index = pointIndex;
} else {
index = seriesIndex;
}
items = plotArea.pointsBySeriesIndex(index);
highlight.show(items);
},
_shouldAttachMouseMove: function () {
var chart = this;
return chart._plotArea.crosshairs.length || chart._tooltip && chart._sharedTooltip();
},
setOptions: function (options) {
var chart = this, dataSource = options.dataSource;
options.dataSource = undefined;
chart._originalOptions = deepExtend(chart._originalOptions, options);
chart.options = deepExtend({}, chart._originalOptions);
chart._sourceSeries = null;
$(document).off(MOUSEMOVE_NS);
Widget.fn._setEvents.call(chart, options);
chart._initTheme(chart.options);
if (dataSource) {
chart.setDataSource(dataSource);
}
if (chart._hasDataSource) {
chart._onDataChanged();
} else {
chart._bindCategories();
chart.redraw();
}
if (chart._shouldAttachMouseMove()) {
chart.element.on(MOUSEMOVE_NS, chart._mousemove);
}
},
destroy: function () {
var chart = this, dataSource = chart.dataSource;
chart.element.off(NS);
if (dataSource) {
dataSource.unbind(CHANGE, chart._dataChangeHandler);
}
$(document).off(MOUSEMOVE_TRACKING);
if (chart._userEvents) {
chart._userEvents.destroy();
}
chart._destroyView();
chart.surface.destroy();
chart.surface = null;
chart._clearRedrawTimeout();
Widget.fn.destroy.call(chart);
},
_destroyView: function () {
var chart = this, model = chart._model, selections = chart._selections;
if (model) {
model.destroy();
chart._model = null;
}
if (selections) {
while (selections.length > 0) {
selections.shift().destroy();
}
}
chart._unsetActivePoint();
if (chart._tooltip) {
chart._tooltip.destroy();
}
if (chart._highlight) {
chart._highlight.destroy();
}
if (chart._zoomSelection) {
chart._zoomSelection.destroy();
}
}
});
dataviz.ExportMixin.extend(Chart.fn);
if (kendo.PDFMixin) {
kendo.PDFMixin.extend(Chart.fn);
}
var PlotAreaFactory = Class.extend({
init: function () {
this._registry = [];
},
register: function (type, seriesTypes) {
this._registry.push({
type: type,
seriesTypes: seriesTypes
});
},
create: function (srcSeries, options) {
var registry = this._registry, match = registry[0], i, entry, series;
for (i = 0; i < registry.length; i++) {
entry = registry[i];
series = filterSeriesByType(srcSeries, entry.seriesTypes);
if (series.length > 0) {
match = entry;
break;
}
}
return new match.type(series, options);
}
});
PlotAreaFactory.current = new PlotAreaFactory();
var SeriesBinder = Class.extend({
init: function () {
this._valueFields = {};
this._otherFields = {};
this._nullValue = {};
this._undefinedValue = {};
},
register: function (seriesTypes, valueFields, otherFields) {
var binder = this, i, type;
valueFields = valueFields || [VALUE];
for (i = 0; i < seriesTypes.length; i++) {
type = seriesTypes[i];
binder._valueFields[type] = valueFields;
binder._otherFields[type] = otherFields;
binder._nullValue[type] = binder._makeValue(valueFields, null);
binder._undefinedValue[type] = binder._makeValue(valueFields, undefined);
}
},
canonicalFields: function (series) {
return this.valueFields(series).concat(this.otherFields(series));
},
valueFields: function (series) {
return this._valueFields[series.type] || [VALUE];
},
otherFields: function (series) {
return this._otherFields[series.type] || [VALUE];
},
bindPoint: function (series, pointIx, item) {
var binder = this, data = series.data, pointData = defined(item) ? item : data[pointIx], result = { valueFields: { value: pointData } }, fields, fieldData, srcValueFields, srcPointFields, valueFields = binder.valueFields(series), otherFields = binder._otherFields[series.type], value;
if (pointData === null) {
value = binder._nullValue[series.type];
} else if (!defined(pointData)) {
value = binder._undefinedValue[series.type];
} else if (isArray(pointData)) {
fieldData = pointData.slice(valueFields.length);
value = binder._bindFromArray(pointData, valueFields);
fields = binder._bindFromArray(fieldData, otherFields);
} else if (typeof pointData === OBJECT) {
srcValueFields = binder.sourceFields(series, valueFields);
srcPointFields = binder.sourceFields(series, otherFields);
value = binder._bindFromObject(pointData, valueFields, srcValueFields);
fields = binder._bindFromObject(pointData, otherFields, srcPointFields);
}
if (defined(value)) {
if (valueFields.length === 1) {
result.valueFields.value = value[valueFields[0]];
} else {
result.valueFields = value;
}
}
result.fields = fields || {};
return result;
},
_makeValue: function (fields, initialValue) {
var value = {}, i, length = fields.length, fieldName;
for (i = 0; i < length; i++) {
fieldName = fields[i];
value[fieldName] = initialValue;
}
return value;
},
_bindFromArray: function (array, fields) {
var value = {}, i, length;
if (fields) {
length = math.min(fields.length, array.length);
for (i = 0; i < length; i++) {
value[fields[i]] = array[i];
}
}
return value;
},
_bindFromObject: function (object, fields, srcFields) {
var value = {}, i, length, fieldName, srcFieldName;
if (fields) {
length = fields.length;
srcFields = srcFields || fields;
for (i = 0; i < length; i++) {
fieldName = fields[i];
srcFieldName = srcFields[i];
value[fieldName] = getField(srcFieldName, object);
}
}
return value;
},
sourceFields: function (series, canonicalFields) {
var i, length, fieldName, sourceFields, sourceFieldName;
if (canonicalFields) {
length = canonicalFields.length;
sourceFields = [];
for (i = 0; i < length; i++) {
fieldName = canonicalFields[i];
sourceFieldName = fieldName === VALUE ? 'field' : fieldName + 'Field';
sourceFields.push(series[sourceFieldName] || fieldName);
}
}
return sourceFields;
}
});
SeriesBinder.current = new SeriesBinder();
var BarLabel = ChartElement.extend({
init: function (content, options) {
var barLabel = this;
ChartElement.fn.init.call(barLabel, options);
this.textBox = new TextBox(content, barLabel.options);
barLabel.append(this.textBox);
},
options: {
position: OUTSIDE_END,
margin: getSpacing(3),
padding: getSpacing(4),
color: BLACK,
background: '',
border: {
width: 1,
color: ''
},
aboveAxis: true,
vertical: false,
animation: {
type: FADEIN,
delay: INITIAL_ANIMATION_DURATION
},
zIndex: 2
},
createVisual: function () {
this.textBox.options.noclip = this.options.noclip;
},
reflow: function (targetBox) {
var barLabel = this, options = barLabel.options, vertical = options.vertical, aboveAxis = options.aboveAxis, text = barLabel.children[0], box = text.box, padding = text.options.padding;
text.options.align = vertical ? CENTER : LEFT;
text.options.vAlign = vertical ? TOP : CENTER;
if (options.position == INSIDE_END) {
if (vertical) {
text.options.vAlign = TOP;
if (!aboveAxis && box.height() < targetBox.height()) {
text.options.vAlign = BOTTOM;
}
} else {
text.options.align = aboveAxis ? RIGHT : LEFT;
}
} else if (options.position == CENTER) {
text.options.vAlign = CENTER;
text.options.align = CENTER;
} else if (options.position == INSIDE_BASE) {
if (vertical) {
text.options.vAlign = aboveAxis ? BOTTOM : TOP;
} else {
text.options.align = aboveAxis ? LEFT : RIGHT;
}
} else if (options.position == OUTSIDE_END) {
if (vertical) {
if (aboveAxis) {
targetBox = new Box2D(targetBox.x1, targetBox.y1 - box.height(), targetBox.x2, targetBox.y1);
} else {
targetBox = new Box2D(targetBox.x1, targetBox.y2, targetBox.x2, targetBox.y2 + box.height());
}
} else {
text.options.align = CENTER;
if (aboveAxis) {
targetBox = new Box2D(targetBox.x2, targetBox.y1, targetBox.x2 + box.width(), targetBox.y2);
} else {
targetBox = new Box2D(targetBox.x1 - box.width(), targetBox.y1, targetBox.x1, targetBox.y2);
}
}
}
if (!options.rotation) {
if (vertical) {
padding.left = padding.right = (targetBox.width() - text.contentBox.width()) / 2;
} else {
padding.top = padding.bottom = (targetBox.height() - text.contentBox.height()) / 2;
}
}
text.reflow(targetBox);
},
alignToClipBox: function (clipBox) {
var barLabel = this, vertical = barLabel.options.vertical, field = vertical ? Y : X, start = field + '1', end = field + '2', text = barLabel.children[0], parentBox = barLabel.parent.box, targetBox;
if (parentBox[start] < clipBox[start] || clipBox[end] < parentBox[end]) {
targetBox = text.paddingBox.clone();
targetBox[start] = math.max(parentBox[start], clipBox[start]);
targetBox[end] = math.min(parentBox[end], clipBox[end]);
this.reflow(targetBox);
}
}
});
var LegendItem = BoxElement.extend({
init: function (options) {
var item = this;
BoxElement.fn.init.call(item, options);
item.createContainer();
item.createMarker();
item.createLabel();
},
createContainer: function () {
var item = this;
item.container = new FloatElement({
vertical: false,
wrap: false,
align: CENTER
});
item.append(item.container);
},
createMarker: function () {
this.container.append(new ShapeElement(this.markerOptions()));
},
markerOptions: function () {
var options = this.options;
var markerColor = options.markerColor;
return deepExtend({}, options.markers, {
background: markerColor,
border: { color: markerColor }
});
},
createLabel: function () {
var item = this, options = item.options, labelOptions = deepExtend({}, options.labels);
item.container.append(new TextBox(options.text, labelOptions));
},
renderComplete: function () {
ChartElement.fn.renderComplete.call(this);
var cursor = this.options.cursor || {};
var eventSink = this._itemOverlay = draw.Path.fromRect(this.container.box.toRect(), {
fill: {
color: WHITE,
opacity: 0
},
stroke: null,
cursor: cursor.style || cursor
});
this.appendVisual(eventSink);
},
click: function (widget, e) {
var args = this.eventArgs(e);
if (!widget.trigger(LEGEND_ITEM_CLICK, args)) {
e.preventDefault();
widget._legendItemClick(args.seriesIndex, args.pointIndex);
}
},
hover: function (widget, e) {
var args = this.eventArgs(e);
if (!widget.trigger(LEGEND_ITEM_HOVER, args)) {
e.preventDefault();
widget._legendItemHover(args.seriesIndex, args.pointIndex);
}
return true;
},
leave: function (widget) {
widget._unsetActivePoint();
},
eventArgs: function (e) {
var options = this.options;
return {
element: $(e.target),
text: options.text,
series: options.series,
seriesIndex: options.series.index,
pointIndex: options.pointIndex
};
},
renderVisual: function () {
var that = this;
var options = that.options;
var customVisual = options.visual;
if (customVisual) {
that.visual = customVisual({
active: options.active,
series: options.series,
pointIndex: options.pointIndex,
options: {
markers: that.markerOptions(),
labels: options.labels
},
createVisual: function () {
that.createVisual();
that.renderChildren();
that.renderComplete();
var defaultVisual = that.visual;
delete that.visual;
return defaultVisual;
}
});
this.addVisual();
} else {
ChartElement.fn.renderVisual.call(that);
}
}
});
var LegendLayout = ChartElement.extend({
render: function () {
var legendItem, items = this.children;
var options = this.options;
var vertical = options.vertical;
this.visual = new draw.Layout(null, {
spacing: vertical ? 0 : options.spacing,
lineSpacing: vertical ? options.spacing : 0,
orientation: vertical ? 'vertical' : 'horizontal'
});
for (var idx = 0; idx < items.length; idx++) {
legendItem = items[idx];
legendItem.reflow(new Box2D());
legendItem.renderVisual();
}
},
reflow: function (box) {
this.visual.rect(box.toRect());
this.visual.reflow();
var bbox = this.visual.clippedBBox();
if (bbox) {
this.box = dataviz.rectToBox(bbox);
} else {
this.box = new Box2D();
}
},
renderVisual: function () {
this.addVisual();
},
createVisual: noop
});
var Legend = ChartElement.extend({
init: function (options) {
var legend = this;
ChartElement.fn.init.call(legend, options);
if (!inArray(legend.options.position, [
TOP,
RIGHT,
BOTTOM,
LEFT,
CUSTOM
])) {
legend.options.position = RIGHT;
}
legend.createContainer();
legend.createItems();
},
options: {
position: RIGHT,
items: [],
labels: { margin: { left: 6 } },
offsetX: 0,
offsetY: 0,
margin: getSpacing(5),
padding: getSpacing(5),
border: {
color: BLACK,
width: 0
},
item: { cursor: POINTER },
spacing: 6,
background: '',
zIndex: 1,
markers: {
border: { width: 1 },
width: 7,
height: 7,
type: 'rect',
align: LEFT,
vAlign: CENTER
}
},
createContainer: function () {
var legend = this, options = legend.options, userAlign = options.align, position = options.position, align = position, vAlign = CENTER;
if (position == CUSTOM) {
align = LEFT;
} else if (inArray(position, [
TOP,
BOTTOM
])) {
if (userAlign == 'start') {
align = LEFT;
} else if (userAlign == 'end') {
align = RIGHT;
} else {
align = CENTER;
}
vAlign = position;
} else if (userAlign) {
if (userAlign == 'start') {
vAlign = TOP;
} else if (userAlign == 'end') {
vAlign = BOTTOM;
}
}
legend.container = new BoxElement({
margin: options.margin,
padding: options.padding,
background: options.background,
border: options.border,
vAlign: vAlign,
align: align,
zIndex: options.zIndex,
shrinkToFit: true
});
legend.append(legend.container);
},
createItems: function () {
var legend = this, options = legend.options, items = options.items, count = items.length, vertical = legend.isVertical(), innerElement, i, item;
innerElement = new LegendLayout({
vertical: vertical,
spacing: options.spacing
});
if (options.reverse) {
items = items.slice(0).reverse();
}
for (i = 0; i < count; i++) {
item = items[i];
innerElement.append(new LegendItem(deepExtend({}, {
markers: options.markers,
labels: options.labels
}, options.item, item)));
}
innerElement.render();
legend.container.append(innerElement);
},
isVertical: function () {
var legend = this, options = legend.options, orientation = options.orientation, position = options.position, vertical = position == CUSTOM && orientation != HORIZONTAL || (defined(orientation) ? orientation != HORIZONTAL : inArray(position, [
LEFT,
RIGHT
]));
return vertical;
},
hasItems: function () {
return this.container.children[0].children.length > 0;
},
reflow: function (targetBox) {
var legend = this, options = legend.options;
targetBox = targetBox.clone();
if (!legend.hasItems()) {
legend.box = targetBox;
return;
}
if (options.position === CUSTOM) {
legend.containerCustomReflow(targetBox);
legend.box = targetBox;
} else {
legend.containerReflow(targetBox);
}
},
containerReflow: function (targetBox) {
var legend = this, options = legend.options, position = options.position, pos = position == TOP || position == BOTTOM ? X : Y, containerBox = targetBox.clone(), container = legend.container, width = options.width, height = options.height, vertical = legend.isVertical(), alignTarget = targetBox.clone(), box;
if (position == LEFT || position == RIGHT) {
containerBox.y1 = alignTarget.y1 = 0;
}
if (vertical && height) {
containerBox.y2 = containerBox.y1 + height;
containerBox.align(alignTarget, Y, container.options.vAlign);
} else if (!vertical && width) {
containerBox.x2 = containerBox.x1 + width;
containerBox.align(alignTarget, X, container.options.align);
}
container.reflow(containerBox);
containerBox = container.box;
box = containerBox.clone();
if (options.offsetX || options.offsetY) {
containerBox.translate(options.offsetX, options.offsetY);
legend.container.reflow(containerBox);
}
box[pos + 1] = targetBox[pos + 1];
box[pos + 2] = targetBox[pos + 2];
legend.box = box;
},
containerCustomReflow: function (targetBox) {
var legend = this, options = legend.options, offsetX = options.offsetX, offsetY = options.offsetY, container = legend.container, width = options.width, height = options.height, vertical = legend.isVertical(), containerBox = targetBox.clone();
if (vertical && height) {
containerBox.y2 = containerBox.y1 + height;
} else if (!vertical && width) {
containerBox.x2 = containerBox.x1 + width;
}
container.reflow(containerBox);
containerBox = container.box;
container.reflow(Box2D(offsetX, offsetY, offsetX + containerBox.width(), offsetY + containerBox.height()));
},
renderVisual: function () {
if (this.hasItems()) {
ChartElement.fn.renderVisual.call(this);
}
}
});
var CategoryAxis = Axis.extend({
init: function (options) {
var axis = this;
options = options || {};
this._initFields();
this._initCategories(options);
Axis.fn.init.call(axis, options);
},
_initFields: function () {
this._ticks = {};
this.outOfRangeMin = 0;
this.outOfRangeMax = 0;
},
_initCategories: function (options) {
var categories = (options.categories || []).slice(0);
var definedMin = defined(options.min);
var definedMax = defined(options.max);
options.categories = categories;
if ((definedMin || definedMax) && categories.length) {
options.srcCategories = options.categories;
var min = definedMin ? math.floor(options.min) : 0;
var max = definedMax ? options.justified ? math.floor(options.max) + 1 : math.ceil(options.max) : categories.length;
options.categories = options.categories.slice(min, max);
}
},
options: {
type: CATEGORY,
categories: [],
vertical: false,
majorGridLines: {
visible: false,
width: 1,
color: BLACK
},
labels: { zIndex: 1 },
justified: false
},
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 = (options.srcCategories || options.categories).length - (options.justified ? 1 : 0) || 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;
return {
min: isNumber(options.min) ? options.min : 0,
max: isNumber(options.max) ? options.max : options.categories.length
};
},
totalRange: function () {
var options = this.options;
return {
min: 0,
max: math.max(this._seriesMax || 0, (options.srcCategories || options.categories).length) - (options.justified ? 1 : 0)
};
},
getScale: function () {
var range = this.rangeIndices();
var min = range.min;
var max = range.max;
var lineBox = this.lineBox();
var size = this.options.vertical ? lineBox.height() : lineBox.width();
var scale = size / (max - min || 1);
return scale * (this.options.reverse ? -1 : 1);
},
getTickPositions: function (stepSize) {
var axis = this, options = axis.options, vertical = options.vertical, lineBox = axis.lineBox(), reverse = options.reverse, scale = axis.getScale(), range = axis.rangeIndices(), min = range.min, max = range.max, current = min % 1 !== 0 ? math.floor(min / 1) + stepSize : min, pos = lineBox[(vertical ? Y : X) + (reverse ? 2 : 1)], positions = [];
while (current <= max) {
positions.push(pos + round(scale * (current - min), COORD_PRECISION));
current += stepSize;
}
return positions;
},
getLabelsTickPositions: function () {
var tickPositions = this.getMajorTickPositions().slice(0);
var range = this.rangeIndices();
var scale = this.getScale();
var box = this.lineBox();
var options = this.options;
var axis = options.vertical ? Y : X;
var start = options.reverse ? 2 : 1;
var end = options.reverse ? 1 : 2;
if (range.min % 1 !== 0) {
tickPositions.unshift(box[axis + start] - scale * (range.min % 1));
}
if (range.max % 1 !== 0) {
tickPositions.push(box[axis + end] + scale * (1 - range.max % 1));
}
return tickPositions;
},
labelTickIndex: function (label) {
var index = label.index;
var range = this.rangeIndices();
if (range.min > 0) {
index = index - math.floor(range.min);
}
return index;
},
arrangeLabels: function () {
Axis.fn.arrangeLabels.call(this);
this.hideOutOfRangeLabels();
},
hideOutOfRangeLabels: function () {
var box = this.box, labels = this.labels, valueAxis = this.options.vertical ? Y : X, start = box[valueAxis + 1], end = box[valueAxis + 2], firstLabel = labels[0], lastLabel = last(labels);
if (labels.length) {
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;
},
getTicks: function () {
var axis = this, cache = axis._ticks, options = axis.options, range = axis.rangeIndices(), reverse = options.reverse, justified = options.justified, lineBox = axis.lineBox(), hash;
hash = lineBox.getHash() + range.min + ',' + range.max + reverse + justified;
if (cache._hash !== hash) {
cache._hash = hash;
cache.majorTicks = axis.getTickPositions(1);
cache.minorTicks = axis.getTickPositions(0.5);
}
return cache;
},
getSlot: function (from, to, limit) {
var axis = this, options = axis.options, reverse = options.reverse, justified = options.justified, valueAxis = options.vertical ? Y : X, lineBox = axis.lineBox(), range = axis.rangeIndices(), min = range.min, scale = this.getScale(), lineStart = lineBox[valueAxis + (reverse ? 2 : 1)], slotBox = lineBox.clone(), p1, p2;
var singleSlot = !defined(to);
from = valueOrDefault(from, 0);
to = valueOrDefault(to, from);
to = math.max(to - 1, from);
to = math.max(from, to);
p1 = lineStart + (from - min) * scale;
p2 = lineStart + (to + 1 - min) * scale;
if (singleSlot && justified) {
p2 = p1;
}
if (limit) {
p1 = limitValue(p1, lineBox[valueAxis + 1], lineBox[valueAxis + 2]);
p2 = limitValue(p2, lineBox[valueAxis + 1], lineBox[valueAxis + 2]);
}
slotBox[valueAxis + 1] = reverse ? p2 : p1;
slotBox[valueAxis + 2] = reverse ? p1 : p2;
return slotBox;
},
pointCategoryIndex: function (point) {
var axis = this, options = axis.options, reverse = options.reverse, justified = options.justified, valueAxis = options.vertical ? Y : X, lineBox = axis.lineBox(), range = axis.rangeIndices(), startValue = reverse ? range.max : range.min, scale = this.getScale(), lineStart = lineBox[valueAxis + 1], lineEnd = lineBox[valueAxis + 2], pos = point[valueAxis];
if (pos < lineStart || pos > lineEnd) {
return null;
}
var size = pos - lineStart;
var value = size / scale;
value = startValue + value;
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) {
var options = this.options;
var index = indexOf(value, options.srcCategories || options.categories);
return index - math.floor(options.min || 0);
},
translateRange: function (delta) {
var axis = this, options = axis.options, lineBox = axis.lineBox(), size = options.vertical ? lineBox.height() : lineBox.width(), range = options.categories.length, scale = size / range, offset = round(delta / scale, DEFAULT_PRECISION);
return {
min: offset,
max: range + offset
};
},
zoomRange: function (rate) {
var rangeIndices = this.totalRangeIndices();
var totalRange = this.totalRange();
var totalMax = totalRange.max;
var totalMin = totalRange.min;
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 axis = this, options = axis.options, range = options.categories.length, 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 labelOptions = options.labels;
var justified = options.justified;
var range = this.totalRangeIndices(true);
var min = range.min;
var max = range.max;
var start = math.floor(min);
var skip;
if (!justified) {
min = math.floor(min);
max = math.ceil(max);
} else {
min = math.ceil(min);
max = math.floor(max);
}
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 axis = this, options = axis.options, dataItem = options.dataItems ? options.dataItems[index] : null, category = valueOrDefault(options.categories[index], ''), text = axis.axisLabelText(category, dataItem, labelOptions);
return new AxisLabel(category, text, index, dataItem, labelOptions);
},
shouldRenderNote: function (value) {
var categories = this.options.categories;
return categories.length && (categories.length > value && value >= 0);
},
pan: function (delta) {
var range = this.totalRangeIndices(true), scale = this.getScale(), offset = round(delta / scale, DEFAULT_PRECISION), totalRange = this.totalRange(), min = range.min + offset, max = range.max + offset;
return this.limitRange(min, max, 0, totalRange.max, offset);
},
pointsRange: function (start, end) {
var axis = this, options = axis.options, reverse = options.reverse, valueAxis = options.vertical ? Y : X, lineBox = axis.lineBox(), range = axis.totalRangeIndices(true), scale = this.getScale(), lineStart = lineBox[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;
return {
min: math.min(min, max),
max: math.max(min, max)
};
}
});
var DateCategoryAxis = CategoryAxis.extend({
init: function (options) {
var axis = this, baseUnit, useDefault;
options = options || {};
options = deepExtend({ roundToBaseUnit: true }, options, {
categories: toDate(options.categories),
min: toDate(options.min),
max: toDate(options.max)
});
options.userSetBaseUnit = options.userSetBaseUnit || options.baseUnit;
options.userSetBaseUnitStep = options.userSetBaseUnitStep || options.baseUnitStep;
if (options.categories && options.categories.length > 0) {
baseUnit = (options.baseUnit || '').toLowerCase();
useDefault = baseUnit !== FIT && !inArray(baseUnit, BASE_UNITS);
if (useDefault) {
options.baseUnit = axis.defaultBaseUnit(options);
}
if (baseUnit === FIT || options.baseUnitStep === AUTO) {
axis.autoBaseUnit(options);
}
this._groupsStart = addDuration(options.categories[0], 0, options.baseUnit, options.weekStartDay);
axis.groupCategories(options);
} else {
options.baseUnit = options.baseUnit || DAYS;
}
this._initFields();
Axis.fn.init.call(axis, options);
},
options: {
type: DATE,
labels: { dateFormats: DateLabelFormats },
autoBaseUnitSteps: {
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
},
shouldRenderNote: function (value) {
var axis = this, range = axis.range(), categories = axis.options.categories || [];
return dateComparer(value, range.min) >= 0 && dateComparer(value, range.max) <= 0 && categories.length;
},
parseNoteValue: function (value) {
return toDate(value);
},
translateRange: function (delta) {
var axis = this, options = axis.options, baseUnit = options.baseUnit, weekStartDay = options.weekStartDay, lineBox = axis.lineBox(), size = options.vertical ? lineBox.height() : lineBox.width(), range = axis.range(), scale = size / (range.max - range.min), offset = round(delta / scale, DEFAULT_PRECISION), from, to;
if (range.min && range.max) {
from = addTicks(options.min || range.min, offset);
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 axis = this, rounds = math.abs(delta), range = axis.range(), from = range.min, to = range.max, step;
if (range.min && range.max) {
while (rounds--) {
range = dateDiff(from, to);
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);
}
}
range = {
min: from,
max: to
};
}
return range;
},
defaultBaseUnit: function (options) {
var categories = options.categories, count = defined(categories) ? categories.length : 0, categoryIx, cat, diff, minDiff = MAX_VALUE, lastCat, unit;
for (categoryIx = 0; categoryIx < count; categoryIx++) {
cat = categories[categoryIx];
if (cat && lastCat) {
diff = absoluteDateDiff(cat, lastCat);
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;
}
}
}
lastCat = cat;
}
return unit || DAYS;
},
_categoryRange: function (categories) {
var range = categories._range;
if (!range) {
range = categories._range = sparseArrayLimits(categories);
}
return range;
},
totalRange: function () {
return {
min: 0,
max: this.options.categories.length
};
},
rangeIndices: function () {
var options = this.options;
var baseUnit = options.baseUnit;
var baseUnitStep = options.baseUnitStep || 1;
var categories = options.categories;
var categoryLimits = this.categoriesRange();
var min = toDate(options.min || categoryLimits.min);
var max = toDate(options.max || categoryLimits.max);
var minIdx = 0, maxIdx = 0;
if (categories.length) {
minIdx = dateIndex(min, categories[0], baseUnit, baseUnitStep);
maxIdx = dateIndex(max, categories[0], baseUnit, baseUnitStep);
if (options.roundToBaseUnit) {
minIdx = math.floor(minIdx);
maxIdx = options.justified ? math.floor(maxIdx) : math.ceil(maxIdx);
}
}
return {
min: minIdx,
max: maxIdx
};
},
labelsRange: function () {
var options = this.options;
var labelOptions = options.labels;
var range = this.rangeIndices();
var min = math.floor(range.min);
var max = math.ceil(range.max);
return {
min: min + labelOptions.skip,
max: options.categories.length ? max + (options.justified ? 1 : 0) : 0
};
},
categoriesRange: function () {
var options = this.options;
var range = this._categoryRange(options.srcCategories || options.categories);
var max = toDate(range.max);
if (!options.justified && dateEquals(max, this._roundToTotalStep(max, options, false))) {
max = this._roundToTotalStep(max, options, true, true);
}
return {
min: toDate(range.min),
max: max
};
},
currentRange: function () {
var options = this.options;
var round = options.roundToBaseUnit !== false;
var totalRange = this.categoriesRange();
var min = options.min;
var max = options.max;
if (!min) {
min = round ? this._roundToTotalStep(totalRange.min, options, false) : totalRange.min;
}
if (!max) {
max = round ? this._roundToTotalStep(totalRange.max, options, !options.justified) : totalRange.max;
}
return {
min: min,
max: max
};
},
datesRange: function () {
var range = this._categoryRange(this.options.srcCategories || this.options.categories);
return {
min: toDate(range.min),
max: toDate(range.max)
};
},
pan: function (delta) {
var axis = this, options = axis.options, baseUnit = options.baseUnit, lineBox = axis.lineBox(), size = options.vertical ? lineBox.height() : lineBox.width(), range = this.currentRange(), totalLimits = this.totalLimits(), min = range.min, max = range.max, scale = size / (max - min), offset = round(delta / scale, DEFAULT_PRECISION), panRange, from, to;
from = addTicks(min, offset);
to = addTicks(max, offset);
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 = baseUnit;
panRange.baseUnitStep = options.baseUnitStep || 1;
panRange.userSetBaseUnit = options.userSetBaseUnit;
panRange.userSetBaseUnitStep = options.userSetBaseUnitStep;
return panRange;
}
},
pointsRange: function (start, end) {
var pointsRange = CategoryAxis.fn.pointsRange.call(this, start, end);
var datesRange = this.currentRange();
var indicesRange = this.rangeIndices();
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,
baseUnitStep: options.userSetBaseUnitStep
};
},
zoomRange: function (delta) {
var options = this.options;
var totalLimits = this.totalLimits();
var currentRange = this.currentRange();
var baseUnit = options.baseUnit;
var baseUnitStep = options.baseUnitStep || 1;
var weekStartDay = options.weekStartDay;
var rangeMax = currentRange.max;
var rangeMin = currentRange.min;
var min = addDuration(rangeMin, delta * baseUnitStep, baseUnit, weekStartDay);
var max = addDuration(rangeMax, -delta * baseUnitStep, baseUnit, weekStartDay);
if (options.userSetBaseUnit == FIT) {
var autoBaseUnitSteps = options.autoBaseUnitSteps;
var maxDateGroups = options.maxDateGroups;
var baseUnitIndex = indexOf(baseUnit, BASE_UNITS);
var autoBaseUnitStep;
var diff = dateDiff(max, min);
var maxDiff = last(autoBaseUnitSteps[baseUnit]) * maxDateGroups * TIME_PER_UNIT[baseUnit];
var rangeDiff = dateDiff(rangeMax, rangeMin);
var ticks;
if (diff < TIME_PER_UNIT[baseUnit] && baseUnit !== SECONDS) {
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);
}
}
}
min = toDate(limitValue(min, totalLimits.min, totalLimits.max));
max = toDate(limitValue(max, totalLimits.min, totalLimits.max));
if (dateDiff(max, min) > 0) {
return {
min: min,
max: max,
baseUnit: options.userSetBaseUnit,
baseUnitStep: options.userSetBaseUnitStep
};
}
},
totalLimits: function () {
var options = this.options;
var datesRange = this.datesRange();
var min = this._roundToTotalStep(toDate(datesRange.min), options, false);
var max = datesRange.max;
if (!options.justified) {
max = this._roundToTotalStep(max, options, true, dateEquals(max, this._roundToTotalStep(max, options, false)));
}
return {
min: min,
max: max
};
},
range: function (options) {
options = options || this.options;
var categories = options.categories, autoUnit = options.baseUnit === FIT, baseUnit = autoUnit ? BASE_UNITS[0] : options.baseUnit, baseUnitStep = options.baseUnitStep || 1, stepOptions = {
baseUnit: baseUnit,
baseUnitStep: baseUnitStep,
weekStartDay: options.weekStartDay
}, categoryLimits = this._categoryRange(categories), min = toDate(options.min || categoryLimits.min), max = toDate(options.max || categoryLimits.max);
return {
min: this._roundToTotalStep(min, stepOptions, false),
max: this._roundToTotalStep(max, stepOptions, true, true)
};
},
autoBaseUnit: function (options) {
var axis = this, categoryLimits = this._categoryRange(options.categories), min = toDate(options.min || categoryLimits.min), max = toDate(options.max || categoryLimits.max), autoUnit = options.baseUnit === FIT, autoUnitIx = 0, baseUnit = autoUnit ? BASE_UNITS[autoUnitIx++] : options.baseUnit, span = max - min, units = span / TIME_PER_UNIT[baseUnit], totalUnits = units, maxDateGroups = options.maxDateGroups || axis.options.maxDateGroups, autoBaseUnitSteps = deepExtend({}, axis.options.autoBaseUnitSteps, options.autoBaseUnitSteps), unitSteps, step, nextStep;
while (!step || units >= maxDateGroups) {
unitSteps = unitSteps || autoBaseUnitSteps[baseUnit].slice(0);
nextStep = unitSteps.shift();
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;
},
_timeScale: function () {
var axis = this, range = axis.range(), options = axis.options, lineBox = axis.lineBox(), vertical = options.vertical, lineSize = vertical ? lineBox.height() : lineBox.width(), timeRange;
if (options.justified && options._collapse !== false) {
var categoryLimits = this._categoryRange(options.categories);
var maxCategory = toTime(categoryLimits.max);
timeRange = toDate(maxCategory) - range.min;
} else {
timeRange = range.max - range.min;
}
return lineSize / timeRange;
},
groupCategories: function (options) {
var axis = this, categories = options.categories, maxCategory = toDate(sparseArrayMax(categories)), baseUnit = options.baseUnit, baseUnitStep = options.baseUnitStep || 1, range = axis.range(options), max = range.max, date, nextDate, groups = [];
for (date = range.min; date < max; date = nextDate) {
groups.push(date);
nextDate = addDuration(date, baseUnitStep, baseUnit, options.weekStartDay);
if (nextDate > maxCategory && !options.max) {
break;
}
}
options.srcCategories = categories;
options.categories = groups;
},
_roundToTotalStep: function (value, options, upper, roundToNext) {
options = options || this.options;
var baseUnit = options.baseUnit;
var baseUnitStep = options.baseUnitStep || 1;
var start = this._groupsStart;
if (start) {
var step = dateIndex(value, start, baseUnit, baseUnitStep);
var roundedStep = upper ? math.ceil(step) : math.floor(step);
if (roundToNext) {
roundedStep++;
}
return addDuration(start, roundedStep * baseUnitStep, baseUnit, options.weekStartDay);
} else {
return addDuration(value, upper ? baseUnitStep : 0, baseUnit, options.weekStartDay);
}
},
createAxisLabel: function (index, labelOptions) {
var options = this.options, dataItem = options.dataItems ? options.dataItems[index] : null, date = options.categories[index], baseUnit = options.baseUnit, visible = true, unitFormat = labelOptions.dateFormats[baseUnit];
if (options.justified) {
var roundedDate = floorDate(date, baseUnit, options.weekStartDay);
visible = dateEquals(roundedDate, date);
} else if (!options.roundToBaseUnit) {
visible = !dateEquals(this.range().max, date);
}
if (visible) {
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) {
var axis = this;
var options = axis.options;
var categories = options.categories;
var index = -1;
if (categories.length) {
index = math.floor(dateIndex(toDate(value), categories[0], options.baseUnit, options.baseUnitStep || 1));
}
return index;
},
getSlot: function (a, b, limit) {
var axis = this;
if (typeof a === OBJECT) {
a = axis.categoryIndex(a);
}
if (typeof b === OBJECT) {
b = axis.categoryIndex(b);
}
return CategoryAxis.fn.getSlot.call(axis, a, b, limit);
}
});
var DateValueAxis = Axis.extend({
init: function (seriesMin, seriesMax, options) {
var axis = this;
options = options || {};
deepExtend(options, {
min: toDate(options.min),
max: toDate(options.max),
axisCrossingValue: toDate(options.axisCrossingValues || options.axisCrossingValue)
});
options = axis.applyDefaults(toDate(seriesMin), toDate(seriesMax), options);
Axis.fn.init.call(axis, options);
},
options: {
type: DATE,
majorGridLines: {
visible: true,
width: 1,
color: BLACK
},
labels: { dateFormats: DateLabelFormats }
},
applyDefaults: function (seriesMin, seriesMax, options) {
var axis = this, min = options.min || seriesMin, max = options.max || seriesMax, baseUnit = options.baseUnit || (max && min ? axis.timeUnits(absoluteDateDiff(max, min)) : HOURS), baseUnitTime = TIME_PER_UNIT[baseUnit], autoMin = floorDate(toTime(min) - 1, baseUnit) || toDate(max), autoMax = ceilDate(toTime(max) + 1, baseUnit), userMajorUnit = options.majorUnit ? options.majorUnit : undefined, majorUnit = userMajorUnit || dataviz.ceil(dataviz.autoMajorUnit(autoMin.getTime(), autoMax.getTime()), baseUnitTime) / baseUnitTime, actualUnits = duration(autoMin, autoMax, baseUnit), totalUnits = dataviz.ceil(actualUnits, majorUnit), unitsToAdd = totalUnits - actualUnits, head = math.floor(unitsToAdd / 2), 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;
this.totalMin = toTime(floorDate(toTime(seriesMin) - 1, baseUnit));
this.totalMax = toTime(ceilDate(toTime(seriesMax) + 1, baseUnit));
return options;
},
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 reverse = options.reverse;
var lineBox = this.lineBox();
var dir = (vertical ? -1 : 1) * (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 * options.majorUnit, options.baseUnit);
var pos = start + dateDiff(date, options.min) * scale * dir;
positions.push(round(pos, COORD_PRECISION));
}
return positions;
},
getMajorTickPositions: function () {
var axis = this;
return axis.getTickPositions(axis.options.majorUnit);
},
getMinorTickPositions: function () {
var axis = this;
return axis.getTickPositions(axis.options.minorUnit);
},
getSlot: function (a, b, limit) {
return NumericAxis.fn.getSlot.call(this, toDate(a), toDate(b), limit);
},
getValue: function (point) {
var value = NumericAxis.fn.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);
},
timeUnits: function (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;
},
translateRange: function (delta, exact) {
var axis = this, options = axis.options, baseUnit = options.baseUnit, weekStartDay = options.weekStartDay, lineBox = axis.lineBox(), size = options.vertical ? lineBox.height() : lineBox.width(), range = axis.range(), scale = size / dateDiff(range.max, range.min), offset = round(delta / scale, DEFAULT_PRECISION), from = addTicks(options.min, offset), to = addTicks(options.max, offset);
if (!exact) {
from = addDuration(from, 0, baseUnit, weekStartDay);
to = addDuration(to, 0, baseUnit, weekStartDay);
}
return {
min: from,
max: to
};
},
scaleRange: function (delta) {
var axis = this, options = axis.options, rounds = math.abs(delta), from = options.min, to = options.max, range, step;
while (rounds--) {
range = dateDiff(from, to);
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);
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
};
}
});
var ClusterLayout = ChartElement.extend({
options: {
vertical: false,
gap: 0,
spacing: 0
},
reflow: function (box) {
var cluster = this, options = cluster.options, vertical = options.vertical, axis = vertical ? Y : X, children = cluster.children, gap = options.gap, spacing = options.spacing, count = children.length, slots = count + gap + spacing * (count - 1), slotSize = (vertical ? box.height() : box.width()) / slots, position = box[axis + 1] + slotSize * (gap / 2), childBox, i;
for (i = 0; i < count; i++) {
childBox = (children[i].box || box).clone();
childBox[axis + 1] = position;
childBox[axis + 2] = position + slotSize;
children[i].reflow(childBox);
if (i < count - 1) {
position += slotSize * spacing;
}
position += slotSize;
}
}
});
var StackWrap = ChartElement.extend({
options: { vertical: true },
reflow: function (targetBox) {
var options = this.options, vertical = options.vertical, positionAxis = vertical ? X : Y, children = this.children, box = this.box = new Box2D(), childrenCount = children.length, i;
for (i = 0; i < childrenCount; i++) {
var currentChild = children[i], childBox;
if (currentChild.visible !== false) {
childBox = currentChild.box.clone();
childBox.snapTo(targetBox, positionAxis);
if (i === 0) {
box = this.box = childBox.clone();
}
currentChild.reflow(childBox);
box.wrap(childBox);
}
}
}
});
var PointEventsMixin = {
click: function (chart, e) {
return chart.trigger(SERIES_CLICK, this.eventArgs(e));
},
hover: function (chart, e) {
return chart.trigger(SERIES_HOVER, this.eventArgs(e));
},
eventArgs: function (e) {
return {
value: this.value,
percentage: this.percentage,
category: this.category,
series: this.series,
dataItem: this.dataItem,
runningTotal: this.runningTotal,
total: this.total,
element: $((e || {}).target),
originalEvent: e,
point: this
};
}
};
var NoteMixin = {
createNote: function () {
var element = this, options = element.options.notes, text = element.noteText || options.label.text;
if (options.visible !== false && defined(text) && text !== null) {
element.note = new Note(element.value, text, element.dataItem, element.category, element.series, element.options.notes);
element.append(element.note);
}
}
};
var Bar = ChartElement.extend({
init: function (value, options) {
var bar = this;
ChartElement.fn.init.call(bar);
bar.options = options;
bar.color = options.color || WHITE;
bar.aboveAxis = valueOrDefault(bar.options.aboveAxis, true);
bar.value = value;
},
defaults: {
border: { width: 1 },
vertical: true,
overlay: { gradient: GLASS },
labels: {
visible: false,
format: '{0}'
},
opacity: 1,
notes: { label: {} }
},
render: function () {
if (this._rendered) {
return;
} else {
this._rendered = true;
}
this.createLabel();
this.createNote();
if (this.errorBar) {
this.append(this.errorBar);
}
},
createLabel: function () {
var options = this.options;
var labels = options.labels;
var labelText;
if (labels.visible) {
if (labels.template) {
var labelTemplate = template(labels.template);
labelText = labelTemplate({
dataItem: this.dataItem,
category: this.category,
value: this.value,
percentage: this.percentage,
runningTotal: this.runningTotal,
total: this.total,
series: this.series
});
} else {
labelText = this.formatValue(labels.format);
}
this.label = new BarLabel(labelText, deepExtend({ vertical: options.vertical }, options.labels));
this.append(this.label);
}
},
formatValue: function (format) {
return this.owner.formatPointValue(this, format);
},
reflow: function (targetBox) {
this.render();
var bar = this, label = bar.label;
bar.box = targetBox;
if (label) {
label.options.aboveAxis = bar.aboveAxis;
label.reflow(targetBox);
}
if (bar.note) {
bar.note.reflow(targetBox);
}
if (bar.errorBars) {
for (var i = 0; i < bar.errorBars.length; i++) {
bar.errorBars[i].reflow(targetBox);
}
}
},
createVisual: function () {
var bar = this;
var box = bar.box;
var options = bar.options;
var customVisual = options.visual;
if (bar.visible !== false) {
ChartElement.fn.createVisual.call(bar);
if (customVisual) {
var visual = this.rectVisual = customVisual({
category: bar.category,
dataItem: bar.dataItem,
value: bar.value,
sender: bar.getChart(),
series: bar.series,
percentage: bar.percentage,
runningTotal: bar.runningTotal,
total: bar.total,
rect: box.toRect(),
createVisual: function () {
var group = new draw.Group();
bar.createRect(group);
return group;
},
options: options
});
if (visual) {
bar.visual.append(visual);
}
} else if (box.width() > 0 && box.height() > 0) {
bar.createRect(bar.visual);
}
}
},
createRect: function (visual) {
var options = this.options;
var border = options.border;
var strokeOpacity = defined(border.opacity) ? border.opacity : options.opacity;
var rect = this.box.toRect();
rect.size.width = Math.round(rect.size.width);
var path = this.rectVisual = draw.Path.fromRect(rect, {
fill: {
color: this.color,
opacity: options.opacity
},
stroke: {
color: this.getBorderColor(),
width: border.width,
opacity: strokeOpacity,
dashType: border.dashType
}
});
var width = this.box.width();
var height = this.box.height();
var size = options.vertical ? width : height;
if (size > BAR_ALIGN_MIN_WIDTH) {
alignPathToPixel(path);
if (width < 1 || height < 1) {
path.options.stroke.lineJoin = 'round';
}
}
visual.append(path);
if (hasGradientOverlay(options)) {
visual.append(this.createGradientOverlay(path, { baseColor: this.color }, deepExtend({
end: !options.vertical ? [
0,
1
] : undefined
}, options.overlay)));
}
},
createHighlight: function (style) {
var highlight = draw.Path.fromRect(this.box.toRect(), style);
return alignPathToPixel(highlight);
},
highlightVisual: function () {
return this.rectVisual;
},
highlightVisualArgs: function () {
return {
options: this.options,
rect: this.box.toRect(),
visual: this.rectVisual
};
},
getBorderColor: function () {
var bar = this, options = bar.options, color = bar.color, border = options.border, borderColor = border.color, brightness = border._brightness || BAR_BORDER_BRIGHTNESS;
if (!defined(borderColor)) {
borderColor = new Color(color).brightness(brightness).toHex();
}
return borderColor;
},
tooltipAnchor: function (tooltipWidth, tooltipHeight) {
var bar = this, options = bar.options, box = bar.box, vertical = options.vertical, aboveAxis = bar.aboveAxis, clipBox = bar.owner.pane.clipBox() || box, x, y;
if (vertical) {
x = box.x2 + TOOLTIP_OFFSET;
y = aboveAxis ? math.max(box.y1, clipBox.y1) : math.min(box.y2, clipBox.y2) - tooltipHeight;
} else {
var x1 = math.max(box.x1, clipBox.x1), x2 = math.min(box.x2, clipBox.x2);
if (options.isStacked) {
x = aboveAxis ? x2 - tooltipWidth : x1;
y = box.y1 - tooltipHeight - TOOLTIP_OFFSET;
} else {
x = aboveAxis ? x2 + TOOLTIP_OFFSET : x1 - tooltipWidth - TOOLTIP_OFFSET;
y = box.y1;
}
}
return new Point2D(x, y);
},
overlapsBox: function (box) {
return this.box.overlaps(box);
}
});
deepExtend(Bar.fn, PointEventsMixin);
deepExtend(Bar.fn, NoteMixin);
var BarChartAnimation = draw.Animation.extend({
options: { duration: INITIAL_ANIMATION_DURATION },
setup: function () {
var element = this.element;
var options = this.options;
var bbox = element.bbox();
if (bbox) {
this.origin = options.origin;
var axis = options.vertical ? Y : X;
var fromScale = this.fromScale = new geom.Point(1, 1);
fromScale[axis] = START_SCALE;
element.transform(geom.transform().scale(fromScale.x, fromScale.y));
} else {
this.abort();
}
},
step: function (pos) {
var scaleX = interpolate(this.fromScale.x, 1, pos);
var scaleY = interpolate(this.fromScale.y, 1, pos);
this.element.transform(geom.transform().scale(scaleX, scaleY, this.origin));
},
abort: function () {
draw.Animation.fn.abort.call(this);
this.element.transform(null);
}
});
draw.AnimationFactory.current.register(BAR, BarChartAnimation);
var FadeInAnimation = draw.Animation.extend({
options: {
duration: 200,
easing: LINEAR
},
setup: function () {
this.fadeTo = this.element.opacity();
this.element.opacity(0);
},
step: function (pos) {
this.element.opacity(pos * this.fadeTo);
}
});
draw.AnimationFactory.current.register(FADEIN, FadeInAnimation);
var ErrorRangeCalculator = function (errorValue, series, field) {
var that = this;
that.initGlobalRanges(errorValue, series, field);
};
ErrorRangeCalculator.prototype = ErrorRangeCalculator.fn = {
percentRegex: /percent(?:\w*)\((\d+)\)/,
standardDeviationRegex: new RegExp('^' + STD_DEV + '(?:\\((\\d+(?:\\.\\d+)?)\\))?$'),
initGlobalRanges: function (errorValue, series, field) {
var that = this, data = series.data, deviationMatch = that.standardDeviationRegex.exec(errorValue);
if (deviationMatch) {
that.valueGetter = that.createValueGetter(series, field);
var average = that.getAverage(data), deviation = that.getStandardDeviation(data, average, false), multiple = deviationMatch[1] ? parseFloat(deviationMatch[1]) : 1, errorRange = {
low: average.value - deviation * multiple,
high: average.value + deviation * multiple
};
that.globalRange = function () {
return errorRange;
};
} else if (errorValue.indexOf && errorValue.indexOf(STD_ERR) >= 0) {
that.valueGetter = that.createValueGetter(series, field);
var standardError = that.getStandardError(data, that.getAverage(data));
that.globalRange = function (value) {
return {
low: value - standardError,
high: value + standardError
};
};
}
},
createValueGetter: function (series, field) {
var data = series.data, binder = SeriesBinder.current, valueFields = binder.valueFields(series), item = defined(data[0]) ? data[0] : {}, idx, srcValueFields, valueGetter;
if (isArray(item)) {
idx = field ? indexOf(field, valueFields) : 0;
valueGetter = getter('[' + idx + ']');
} else if (isNumber(item)) {
valueGetter = getter();
} else if (typeof item === OBJECT) {
srcValueFields = binder.sourceFields(series, valueFields);
valueGetter = getter(srcValueFields[indexOf(field, valueFields)]);
}
return valueGetter;
},
getErrorRange: function (pointValue, errorValue) {
var that = this, low, high, value;
if (!defined(errorValue)) {
return;
}
if (that.globalRange) {
return that.globalRange(pointValue);
}
if (isArray(errorValue)) {
low = pointValue - errorValue[0];
high = pointValue + errorValue[1];
} else if (isNumber(value = parseFloat(errorValue))) {
low = pointValue - value;
high = pointValue + value;
} else if (value = that.percentRegex.exec(errorValue)) {
var percentValue = pointValue * (parseFloat(value[1]) / 100);
low = pointValue - math.abs(percentValue);
high = pointValue + math.abs(percentValue);
} else {
throw new Error('Invalid ErrorBar value: ' + errorValue);
}
return {
low: low,
high: high
};
},
getStandardError: function (data, average) {
return this.getStandardDeviation(data, average, true) / math.sqrt(average.count);
},
getStandardDeviation: function (data, average, isSample) {
var squareDifferenceSum = 0, length = data.length, total = isSample ? average.count - 1 : average.count, value;
for (var i = 0; i < length; i++) {
value = this.valueGetter(data[i]);
if (isNumber(value)) {
squareDifferenceSum += math.pow(value - average.value, 2);
}
}
return math.sqrt(squareDifferenceSum / total);
},
getAverage: function (data) {
var sum = 0, count = 0, length = data.length, value;
for (var i = 0; i < length; i++) {
value = this.valueGetter(data[i]);
if (isNumber(value)) {
sum += value;
count++;
}
}
return {
value: sum / count,
count: count
};
}
};
var CategoricalChart = ChartElement.extend({
init: function (plotArea, options) {
var chart = this;
ChartElement.fn.init.call(chart, options);
chart.plotArea = plotArea;
chart.categoryAxis = plotArea.seriesCategoryAxis(options.series[0]);
chart.valueAxisRanges = {};
chart.points = [];
chart.categoryPoints = [];
chart.seriesPoints = [];
chart.seriesOptions = [];
chart._evalSeries = [];
chart.render();
},
options: {
series: [],
invertAxes: false,
isStacked: false,
clip: true
},
render: function () {
var chart = this;
chart.traverseDataPoints(proxy(chart.addValue, chart));
},
pointOptions: function (series, seriesIx) {
var options = this.seriesOptions[seriesIx];
if (!options) {
var defaults = this.pointType().fn.defaults;
this.seriesOptions[seriesIx] = options = deepExtend({}, defaults, { vertical: !this.options.invertAxes }, series);
}
return options;
},
plotValue: function (point) {
if (!point) {
return 0;
}
if (this.options.isStacked100 && isNumber(point.value)) {
var categoryIx = point.categoryIx;
var categoryPts = this.categoryPoints[categoryIx];
var categorySum = 0;
var otherValues = [];
for (var i = 0; i < categoryPts.length; i++) {
var other = categoryPts[i];
if (other) {
var stack = point.series.stack;
var otherStack = other.series.stack;
if (stack && otherStack && stack.group !== otherStack.group) {
continue;
}
if (isNumber(other.value)) {
categorySum += math.abs(other.value);
otherValues.push(math.abs(other.value));
}
}
}
if (categorySum > 0) {
return point.value / categorySum;
}
}
return point.value;
},
plotRange: function (point, startValue) {
var categoryIx = point.categoryIx;
var categoryPts = this.categoryPoints[categoryIx];
if (this.options.isStacked) {
startValue = startValue || 0;
var plotValue = this.plotValue(point);
var positive = plotValue >= 0;
var prevValue = startValue;
var isStackedBar = false;
for (var i = 0; i < categoryPts.length; i++) {
var other = categoryPts[i];
if (point === other) {
break;
}
var stack = point.series.stack;
var otherStack = other.series.stack;
if (stack && otherStack) {
if (typeof stack === STRING && stack !== otherStack) {
continue;
}
if (stack.group && stack.group !== otherStack.group) {
continue;
}
}
var otherValue = this.plotValue(other);
if (otherValue >= 0 && positive || otherValue < 0 && !positive) {
prevValue += otherValue;
plotValue += otherValue;
isStackedBar = true;
if (this.options.isStacked100) {
plotValue = math.min(plotValue, 1);
}
}
}
if (isStackedBar) {
prevValue -= startValue;
}
return [
prevValue,
plotValue
];
}
var series = point.series;
var valueAxis = this.seriesValueAxis(series);
var axisCrossingValue = this.categoryAxisCrossingValue(valueAxis);
return [
axisCrossingValue,
point.value || axisCrossingValue
];
},
stackLimits: function (axisName, stackName) {
var min = MAX_VALUE;
var max = MIN_VALUE;
for (var i = 0; i < this.categoryPoints.length; i++) {
var categoryPts = this.categoryPoints[i];
for (var pIx = 0; pIx < categoryPts.length; pIx++) {
var point = categoryPts[pIx];
if (point) {
if (point.series.stack === stackName || point.series.axis === axisName) {
var to = this.plotRange(point, 0)[1];
if (defined(to) && isFinite(to)) {
max = math.max(max, to);
min = math.min(min, to);
}
}
}
}
}
return {
min: min,
max: max
};
},
updateStackRange: function () {
var chart = this;
var chartSeries = chart.options.series;
var isStacked = chart.options.isStacked;
var limits;
var limitsCache = {};
if (isStacked) {
for (var i = 0; i < chartSeries.length; i++) {
var series = chartSeries[i];
var axisName = series.axis;
var key = axisName + series.stack;
limits = limitsCache[key];
if (!limits) {
limits = chart.stackLimits(axisName, series.stack);
var errorTotals = chart.errorTotals;
if (errorTotals) {
if (errorTotals.negative.length) {
limits.min = math.min(limits.min, sparseArrayMin(errorTotals.negative));
}
if (errorTotals.positive.length) {
limits.max = math.max(limits.max, sparseArrayMax(errorTotals.positive));
}
}
if (limits.min !== MAX_VALUE || limits.max !== MIN_VALUE) {
limitsCache[key] = limits;
} else {
limits = null;
}
}
if (limits) {
chart.valueAxisRanges[axisName] = limits;
}
}
}
},
addErrorBar: function (point, data, categoryIx) {
var chart = this, value = point.value, series = point.series, seriesIx = point.seriesIx, errorBars = point.options.errorBars, errorRange, lowValue = data.fields[ERROR_LOW_FIELD], highValue = data.fields[ERROR_HIGH_FIELD];
if (isNumber(lowValue) && isNumber(highValue)) {
errorRange = {
low: lowValue,
high: highValue
};
} else if (errorBars && defined(errorBars.value)) {
chart.seriesErrorRanges = chart.seriesErrorRanges || [];
chart.seriesErrorRanges[seriesIx] = chart.seriesErrorRanges[seriesIx] || new ErrorRangeCalculator(errorBars.value, series, VALUE);
errorRange = chart.seriesErrorRanges[seriesIx].getErrorRange(value, errorBars.value);
}
if (errorRange) {
point.low = errorRange.low;
point.high = errorRange.high;
chart.addPointErrorBar(point, categoryIx);
}
},
addPointErrorBar: function (point, categoryIx) {
var chart = this, series = point.series, low = point.low, high = point.high, isVertical = !chart.options.invertAxes, options = point.options.errorBars, errorBar, stackedErrorRange;
if (chart.options.isStacked) {
stackedErrorRange = chart.stackedErrorRange(point, categoryIx);
low = stackedErrorRange.low;
high = stackedErrorRange.high;
} else {
var fields = {
categoryIx: categoryIx,
series: series
};
chart.updateRange({ value: low }, fields);
chart.updateRange({ value: high }, fields);
}
errorBar = new CategoricalErrorBar(low, high, isVertical, chart, series, options);
point.errorBars = [errorBar];
point.append(errorBar);
},
stackedErrorRange: function (point, categoryIx) {
var chart = this, plotValue = chart.plotRange(point, 0)[1] - point.value, low = point.low + plotValue, high = point.high + plotValue;
chart.errorTotals = chart.errorTotals || {
positive: [],
negative: []
};
if (low < 0) {
chart.errorTotals.negative[categoryIx] = math.min(chart.errorTotals.negative[categoryIx] || 0, low);
}
if (high > 0) {
chart.errorTotals.positive[categoryIx] = math.max(chart.errorTotals.positive[categoryIx] || 0, high);
}
return {
low: low,
high: high
};
},
addValue: function (data, fields) {
var chart = this;
var categoryIx = fields.categoryIx;
var series = fields.series;
var seriesIx = fields.seriesIx;
var categoryPoints = chart.categoryPoints[categoryIx];
if (!categoryPoints) {
chart.categoryPoints[categoryIx] = categoryPoints = [];
}
var seriesPoints = chart.seriesPoints[seriesIx];
if (!seriesPoints) {
chart.seriesPoints[seriesIx] = seriesPoints = [];
}
var point = chart.createPoint(data, fields);
if (point) {
$.extend(point, fields);
point.owner = chart;
point.dataItem = series.data[categoryIx];
point.noteText = data.fields.noteText;
chart.addErrorBar(point, data, categoryIx);
}
chart.points.push(point);
seriesPoints.push(point);
categoryPoints.push(point);
chart.updateRange(data.valueFields, fields);
},
evalPointOptions: function (options, value, category, categoryIx, series, seriesIx) {
var state = {
defaults: series._defaults,
excluded: [
'data',
'aggregate',
'_events',
'tooltip',
'template',
'visual',
'toggle',
'_outOfRangeMinPoint',
'_outOfRangeMaxPoint'
]
};
var doEval = this._evalSeries[seriesIx];
if (!defined(doEval)) {
this._evalSeries[seriesIx] = doEval = evalOptions(options, {}, state, true);
}
if (doEval) {
options = deepExtend({}, options);
evalOptions(options, {
value: value,
category: category,
index: categoryIx,
series: series,
dataItem: series.data[categoryIx]
}, state);
}
return options;
},
updateRange: function (data, fields) {
var chart = this, axisName = fields.series.axis, value = data.value, axisRange = chart.valueAxisRanges[axisName];
if (isFinite(value) && value !== null) {
axisRange = chart.valueAxisRanges[axisName] = axisRange || {
min: MAX_VALUE,
max: MIN_VALUE
};
axisRange.min = math.min(axisRange.min, value);
axisRange.max = math.max(axisRange.max, value);
}
},
seriesValueAxis: function (series) {
var plotArea = this.plotArea, axisName = series.axis, axis = axisName ? plotArea.namedValueAxes[axisName] : plotArea.valueAxis;
if (!axis) {
throw new Error('Unable to locate value axis with name ' + axisName);
}
return axis;
},
reflow: function (targetBox) {
var chart = this, pointIx = 0, categorySlots = chart.categorySlots = [], chartPoints = chart.points, categoryAxis = chart.categoryAxis, value, valueAxis, point;
chart.traverseDataPoints(function (data, fields) {
var categoryIx = fields.categoryIx;
var currentSeries = fields.series;
value = chart.pointValue(data);
valueAxis = chart.seriesValueAxis(currentSeries);
point = chartPoints[pointIx++];
var categorySlot = categorySlots[categoryIx];
if (!categorySlot) {
categorySlots[categoryIx] = categorySlot = chart.categorySlot(categoryAxis, categoryIx, valueAxis);
}
if (point) {
var plotRange = chart.plotRange(point, valueAxis.startValue());
var valueSlot = valueAxis.getSlot(plotRange[0], plotRange[1], !chart.options.clip);
if (valueSlot) {
var pointSlot = chart.pointSlot(categorySlot, valueSlot);
point.aboveAxis = chart.aboveAxis(point, valueAxis);
if (chart.options.isStacked100) {
point.percentage = chart.plotValue(point);
}
chart.reflowPoint(point, pointSlot);
} else {
point.visible = false;
}
}
});
chart.reflowCategories(categorySlots);
chart.box = targetBox;
},
aboveAxis: function (point, valueAxis) {
var axisCrossingValue = this.categoryAxisCrossingValue(valueAxis);
var value = point.value;
return valueAxis.options.reverse ? value < axisCrossingValue : value >= axisCrossingValue;
},
categoryAxisCrossingValue: function (valueAxis) {
var categoryAxis = this.categoryAxis, options = valueAxis.options, crossingValues = [].concat(options.axisCrossingValues || options.axisCrossingValue);
return crossingValues[categoryAxis.axisIndex || 0] || 0;
},
reflowPoint: function (point, pointSlot) {
point.reflow(pointSlot);
},
reflowCategories: function () {
},
pointSlot: function (categorySlot, valueSlot) {
var chart = this, options = chart.options, invertAxes = options.invertAxes, slotX = invertAxes ? valueSlot : categorySlot, slotY = invertAxes ? categorySlot : valueSlot;
return new Box2D(slotX.x1, slotY.y1, slotX.x2, slotY.y2);
},
categorySlot: function (categoryAxis, categoryIx) {
return categoryAxis.getSlot(categoryIx);
},
traverseDataPoints: function (callback) {
var chart = this, options = chart.options, series = options.series, categories = chart.categoryAxis.options.categories || [], count = categoriesCount(series), categoryIx, seriesIx, pointData, currentCategory, currentSeries, seriesCount = series.length;
for (seriesIx = 0; seriesIx < seriesCount; seriesIx++) {
this._outOfRangeCallback(series[seriesIx], '_outOfRangeMinPoint', seriesIx, callback);
}
for (categoryIx = 0; categoryIx < count; categoryIx++) {
for (seriesIx = 0; seriesIx < seriesCount; seriesIx++) {
currentSeries = series[seriesIx];
currentCategory = categories[categoryIx];
pointData = this._bindPoint(currentSeries, seriesIx, categoryIx);
callback(pointData, {
category: currentCategory,
categoryIx: categoryIx,
series: currentSeries,
seriesIx: seriesIx
});
}
}
for (seriesIx = 0; seriesIx < seriesCount; seriesIx++) {
this._outOfRangeCallback(series[seriesIx], '_outOfRangeMaxPoint', seriesIx, callback);
}
},
_outOfRangeCallback: function (series, field, seriesIx, callback) {
var outOfRangePoint = series[field];
if (outOfRangePoint) {
var categoryIx = outOfRangePoint.categoryIx;
var pointData = this._bindPoint(series, seriesIx, categoryIx, outOfRangePoint.item);
callback(pointData, {
category: outOfRangePoint.category,
categoryIx: categoryIx,
series: series,
seriesIx: seriesIx
});
}
},
_bindPoint: function (series, seriesIx, categoryIx, item) {
if (!this._bindCache) {
this._bindCache = [];
}
var bindCache = this._bindCache[seriesIx];
if (!bindCache) {
bindCache = this._bindCache[seriesIx] = [];
}
var data = bindCache[categoryIx];
if (!data) {
data = bindCache[categoryIx] = SeriesBinder.current.bindPoint(series, categoryIx, item);
}
return data;
},
formatPointValue: function (point, format) {
if (point.value === null) {
return '';
}
return autoFormat(format, point.value);
},
pointValue: function (data) {
return data.valueFields.value;
}
});
var BarChart = CategoricalChart.extend({
options: { animation: { type: BAR } },
render: function () {
var chart = this;
CategoricalChart.fn.render.apply(chart);
chart.updateStackRange();
},
pointType: function () {
return Bar;
},
clusterType: function () {
return ClusterLayout;
},
stackType: function () {
return StackWrap;
},
stackLimits: function (axisName, stackName) {
var limits = CategoricalChart.fn.stackLimits.call(this, axisName, stackName);
return limits;
},
createPoint: function (data, fields) {
var chart = this;
var categoryIx = fields.categoryIx;
var category = fields.category;
var series = fields.series;
var seriesIx = fields.seriesIx;
var value = chart.pointValue(data);
var options = chart.options;
var children = chart.children;
var isStacked = chart.options.isStacked;
var point;
var pointType = chart.pointType();
var pointOptions;
var cluster;
var clusterType = chart.clusterType();
pointOptions = this.pointOptions(series, seriesIx);
var labelOptions = pointOptions.labels;
if (isStacked) {
if (labelOptions.position == OUTSIDE_END) {
labelOptions.position = INSIDE_END;
}
}
pointOptions.isStacked = isStacked;
var color = data.fields.color || series.color;
if (value < 0 && pointOptions.negativeColor) {
color = pointOptions.negativeColor;
}
pointOptions = chart.evalPointOptions(pointOptions, value, category, categoryIx, series, seriesIx);
if (kendo.isFunction(series.color)) {
color = pointOptions.color;
}
point = new pointType(value, pointOptions);
point.color = color;
cluster = children[categoryIx];
if (!cluster) {
cluster = new clusterType({
vertical: options.invertAxes,
gap: options.gap,
spacing: options.spacing
});
chart.append(cluster);
}
if (isStacked) {
var stackWrap = chart.getStackWrap(series, cluster);
stackWrap.append(point);
} else {
cluster.append(point);
}
return point;
},
getStackWrap: function (series, cluster) {
var stack = series.stack;
var stackGroup = stack ? stack.group || stack : stack;
var wraps = cluster.children;
var stackWrap;
if (typeof stackGroup === STRING) {
for (var i = 0; i < wraps.length; i++) {
if (wraps[i]._stackGroup === stackGroup) {
stackWrap = wraps[i];
break;
}
}
} else {
stackWrap = wraps[0];
}
if (!stackWrap) {
var stackType = this.stackType();
stackWrap = new stackType({ vertical: !this.options.invertAxes });
stackWrap._stackGroup = stackGroup;
cluster.append(stackWrap);
}
return stackWrap;
},
categorySlot: function (categoryAxis, categoryIx, valueAxis) {
var chart = this, options = chart.options, categorySlot = categoryAxis.getSlot(categoryIx), startValue = valueAxis.startValue(), stackAxis, zeroSlot;
if (options.isStacked) {
zeroSlot = valueAxis.getSlot(startValue, startValue, true);
stackAxis = options.invertAxes ? X : Y;
categorySlot[stackAxis + 1] = categorySlot[stackAxis + 2] = zeroSlot[stackAxis + 1];
}
return categorySlot;
},
reflowCategories: function (categorySlots) {
var chart = this, children = chart.children, childrenLength = children.length, i;
for (i = 0; i < childrenLength; i++) {
children[i].reflow(categorySlots[i]);
}
},
createAnimation: function () {
this._setAnimationOptions();
ChartElement.fn.createAnimation.call(this);
if (anyHasZIndex(this.options.series)) {
this._setChildrenAnimation();
}
},
_setChildrenAnimation: function () {
var points = this.points;
var point, pointVisual;
for (var idx = 0; idx < points.length; idx++) {
point = points[idx];
pointVisual = point.visual;
if (pointVisual && defined(pointVisual.options.zIndex)) {
point.options.animation = this.options.animation;
point.createAnimation();
}
}
},
_setAnimationOptions: function () {
var options = this.options;
var animation = options.animation || {};
var origin;
if (this.options.isStacked) {
var valueAxis = this.seriesValueAxis(options.series[0]);
origin = valueAxis.getSlot(valueAxis.startValue());
} else {
origin = this.categoryAxis.getSlot(0);
}
animation.origin = new geom.Point(origin.x1, origin.y1);
animation.vertical = !options.invertAxes;
}
});
var RangeBar = Bar.extend({
defaults: {
labels: { format: '{0} - {1}' },
tooltip: { format: '{1}' }
},
createLabel: function () {
var labels = this.options.labels;
var fromOptions = deepExtend({}, labels, labels.from);
var toOptions = deepExtend({}, labels, labels.to);
if (fromOptions.visible) {
this.labelFrom = this._createLabel(fromOptions);
this.append(this.labelFrom);
}
if (toOptions.visible) {
this.labelTo = this._createLabel(toOptions);
this.append(this.labelTo);
}
},
_createLabel: function (options) {
var labelText;
if (options.template) {
var labelTemplate = template(options.template);
labelText = labelTemplate({
dataItem: this.dataItem,
category: this.category,
value: this.value,
percentage: this.percentage,
runningTotal: this.runningTotal,
total: this.total,
series: this.series
});
} else {
labelText = this.formatValue(options.format);
}
return new BarLabel(labelText, deepExtend({ vertical: this.options.vertical }, options));
},
reflow: function (targetBox) {
this.render();
var rangeBar = this, labelFrom = rangeBar.labelFrom, labelTo = rangeBar.labelTo;
rangeBar.box = targetBox;
if (labelFrom) {
labelFrom.options.aboveAxis = rangeBar.value.from > rangeBar.value.to;
labelFrom.reflow(targetBox);
}
if (labelTo) {
labelTo.options.aboveAxis = rangeBar.value.to > rangeBar.value.from;
labelTo.reflow(targetBox);
}
if (rangeBar.note) {
rangeBar.note.reflow(targetBox);
}
}
});
var RangeBarChart = BarChart.extend({
pointType: function () {
return RangeBar;
},
pointValue: function (data) {
return data.valueFields;
},
formatPointValue: function (point, format) {
if (point.value.from === null && point.value.to === null) {
return '';
}
return autoFormat(format, point.value.from, point.value.to);
},
plotLimits: CategoricalChart.fn.plotLimits,
plotRange: function (point) {
if (!point) {
return 0;
}
return [
point.value.from,
point.value.to
];
},
updateRange: function (value, fields) {
var chart = this, axisName = fields.series.axis, from = value.from, to = value.to, axisRange = chart.valueAxisRanges[axisName];
if (value !== null && isNumber(from) && isNumber(to)) {
axisRange = chart.valueAxisRanges[axisName] = axisRange || {
min: MAX_VALUE,
max: MIN_VALUE
};
axisRange.min = math.min(axisRange.min, from);
axisRange.max = math.max(axisRange.max, from);
axisRange.min = math.min(axisRange.min, to);
axisRange.max = math.max(axisRange.max, to);
}
},
aboveAxis: function (point) {
var value = point.value;
return value.from < value.to;
}
});
var BulletChart = CategoricalChart.extend({
init: function (plotArea, options) {
var chart = this;
chart.wrapData(options);
CategoricalChart.fn.init.call(chart, plotArea, options);
},
options: { animation: { type: BAR } },
wrapData: function (options) {
var series = options.series, i, data, seriesItem;
for (i = 0; i < series.length; i++) {
seriesItem = series[i];
data = seriesItem.data;
if (data && !isArray(data[0]) && typeof data[0] != OBJECT) {
seriesItem.data = [data];
}
}
},
reflowCategories: function (categorySlots) {
var chart = this, children = chart.children, childrenLength = children.length, i;
for (i = 0; i < childrenLength; i++) {
children[i].reflow(categorySlots[i]);
}
},
plotRange: function (point) {
var series = point.series;
var valueAxis = this.seriesValueAxis(series);
var axisCrossingValue = this.categoryAxisCrossingValue(valueAxis);
return [
axisCrossingValue,
point.value.current || axisCrossingValue
];
},
createPoint: function (data, fields) {
var chart = this;
var categoryIx = fields.categoryIx;
var category = fields.category;
var series = fields.series;
var seriesIx = fields.seriesIx;
var value = data.valueFields;
var options = chart.options;
var children = chart.children;
var bullet;
var bulletOptions;
var cluster;
bulletOptions = deepExtend({
vertical: !options.invertAxes,
overlay: series.overlay,
categoryIx: categoryIx,
invertAxes: options.invertAxes
}, series);
var color = data.fields.color || series.color;
bulletOptions = chart.evalPointOptions(bulletOptions, value, category, categoryIx, series, seriesIx);
if (kendo.isFunction(series.color)) {
color = bulletOptions.color;
}
bullet = new Bullet(value, bulletOptions);
bullet.color = color;
cluster = children[categoryIx];
if (!cluster) {
cluster = new ClusterLayout({
vertical: options.invertAxes,
gap: options.gap,
spacing: options.spacing
});
chart.append(cluster);
}
cluster.append(bullet);
return bullet;
},
updateRange: function (value, fields) {
var chart = this, axisName = fields.series.axis, current = value.current, target = value.target, axisRange = chart.valueAxisRanges[axisName];
if (defined(current) && !isNaN(current) && defined(target && !isNaN(target))) {
axisRange = chart.valueAxisRanges[axisName] = axisRange || {
min: MAX_VALUE,
max: MIN_VALUE
};
axisRange.min = math.min.apply(math, [
axisRange.min,
current,
target
]);
axisRange.max = math.max.apply(math, [
axisRange.max,
current,
target
]);
}
},
formatPointValue: function (point, format) {
return autoFormat(format, point.value.current, point.value.target);
},
pointValue: function (data) {
return data.valueFields.current;
},
aboveAxis: function (point) {
var value = point.value.current;
return value > 0;
},
createAnimation: function () {
var points = this.points;
var point;
this._setAnimationOptions();
for (var idx = 0; idx < points.length; idx++) {
point = points[idx];
point.options.animation = this.options.animation;
point.createAnimation();
}
},
_setAnimationOptions: BarChart.fn._setAnimationOptions
});
var Bullet = ChartElement.extend({
init: function (value, options) {
var bullet = this;
ChartElement.fn.init.call(bullet, options);
bullet.aboveAxis = bullet.options.aboveAxis;
bullet.color = options.color || WHITE;
bullet.value = value;
},
options: {
border: { width: 1 },
vertical: false,
opacity: 1,
target: {
shape: '',
border: {
width: 0,
color: 'green'
},
line: { width: 2 }
},
tooltip: { format: 'Current: {0}</br>Target: {1}' }
},
render: function () {
var bullet = this, options = bullet.options;
if (!bullet._rendered) {
bullet._rendered = true;
if (defined(bullet.value.target)) {
bullet.target = new Target({
type: options.target.shape,
background: options.target.color || bullet.color,
opacity: options.opacity,
zIndex: options.zIndex,
border: options.target.border,
vAlign: TOP,
align: RIGHT
});
bullet.append(bullet.target);
}
bullet.createNote();
}
},
reflow: function (box) {
this.render();
var bullet = this, options = bullet.options, chart = bullet.owner, target = bullet.target, invertAxes = options.invertAxes, valueAxis = chart.seriesValueAxis(bullet.options), categorySlot = chart.categorySlot(chart.categoryAxis, options.categoryIx, valueAxis), targetValueSlot = valueAxis.getSlot(bullet.value.target), targetSlotX = invertAxes ? targetValueSlot : categorySlot, targetSlotY = invertAxes ? categorySlot : targetValueSlot, targetSlot;
if (target) {
targetSlot = new Box2D(targetSlotX.x1, targetSlotY.y1, targetSlotX.x2, targetSlotY.y2);
target.options.height = invertAxes ? targetSlot.height() : options.target.line.width;
target.options.width = invertAxes ? options.target.line.width : targetSlot.width();
target.reflow(targetSlot);
}
if (bullet.note) {
bullet.note.reflow(box);
}
bullet.box = box;
},
createVisual: function () {
ChartElement.fn.createVisual.call(this);
var options = this.options;
var body = draw.Path.fromRect(this.box.toRect(), {
fill: {
color: this.color,
opacity: options.opacity
},
stroke: null
});
if (options.border.width > 0) {
body.options.set('stroke', {
color: options.border.color || this.color,
width: options.border.width,
dashType: options.border.dashType,
opacity: valueOrDefault(options.border.opacity, options.opacity)
});
}
this.bodyVisual = body;
alignPathToPixel(body);
this.visual.append(body);
},
createAnimation: function () {
if (this.bodyVisual) {
this.animation = draw.Animation.create(this.bodyVisual, this.options.animation);
}
},
tooltipAnchor: function (tooltipWidth, tooltipHeight) {
var bar = this, options = bar.options, box = bar.box, vertical = options.vertical, aboveAxis = bar.aboveAxis, clipBox = bar.owner.pane.clipBox() || box, x, y;
if (vertical) {
x = box.x2 + TOOLTIP_OFFSET;
y = aboveAxis ? math.max(box.y1, clipBox.y1) : math.min(box.y2, clipBox.y2) - tooltipHeight;
} else {
var x1 = math.max(box.x1, clipBox.x1), x2 = math.min(box.x2, clipBox.x2);
if (options.isStacked) {
x = aboveAxis ? x2 - tooltipWidth : x1;
y = box.y1 - tooltipHeight - TOOLTIP_OFFSET;
} else {
x = aboveAxis ? x2 + TOOLTIP_OFFSET : x1 - tooltipWidth - TOOLTIP_OFFSET;
y = box.y1;
}
}
return new Point2D(x, y);
},
createHighlight: function (style) {
return draw.Path.fromRect(this.box.toRect(), style);
},
highlightVisual: function () {
return this.bodyVisual;
},
highlightVisualArgs: function () {
return {
rect: this.box.toRect(),
visual: this.bodyVisual,
options: this.options
};
},
formatValue: function (format) {
var bullet = this;
return bullet.owner.formatPointValue(bullet, format);
}
});
deepExtend(Bullet.fn, PointEventsMixin);
deepExtend(Bullet.fn, NoteMixin);
var Target = ShapeElement.extend();
deepExtend(Target.fn, PointEventsMixin);
var ErrorBarBase = ChartElement.extend({
init: function (low, high, isVertical, chart, series, options) {
var errorBar = this;
errorBar.low = low;
errorBar.high = high;
errorBar.isVertical = isVertical;
errorBar.chart = chart;
errorBar.series = series;
ChartElement.fn.init.call(errorBar, options);
},
options: {
animation: {
type: FADEIN,
delay: INITIAL_ANIMATION_DURATION
},
endCaps: true,
line: { width: 1 },
zIndex: 1
},
getAxis: function () {
},
reflow: function (targetBox) {
var linePoints, errorBar = this, endCaps = errorBar.options.endCaps, isVertical = errorBar.isVertical, axis = errorBar.getAxis(), valueBox = axis.getSlot(errorBar.low, errorBar.high), centerBox = targetBox.center(), capsWidth = errorBar.getCapsWidth(targetBox, isVertical), capValue = isVertical ? centerBox.x : centerBox.y, capStart = capValue - capsWidth, capEnd = capValue + capsWidth;
if (isVertical) {
linePoints = [
Point2D(centerBox.x, valueBox.y1),
Point2D(centerBox.x, valueBox.y2)
];
if (endCaps) {
linePoints.push(Point2D(capStart, valueBox.y1), Point2D(capEnd, valueBox.y1), Point2D(capStart, valueBox.y2), Point2D(capEnd, valueBox.y2));
}
errorBar.box = Box2D(capStart, valueBox.y1, capEnd, valueBox.y2);
} else {
linePoints = [
Point2D(valueBox.x1, centerBox.y),
Point2D(valueBox.x2, centerBox.y)
];
if (endCaps) {
linePoints.push(Point2D(valueBox.x1, capStart), Point2D(valueBox.x1, capEnd), Point2D(valueBox.x2, capStart), Point2D(valueBox.x2, capEnd));
}
errorBar.box = Box2D(valueBox.x1, capStart, valueBox.x2, capEnd);
}
errorBar.linePoints = linePoints;
},
getCapsWidth: function (box, isVertical) {
var boxSize = isVertical ? box.width() : box.height(), capsWidth = math.min(math.floor(boxSize / 2), DEFAULT_ERROR_BAR_WIDTH) || DEFAULT_ERROR_BAR_WIDTH;
return capsWidth;
},
createVisual: function () {
var that = this;
var options = that.options;
var visual = options.visual;
if (visual) {
that.visual = visual({
low: that.low,
high: that.high,
rect: that.box.toRect(),
sender: that.getChart(),
options: {
endCaps: options.endCaps,
color: options.color,
line: options.line
},
createVisual: function () {
that.createDefaultVisual();
var defaultVisual = that.visual;
delete that.visual;
return defaultVisual;
}
});
} else {
that.createDefaultVisual();
}
},
createDefaultVisual: function () {
var errorBar = this, options = errorBar.options, lineOptions = {
stroke: {
color: options.color,
width: options.line.width,
dashType: options.line.dashType
}
}, linePoints = errorBar.linePoints;
ChartElement.fn.createVisual.call(this);
for (var idx = 0; idx < linePoints.length; idx += 2) {
var line = new draw.Path(lineOptions).moveTo(linePoints[idx].x, linePoints[idx].y).lineTo(linePoints[idx + 1].x, linePoints[idx + 1].y);
this.visual.append(line);
}
}
});
var CategoricalErrorBar = ErrorBarBase.extend({
getAxis: function () {
var errorBar = this, chart = errorBar.chart, series = errorBar.series, axis = chart.seriesValueAxis(series);
return axis;
}
});
var ScatterErrorBar = ErrorBarBase.extend({
getAxis: function () {
var errorBar = this, chart = errorBar.chart, series = errorBar.series, axes = chart.seriesAxes(series), axis = errorBar.isVertical ? axes.y : axes.x;
return axis;
}
});
var LinePoint = ChartElement.extend({
init: function (value, options) {
var point = this;
ChartElement.fn.init.call(point);
point.value = value;
point.options = options;
point.aboveAxis = valueOrDefault(point.options.aboveAxis, true);
point.tooltipTracking = true;
},
defaults: {
vertical: true,
markers: {
visible: true,
background: WHITE,
size: LINE_MARKER_SIZE,
type: CIRCLE,
border: { width: 2 },
opacity: 1
},
labels: {
visible: false,
position: ABOVE,
margin: getSpacing(3),
padding: getSpacing(4),
animation: {
type: FADEIN,
delay: INITIAL_ANIMATION_DURATION
}
},
notes: { label: {} },
highlight: { markers: { border: {} } }
},
render: function () {
var point = this, options = point.options, markers = options.markers, labels = options.labels, labelText = point.value;
if (point._rendered) {
return;
} else {
point._rendered = true;
}
if (markers.visible && markers.size) {
point.marker = point.createMarker();
point.append(point.marker);
}
if (labels.visible) {
if (labels.template) {
var labelTemplate = template(labels.template);
labelText = labelTemplate({
dataItem: point.dataItem,
category: point.category,
value: point.value,
percentage: point.percentage,
series: point.series
});
} else if (labels.format) {
labelText = point.formatValue(labels.format);
}
point.label = new TextBox(labelText, deepExtend({
align: CENTER,
vAlign: CENTER,
margin: {
left: 5,
right: 5
},
zIndex: valueOrDefault(labels.zIndex, this.series.zIndex)
}, labels));
point.append(point.label);
}
point.createNote();
if (point.errorBar) {
point.append(point.errorBar);
}
},
markerBorder: function () {
var options = this.options.markers;
var background = options.background;
var border = deepExtend({ color: this.color }, options.border);
if (!defined(border.color)) {
border.color = new Color(background).brightness(BAR_BORDER_BRIGHTNESS).toHex();
}
return border;
},
createVisual: noop,
createMarker: function () {
var options = this.options.markers;
var marker = new ShapeElement({
type: options.type,
width: options.size,
height: options.size,
rotation: options.rotation,
background: options.background,
border: this.markerBorder(),
opacity: options.opacity,
zIndex: valueOrDefault(options.zIndex, this.series.zIndex),
animation: options.animation,
visual: options.visual
}, {
dataItem: this.dataItem,
value: this.value,
series: this.series,
category: this.category
});
return marker;
},
markerBox: function () {
if (!this.marker) {
this.marker = this.createMarker();
this.marker.reflow(this._childBox);
}
return this.marker.box;
},
reflow: function (targetBox) {
var point = this, options = point.options, vertical = options.vertical, aboveAxis = point.aboveAxis, childBox, center;
point.render();
point.box = targetBox;
childBox = targetBox.clone();
if (vertical) {
if (aboveAxis) {
childBox.y1 -= childBox.height();
} else {
childBox.y2 += childBox.height();
}
} else {
if (aboveAxis) {
childBox.x1 += childBox.width();
} else {
childBox.x2 -= childBox.width();
}
}
point._childBox = childBox;
if (point.marker) {
point.marker.reflow(childBox);
}
point.reflowLabel(childBox);
if (point.errorBars) {
for (var i = 0; i < point.errorBars.length; i++) {
point.errorBars[i].reflow(childBox);
}
}
if (point.note) {
var noteTargetBox = point.markerBox();
if (!point.marker) {
center = noteTargetBox.center();
noteTargetBox = Box2D(center.x, center.y, center.x, center.y);
}
point.note.reflow(noteTargetBox);
}
},
reflowLabel: function (box) {
var point = this, options = point.options, label = point.label, anchor = options.labels.position;
if (label) {
anchor = anchor === ABOVE ? TOP : anchor;
anchor = anchor === BELOW ? BOTTOM : anchor;
label.reflow(box);
label.box.alignTo(point.markerBox(), anchor);
label.reflow(label.box);
}
},
createHighlight: function () {
var highlight = this.options.highlight;
var markers = highlight.markers;
var defaultColor = this.markerBorder().color;
var options = this.options.markers;
var shadow = new ShapeElement({
type: options.type,
width: options.size,
height: options.size,
rotation: options.rotation,
background: markers.color || defaultColor,
border: {
color: markers.border.color,
width: markers.border.width,
opacity: valueOrDefault(markers.border.opacity, 1)
},
opacity: valueOrDefault(markers.opacity, 1)
});
shadow.reflow(this._childBox);
return shadow.getElement();
},
highlightVisual: function () {
return (this.marker || {}).visual;
},
highlightVisualArgs: function () {
var marker = this.marker;
var visual;
var rect;
if (marker) {
rect = marker.paddingBox.toRect();
visual = marker.visual;
} else {
var size = this.options.markers.size;
var halfSize = size / 2;
var center = this.box.center();
rect = new geom.Rect([
center.x - halfSize,
center.y - halfSize
], [
size,
size
]);
}
return {
options: this.options,
rect: rect,
visual: visual
};
},
tooltipAnchor: function (tooltipWidth, tooltipHeight) {
var point = this, markerBox = point.markerBox(), aboveAxis = point.aboveAxis, x = markerBox.x2 + TOOLTIP_OFFSET, y = aboveAxis ? markerBox.y1 - tooltipHeight : markerBox.y2, clipBox = point.owner.pane.clipBox(), showTooltip = !clipBox || clipBox.overlaps(markerBox);
if (showTooltip) {
return Point2D(x, y);
}
},
formatValue: function (format) {
var point = this;
return point.owner.formatPointValue(point, format);
},
overlapsBox: function (box) {
var markerBox = this.markerBox();
return markerBox.overlaps(box);
}
});
deepExtend(LinePoint.fn, PointEventsMixin);
deepExtend(LinePoint.fn, NoteMixin);
var Bubble = LinePoint.extend({
init: function (value, options) {
var point = this;
LinePoint.fn.init.call(point, value, options);
point.category = value.category;
},
defaults: {
labels: { position: CENTER },
highlight: {
opacity: 1,
border: {
width: 1,
opacity: 1
}
}
},
createHighlight: function () {
var highlight = this.options.highlight;
var border = highlight.border;
var markers = this.options.markers;
var center = this.box.center();
var radius = markers.size / 2 - border.width / 2;
var overlay = new draw.Circle(new geom.Circle([
center.x,
center.y
], radius), {
stroke: {
color: border.color || new Color(markers.background).brightness(BAR_BORDER_BRIGHTNESS).toHex(),
width: border.width,
opacity: border.opacity
},
fill: {
color: markers.background,
opacity: highlight.opacity
}
});
return overlay;
}
});
var LineSegment = ChartElement.extend({
init: function (linePoints, series, seriesIx) {
var segment = this;
ChartElement.fn.init.call(segment);
segment.linePoints = linePoints;
segment.series = series;
segment.seriesIx = seriesIx;
},
options: { closed: false },
points: function (visualPoints) {
var segment = this, linePoints = segment.linePoints.concat(visualPoints || []), points = [];
for (var i = 0, length = linePoints.length; i < length; i++) {
if (linePoints[i].visible !== false) {
points.push(linePoints[i]._childBox.toRect().center());
}
}
return points;
},
createVisual: function () {
var options = this.options;
var series = this.series;
var defaults = series._defaults;
var color = series.color;
if (isFn(color) && defaults) {
color = defaults.color;
}
var line = draw.Path.fromPoints(this.points(), {
stroke: {
color: color,
width: series.width,
opacity: series.opacity,
dashType: series.dashType
},
zIndex: series.zIndex
});
if (options.closed) {
line.close();
}
this.visual = line;
},
aliasFor: function (e, coords) {
var segment = this, seriesIx = segment.seriesIx;
return segment.parent.getNearestPoint(coords.x, coords.y, seriesIx);
}
});
var LineChartMixin = {
renderSegments: function () {
var chart = this, options = chart.options, series = options.series, seriesPoints = chart.seriesPoints, currentSeries, seriesIx, seriesCount = seriesPoints.length, sortedPoints, linePoints, point, pointIx, pointCount, lastSegment;
this._segments = [];
for (seriesIx = 0; seriesIx < seriesCount; seriesIx++) {
currentSeries = series[seriesIx];
sortedPoints = chart.sortPoints(seriesPoints[seriesIx]);
pointCount = sortedPoints.length;
linePoints = [];
for (pointIx = 0; pointIx < pointCount; pointIx++) {
point = sortedPoints[pointIx];
if (point) {
linePoints.push(point);
} else if (chart.seriesMissingValues(currentSeries) !== INTERPOLATE) {
if (linePoints.length > 1) {
lastSegment = chart.createSegment(linePoints, currentSeries, seriesIx, lastSegment);
this._addSegment(lastSegment);
}
linePoints = [];
}
}
if (linePoints.length > 1) {
lastSegment = chart.createSegment(linePoints, currentSeries, seriesIx, lastSegment);
this._addSegment(lastSegment);
}
}
this.children.unshift.apply(this.children, this._segments);
},
_addSegment: function (segment) {
this._segments.push(segment);
segment.parent = this;
},
sortPoints: function (points) {
return points;
},
seriesMissingValues: function (series) {
var missingValues = series.missingValues, assumeZero = !missingValues && this.options.isStacked;
return assumeZero ? ZERO : missingValues || INTERPOLATE;
},
getNearestPoint: function (x, y, seriesIx) {
var target = new Point2D(x, y);
var allPoints = this.seriesPoints[seriesIx];
var nearestPointDistance = MAX_VALUE;
var nearestPoint;
for (var i = 0; i < allPoints.length; i++) {
var point = allPoints[i];
if (point && defined(point.value) && point.value !== null && point.visible !== false) {
var pointBox = point.box;
var pointDistance = pointBox.center().distanceTo(target);
if (pointDistance < nearestPointDistance) {
nearestPoint = point;
nearestPointDistance = pointDistance;
}
}
}
return nearestPoint;
}
};
var ClipAnimationMixin = {
createAnimation: function () {
var root = this.getRoot();
if (root && (root.options || {}).transitions !== false) {
var box = root.box;
var clipPath = draw.Path.fromRect(box.toRect());
this.visual.clip(clipPath);
this.animation = new ClipAnimation(clipPath, { box: box });
if (anyHasZIndex(this.options.series)) {
this._setChildrenAnimation(clipPath);
}
}
},
_setChildrenAnimation: function (clipPath) {
var points = this.animationPoints();
var point;
for (var idx = 0; idx < points.length; idx++) {
point = points[idx];
if (point && point.visual && defined(point.visual.options.zIndex)) {
point.visual.clip(clipPath);
}
}
}
};
var LineChart = CategoricalChart.extend({
render: function () {
var chart = this;
CategoricalChart.fn.render.apply(chart);
chart.updateStackRange();
chart.renderSegments();
},
pointType: function () {
return LinePoint;
},
createPoint: function (data, fields) {
var chart = this;
var categoryIx = fields.categoryIx;
var category = fields.category;
var series = fields.series;
var seriesIx = fields.seriesIx;
var value = data.valueFields.value;
var missingValues = chart.seriesMissingValues(series);
var point;
var pointOptions;
if (!defined(value) || value === null) {
if (missingValues === ZERO) {
value = 0;
} else {
return null;
}
}
pointOptions = this.pointOptions(series, seriesIx);
pointOptions = chart.evalPointOptions(pointOptions, value, category, categoryIx, series, seriesIx);
var color = data.fields.color || series.color;
if (kendo.isFunction(series.color)) {
color = pointOptions.color;
}
point = new LinePoint(value, pointOptions);
point.color = color;
chart.append(point);
return point;
},
plotRange: function (point) {
var plotValue = this.plotValue(point);
if (this.options.isStacked) {
var categoryIx = point.categoryIx;
var categoryPts = this.categoryPoints[categoryIx];
for (var i = 0; i < categoryPts.length; i++) {
var other = categoryPts[i];
if (point === other) {
break;
}
plotValue += this.plotValue(other);
}
}
return [
plotValue,
plotValue
];
},
createSegment: function (linePoints, currentSeries, seriesIx) {
var pointType, style = currentSeries.style;
if (style === STEP) {
pointType = StepLineSegment;
} else if (style === SMOOTH) {
pointType = SplineSegment;
} else {
pointType = LineSegment;
}
return new pointType(linePoints, currentSeries, seriesIx);
},
animationPoints: function () {
var points = this.points;
var result = [];
for (var idx = 0; idx < points.length; idx++) {
result.push((points[idx] || {}).marker);
}
return result.concat(this._segments);
}
});
deepExtend(LineChart.fn, LineChartMixin, ClipAnimationMixin);
var ClipAnimation = draw.Animation.extend({
options: { duration: INITIAL_ANIMATION_DURATION },
setup: function () {
this._setEnd(this.options.box.x1);
},
step: function (pos) {
var box = this.options.box;
this._setEnd(interpolate(box.x1, box.x2, pos));
},
_setEnd: function (x) {
var element = this.element;
var segments = element.segments;
var topRight = segments[1].anchor();
var bottomRight = segments[2].anchor();
element.suspend();
topRight.setX(x);
element.resume();
bottomRight.setX(x);
}
});
draw.AnimationFactory.current.register(CLIP, ClipAnimation);
var StepLineSegment = LineSegment.extend({
points: function (visualPoints) {
var segment = this, points;
points = segment.calculateStepPoints(segment.linePoints);
if (visualPoints && visualPoints.length) {
points = points.concat(segment.calculateStepPoints(visualPoints).reverse());
}
return points;
},
calculateStepPoints: function (points) {
var segment = this, chart = segment.parent, plotArea = chart.plotArea, categoryAxis = plotArea.seriesCategoryAxis(segment.series), isInterpolate = chart.seriesMissingValues(segment.series) === INTERPOLATE, length = points.length, reverse = categoryAxis.options.reverse, vertical = categoryAxis.options.vertical, dir = reverse ? 2 : 1, revDir = reverse ? 1 : 2, prevPoint, point, i, prevMarkerBoxCenter, markerBoxCenter, result = [];
for (i = 1; i < length; i++) {
prevPoint = points[i - 1];
point = points[i];
prevMarkerBoxCenter = prevPoint.markerBox().center();
markerBoxCenter = point.markerBox().center();
if (categoryAxis.options.justified) {
result.push(new geom.Point(prevMarkerBoxCenter.x, prevMarkerBoxCenter.y));
if (vertical) {
result.push(new geom.Point(prevMarkerBoxCenter.x, markerBoxCenter.y));
} else {
result.push(new geom.Point(markerBoxCenter.x, prevMarkerBoxCenter.y));
}
result.push(new geom.Point(markerBoxCenter.x, markerBoxCenter.y));
} else {
if (vertical) {
result.push(new geom.Point(prevMarkerBoxCenter.x, prevPoint.box[Y + dir]));
result.push(new geom.Point(prevMarkerBoxCenter.x, prevPoint.box[Y + revDir]));
if (isInterpolate) {
result.push(new geom.Point(prevMarkerBoxCenter.x, point.box[Y + dir]));
}
result.push(new geom.Point(markerBoxCenter.x, point.box[Y + dir]));
result.push(new geom.Point(markerBoxCenter.x, point.box[Y + revDir]));
} else {
result.push(new geom.Point(prevPoint.box[X + dir], prevMarkerBoxCenter.y));
result.push(new geom.Point(prevPoint.box[X + revDir], prevMarkerBoxCenter.y));
if (isInterpolate) {
result.push(new geom.Point(point.box[X + dir], prevMarkerBoxCenter.y));
}
result.push(new geom.Point(point.box[X + dir], markerBoxCenter.y));
result.push(new geom.Point(point.box[X + revDir], markerBoxCenter.y));
}
}
}
return result || [];
}
});
var SplineSegment = LineSegment.extend({
createVisual: function () {
var series = this.series;
var defaults = series._defaults;
var color = series.color;
if (isFn(color) && defaults) {
color = defaults.color;
}
var curveProcessor = new CurveProcessor(this.options.closed);
var segments = curveProcessor.process(this.points());
var curve = new draw.Path({
stroke: {
color: color,
width: series.width,
opacity: series.opacity,
dashType: series.dashType
},
zIndex: series.zIndex
});
curve.segments.push.apply(curve.segments, segments);
this.visual = curve;
}
});
var AreaSegmentMixin = {
points: function () {
var segment = this, chart = segment.parent, plotArea = chart.plotArea, invertAxes = chart.options.invertAxes, valueAxis = chart.seriesValueAxis(segment.series), valueAxisLineBox = valueAxis.lineBox(), categoryAxis = plotArea.seriesCategoryAxis(segment.series), categoryAxisLineBox = categoryAxis.lineBox(), end = invertAxes ? categoryAxisLineBox.x1 : categoryAxisLineBox.y1, stackPoints = segment.stackPoints, points = segment._linePoints(stackPoints), pos = invertAxes ? X : Y, firstPoint, lastPoint;
end = limitValue(end, valueAxisLineBox[pos + 1], valueAxisLineBox[pos + 2]);
if (!segment.stackPoints && points.length > 1) {
firstPoint = points[0];
lastPoint = last(points);
if (invertAxes) {
points.unshift(new geom.Point(end, firstPoint.y));
points.push(new geom.Point(end, lastPoint.y));
} else {
points.unshift(new geom.Point(firstPoint.x, end));
points.push(new geom.Point(lastPoint.x, end));
}
}
return points;
},
createVisual: function () {
var series = this.series;
var defaults = series._defaults;
var color = series.color;
if (isFn(color) && defaults) {
color = defaults.color;
}
this.visual = new draw.Group({ zIndex: series.zIndex });
this.createArea(color);
this.createLine(color);
},
createLine: function (color) {
var series = this.series;
var lineOptions = deepExtend({
color: color,
opacity: series.opacity
}, series.line);
if (lineOptions.visible !== false && lineOptions.width > 0) {
var line = draw.Path.fromPoints(this._linePoints(), {
stroke: {
color: lineOptions.color,
width: lineOptions.width,
opacity: lineOptions.opacity,
dashType: lineOptions.dashType,
lineCap: 'butt'
}
});
this.visual.append(line);
}
},
createArea: function (color) {
var series = this.series;
var area = draw.Path.fromPoints(this.points(), {
fill: {
color: color,
opacity: series.opacity
},
stroke: null
});
this.visual.append(area);
}
};
var AreaSegment = LineSegment.extend({
init: function (linePoints, stackPoints, currentSeries, seriesIx) {
var segment = this;
segment.stackPoints = stackPoints;
LineSegment.fn.init.call(segment, linePoints, currentSeries, seriesIx);
},
_linePoints: LineSegment.fn.points
});
deepExtend(AreaSegment.fn, AreaSegmentMixin);
var AreaChart = LineChart.extend({
createSegment: function (linePoints, currentSeries, seriesIx, prevSegment) {
var chart = this, options = chart.options, isStacked = options.isStacked, stackPoints, pointType, style = (currentSeries.line || {}).style;
if (isStacked && seriesIx > 0 && prevSegment) {
var missingValues = this.seriesMissingValues(currentSeries);
if (missingValues != 'gap') {
stackPoints = prevSegment.linePoints;
} else {
stackPoints = this._gapStackPoints(linePoints, seriesIx, style);
}
if (style !== STEP) {
stackPoints = stackPoints.slice(0).reverse();
}
}
if (style === SMOOTH) {
return new SplineAreaSegment(linePoints, prevSegment, isStacked, currentSeries, seriesIx);
}
if (style === STEP) {
pointType = StepAreaSegment;
} else {
pointType = AreaSegment;
}
return new pointType(linePoints, stackPoints, currentSeries, seriesIx);
},
reflow: function (targetBox) {
LineChart.fn.reflow.call(this, targetBox);
var stackPoints = this._stackPoints;
if (stackPoints) {
var stackPoint, pointSlot;
for (var idx = 0; idx < stackPoints.length; idx++) {
stackPoint = stackPoints[idx];
pointSlot = this.categoryAxis.getSlot(stackPoint.categoryIx);
stackPoint.reflow(pointSlot);
}
}
},
_gapStackPoints: function (linePoints, seriesIx, style) {
var seriesPoints = this.seriesPoints;
var startIdx = linePoints[0].categoryIx;
var endIdx = startIdx + linePoints.length;
var stackPoints = [];
var currentSeriesIx;
var point, gapStackPoint;
this._stackPoints = this._stackPoints || [];
for (var idx = startIdx; idx < endIdx; idx++) {
currentSeriesIx = seriesIx;
do {
currentSeriesIx--;
point = seriesPoints[currentSeriesIx][idx];
} while (currentSeriesIx > 0 && !point);
if (point) {
if (style !== STEP && idx > startIdx && !seriesPoints[currentSeriesIx][idx - 1]) {
stackPoints.push(this._previousSegmentPoint(idx, idx - 1, currentSeriesIx));
}
stackPoints.push(point);
if (style !== STEP && idx + 1 < endIdx && !seriesPoints[currentSeriesIx][idx + 1]) {
stackPoints.push(this._previousSegmentPoint(idx, idx + 1, currentSeriesIx));
}
} else {
gapStackPoint = this._createGapStackPoint(idx);
this._stackPoints.push(gapStackPoint);
stackPoints.push(gapStackPoint);
}
}
return stackPoints;
},
_previousSegmentPoint: function (categoryIx, segmentIx, seriesIdx) {
var seriesPoints = this.seriesPoints;
var point;
while (seriesIdx > 0 && !point) {
seriesIdx--;
point = seriesPoints[seriesIdx][segmentIx];
}
if (!point) {
point = this._createGapStackPoint(categoryIx);
this._stackPoints.push(point);
} else {
point = seriesPoints[seriesIdx][categoryIx];
}
return point;
},
_createGapStackPoint: function (categoryIx) {
var options = this.pointOptions({}, 0);
var point = new LinePoint(0, options);
point.categoryIx = categoryIx;
point.series = {};
return point;
},
seriesMissingValues: function (series) {
return series.missingValues || ZERO;
}
});
var SplineAreaSegment = AreaSegment.extend({
init: function (linePoints, prevSegment, isStacked, currentSeries, seriesIx) {
var segment = this;
segment.prevSegment = prevSegment;
segment.isStacked = isStacked;
LineSegment.fn.init.call(segment, linePoints, currentSeries, seriesIx);
},
strokeSegments: function () {
var segments = this._strokeSegments;
if (!segments) {
var curveProcessor = new CurveProcessor(this.options.closed);
var linePoints = LineSegment.fn.points.call(this);
segments = this._strokeSegments = curveProcessor.process(linePoints);
}
return segments;
},
createVisual: function () {
var series = this.series;
var defaults = series._defaults;
var color = series.color;
if (isFn(color) && defaults) {
color = defaults.color;
}
this.visual = new draw.Group({ zIndex: series.zIndex });
this.createFill({
fill: {
color: color,
opacity: series.opacity
},
stroke: null
});
this.createStroke({
stroke: deepExtend({
color: color,
opacity: series.opacity,
lineCap: 'butt'
}, series.line)
});
},
createFill: function (style) {
var strokeSegments = this.strokeSegments();
var fillSegments = strokeSegments.slice(0);
var prevSegment = this.prevSegment;
if (this.isStacked && prevSegment) {
var prevStrokeSegments = prevSegment.strokeSegments();
var prevAnchor = last(prevStrokeSegments).anchor();
fillSegments.push(new draw.Segment(prevAnchor, prevAnchor, last(strokeSegments).anchor()));
var stackSegments = $.map(prevStrokeSegments, function (segment) {
return new draw.Segment(segment.anchor(), segment.controlOut(), segment.controlIn());
}).reverse();
append(fillSegments, stackSegments);
var firstAnchor = fillSegments[0].anchor();
fillSegments.push(new draw.Segment(firstAnchor, firstAnchor, last(stackSegments).anchor()));
}
var fill = new draw.Path(style);
fill.segments.push.apply(fill.segments, fillSegments);
this.closeFill(fill);
this.visual.append(fill);
},
closeFill: function (fillPath) {
var segment = this, chart = segment.parent, prevSegment = segment.prevSegment, plotArea = chart.plotArea, invertAxes = chart.options.invertAxes, valueAxis = chart.seriesValueAxis(segment.series), valueAxisLineBox = valueAxis.lineBox(), categoryAxis = plotArea.seriesCategoryAxis(segment.series), categoryAxisLineBox = categoryAxis.lineBox(), end = invertAxes ? categoryAxisLineBox.x1 : categoryAxisLineBox.y1, pos = invertAxes ? X : Y, segments = segment.strokeSegments(), firstPoint = segments[0].anchor(), lastPoint = last(segments).anchor();
end = limitValue(end, valueAxisLineBox[pos + 1], valueAxisLineBox[pos + 2]);
if (!(chart.options.isStacked && prevSegment) && segments.length > 1) {
if (invertAxes) {
fillPath.lineTo(end, lastPoint.y).lineTo(end, firstPoint.y);
} else {
fillPath.lineTo(lastPoint.x, end).lineTo(firstPoint.x, end);
}
}
},
createStroke: function (style) {
if (style.stroke.width > 0) {
var stroke = new draw.Path(style);
stroke.segments.push.apply(stroke.segments, this.strokeSegments());
this.visual.append(stroke);
}
}
});
var StepAreaSegment = StepLineSegment.extend({
init: function (linePoints, stackPoints, currentSeries, seriesIx) {
var segment = this;
segment.stackPoints = stackPoints;
StepLineSegment.fn.init.call(segment, linePoints, currentSeries, seriesIx);
},
_linePoints: StepLineSegment.fn.points
});
deepExtend(StepAreaSegment.fn, AreaSegmentMixin);
var ScatterChart = ChartElement.extend({
init: function (plotArea, options) {
var chart = this;
ChartElement.fn.init.call(chart, options);
chart.plotArea = plotArea;
chart.xAxisRanges = {};
chart.yAxisRanges = {};
chart.points = [];
chart.seriesPoints = [];
chart.seriesOptions = [];
chart._evalSeries = [];
chart.render();
},
options: {
series: [],
tooltip: { format: '{0}, {1}' },
labels: { format: '{0}, {1}' },
clip: true
},
render: function () {
var chart = this;
chart.traverseDataPoints(proxy(chart.addValue, chart));
},
addErrorBar: function (point, field, fields) {
var errorRange, chart = this, value = point.value[field], valueErrorField = field + 'Value', lowField = field + 'ErrorLow', highField = field + 'ErrorHigh', seriesIx = fields.seriesIx, series = fields.series, errorBars = point.options.errorBars, lowValue = fields[lowField], highValue = fields[highField];
if (isNumber(value)) {
if (isNumber(lowValue) && isNumber(highValue)) {
errorRange = {
low: lowValue,
high: highValue
};
}
if (errorBars && defined(errorBars[valueErrorField])) {
chart.seriesErrorRanges = chart.seriesErrorRanges || {
x: [],
y: []
};
chart.seriesErrorRanges[field][seriesIx] = chart.seriesErrorRanges[field][seriesIx] || new ErrorRangeCalculator(errorBars[valueErrorField], series, field);
errorRange = chart.seriesErrorRanges[field][seriesIx].getErrorRange(value, errorBars[valueErrorField]);
}
if (errorRange) {
chart.addPointErrorBar(errorRange, point, field);
}
}
},
addPointErrorBar: function (errorRange, point, field) {
var chart = this, low = errorRange.low, high = errorRange.high, series = point.series, isVertical = field === Y, options = point.options.errorBars, item = {}, errorBar;
point[field + 'Low'] = low;
point[field + 'High'] = high;
point.errorBars = point.errorBars || [];
errorBar = new ScatterErrorBar(low, high, isVertical, chart, series, options);
point.errorBars.push(errorBar);
point.append(errorBar);
item[field] = low;
chart.updateRange(item, series);
item[field] = high;
chart.updateRange(item, series);
},
addValue: function (value, fields) {
var chart = this, point, x = value.x, y = value.y, seriesIx = fields.seriesIx, series = this.options.series[seriesIx], missingValues = this.seriesMissingValues(series), seriesPoints = chart.seriesPoints[seriesIx];
if (!(hasValue(x) && hasValue(y))) {
value = this.createMissingValue(value, missingValues);
}
if (value) {
point = chart.createPoint(value, fields);
if (point) {
extend(point, fields);
chart.addErrorBar(point, X, fields);
chart.addErrorBar(point, Y, fields);
}
chart.updateRange(value, fields.series);
}
chart.points.push(point);
seriesPoints.push(point);
},
seriesMissingValues: function (series) {
return series.missingValues;
},
createMissingValue: noop,
updateRange: function (value, series) {
var chart = this, x = value.x, y = value.y, xAxisName = series.xAxis, yAxisName = series.yAxis, xAxisRange = chart.xAxisRanges[xAxisName], yAxisRange = chart.yAxisRanges[yAxisName];
if (hasValue(x)) {
xAxisRange = chart.xAxisRanges[xAxisName] = xAxisRange || {
min: MAX_VALUE,
max: MIN_VALUE
};
if (typeof x === STRING) {
x = toDate(x);
}
xAxisRange.min = math.min(xAxisRange.min, x);
xAxisRange.max = math.max(xAxisRange.max, x);
}
if (hasValue(y)) {
yAxisRange = chart.yAxisRanges[yAxisName] = yAxisRange || {
min: MAX_VALUE,
max: MIN_VALUE
};
if (typeof y === STRING) {
y = toDate(y);
}
yAxisRange.min = math.min(yAxisRange.min, y);
yAxisRange.max = math.max(yAxisRange.max, y);
}
},
evalPointOptions: function (options, value, fields) {
var series = fields.series;
var seriesIx = fields.seriesIx;
var state = {
defaults: series._defaults,
excluded: [
'data',
'tooltip',
'tempate',
'visual',
'toggle',
'_outOfRangeMinPoint',
'_outOfRangeMaxPoint'
]
};
var doEval = this._evalSeries[seriesIx];
if (!defined(doEval)) {
this._evalSeries[seriesIx] = doEval = evalOptions(options, {}, state, true);
}
if (doEval) {
options = deepExtend({}, options);
evalOptions(options, {
value: value,
series: series,
dataItem: fields.dataItem
}, state);
}
return options;
},
pointType: function () {
return LinePoint;
},
pointOptions: function (series, seriesIx) {
var options = this.seriesOptions[seriesIx];
if (!options) {
var defaults = this.pointType().fn.defaults;
this.seriesOptions[seriesIx] = options = deepExtend({}, defaults, {
markers: { opacity: series.opacity },
tooltip: { format: this.options.tooltip.format },
labels: { format: this.options.labels.format }
}, series);
}
return options;
},
createPoint: function (value, fields) {
var chart = this, series = fields.series, point;
var pointOptions = this.pointOptions(series, fields.seriesIx);
var color = fields.color || series.color;
pointOptions = chart.evalPointOptions(pointOptions, value, fields);
if (kendo.isFunction(series.color)) {
color = pointOptions.color;
}
point = new LinePoint(value, pointOptions);
point.color = color;
chart.append(point);
return point;
},
seriesAxes: function (series) {
var plotArea = this.plotArea, xAxisName = series.xAxis, xAxis = xAxisName ? plotArea.namedXAxes[xAxisName] : plotArea.axisX, yAxisName = series.yAxis, yAxis = yAxisName ? plotArea.namedYAxes[yAxisName] : plotArea.axisY;
if (!xAxis) {
throw new Error('Unable to locate X axis with name ' + xAxisName);
}
if (!yAxis) {
throw new Error('Unable to locate Y axis with name ' + yAxisName);
}
return {
x: xAxis,
y: yAxis
};
},
reflow: function (targetBox) {
var chart = this, chartPoints = chart.points, pointIx = 0, point, seriesAxes, limit = !chart.options.clip;
chart.traverseDataPoints(function (value, fields) {
point = chartPoints[pointIx++];
seriesAxes = chart.seriesAxes(fields.series);
var slotX = seriesAxes.x.getSlot(value.x, value.x, limit), slotY = seriesAxes.y.getSlot(value.y, value.y, limit), pointSlot;
if (point) {
if (slotX && slotY) {
pointSlot = chart.pointSlot(slotX, slotY);
point.reflow(pointSlot);
} else {
point.visible = false;
}
}
});
chart.box = targetBox;
},
pointSlot: function (slotX, slotY) {
return new Box2D(slotX.x1, slotY.y1, slotX.x2, slotY.y2);
},
traverseDataPoints: function (callback) {
var chart = this, options = chart.options, series = options.series, seriesPoints = chart.seriesPoints, pointIx, seriesIx, currentSeries, currentSeriesPoints, pointData, value, fields;
for (seriesIx = 0; seriesIx < series.length; seriesIx++) {
currentSeries = series[seriesIx];
currentSeriesPoints = seriesPoints[seriesIx];
if (!currentSeriesPoints) {
seriesPoints[seriesIx] = [];
}
for (pointIx = 0; pointIx < currentSeries.data.length; pointIx++) {
pointData = this._bindPoint(currentSeries, seriesIx, pointIx);
value = pointData.valueFields;
fields = pointData.fields;
callback(value, deepExtend({
pointIx: pointIx,
series: currentSeries,
seriesIx: seriesIx,
dataItem: currentSeries.data[pointIx],
owner: chart
}, fields));
}
}
},
_bindPoint: CategoricalChart.fn._bindPoint,
formatPointValue: function (point, format) {
var value = point.value;
return autoFormat(format, value.x, value.y);
},
animationPoints: function () {
var points = this.points;
var result = [];
for (var idx = 0; idx < points.length; idx++) {
result.push((points[idx] || {}).marker);
}
return result;
}
});
deepExtend(ScatterChart.fn, ClipAnimationMixin);
var ScatterLineChart = ScatterChart.extend({
render: function () {
var chart = this;
ScatterChart.fn.render.call(chart);
chart.renderSegments();
},
createSegment: function (linePoints, currentSeries, seriesIx) {
var pointType, style = currentSeries.style;
if (style === SMOOTH) {
pointType = SplineSegment;
} else {
pointType = LineSegment;
}
return new pointType(linePoints, currentSeries, seriesIx);
},
animationPoints: function () {
var points = ScatterChart.fn.animationPoints.call(this);
return points.concat(this._segments);
},
createMissingValue: function (value, missingValues) {
if (missingValues === ZERO) {
var missingValue = {
x: value.x,
y: value.y
};
if (!hasValue(missingValue.x)) {
missingValue.x = 0;
}
if (!hasValue(missingValue.y)) {
missingValue.y = 0;
}
return missingValue;
}
}
});
deepExtend(ScatterLineChart.fn, LineChartMixin);
var BubbleChart = ScatterChart.extend({
init: function (plotArea, options) {
this._maxSize = MIN_VALUE;
ScatterChart.fn.init.call(this, plotArea, options);
},
options: {
tooltip: { format: '{3}' },
labels: { format: '{3}' }
},
addValue: function (value, fields) {
if (value.size !== null && (value.size > 0 || value.size < 0 && fields.series.negativeValues.visible)) {
this._maxSize = math.max(this._maxSize, math.abs(value.size));
ScatterChart.fn.addValue.call(this, value, fields);
} else {
this.points.push(null);
this.seriesPoints[fields.seriesIx].push(null);
}
},
reflow: function (box) {
var chart = this;
chart.updateBubblesSize(box);
ScatterChart.fn.reflow.call(chart, box);
},
pointType: function () {
return Bubble;
},
createPoint: function (value, fields) {
var chart = this, series = fields.series, pointsCount = series.data.length, delay = fields.pointIx * (INITIAL_ANIMATION_DURATION / pointsCount), animationOptions = {
delay: delay,
duration: INITIAL_ANIMATION_DURATION - delay,
type: BUBBLE
}, point, pointOptions;
var color = fields.color || series.color;
if (value.size < 0 && series.negativeValues.visible) {
color = valueOrDefault(series.negativeValues.color, color);
}
pointOptions = deepExtend({
labels: {
animation: {
delay: delay,
duration: INITIAL_ANIMATION_DURATION - delay
}
}
}, this.pointOptions(series, fields.seriesIx), {
markers: {
type: CIRCLE,
border: series.border,
opacity: series.opacity,
animation: animationOptions
}
});
pointOptions = chart.evalPointOptions(pointOptions, value, fields);
if (kendo.isFunction(series.color)) {
color = pointOptions.color;
}
pointOptions.markers.background = color;
point = new Bubble(value, pointOptions);
point.color = color;
chart.append(point);
return point;
},
updateBubblesSize: function (box) {
var chart = this, options = chart.options, series = options.series, boxSize = math.min(box.width(), box.height()), seriesIx, pointIx;
for (seriesIx = 0; seriesIx < series.length; seriesIx++) {
var currentSeries = series[seriesIx], seriesPoints = chart.seriesPoints[seriesIx], minSize = currentSeries.minSize || math.max(boxSize * 0.02, 10), maxSize = currentSeries.maxSize || boxSize * 0.2, minR = minSize / 2, maxR = maxSize / 2, minArea = math.PI * minR * minR, maxArea = math.PI * maxR * maxR, areaRange = maxArea - minArea, areaRatio = areaRange / chart._maxSize;
for (pointIx = 0; pointIx < seriesPoints.length; pointIx++) {
var point = seriesPoints[pointIx];
if (point) {
var area = math.abs(point.value.size) * areaRatio, r = math.sqrt((minArea + area) / math.PI), baseZIndex = valueOrDefault(point.options.zIndex, 0), zIndex = baseZIndex + (1 - r / maxR);
deepExtend(point.options, {
zIndex: zIndex,
markers: {
size: r * 2,
zIndex: zIndex
},
labels: { zIndex: zIndex + 1 }
});
}
}
}
},
formatPointValue: function (point, format) {
var value = point.value;
return autoFormat(format, value.x, value.y, value.size, point.category);
},
createAnimation: noop,
createVisual: noop
});
var Candlestick = ChartElement.extend({
init: function (value, options) {
ChartElement.fn.init.call(this, options);
this.value = value;
},
options: {
border: { _brightness: 0.8 },
line: { width: 2 },
overlay: { gradient: GLASS },
tooltip: { format: '<table style=\'text-align: left;\'>' + '<th colspan=\'2\'>{4:d}</th>' + '<tr><td>Open:</td><td>{0:C}</td></tr>' + '<tr><td>High:</td><td>{1:C}</td></tr>' + '<tr><td>Low:</td><td>{2:C}</td></tr>' + '<tr><td>Close:</td><td>{3:C}</td></tr>' + '</table>' },
highlight: {
opacity: 1,
border: {
width: 1,
opacity: 1
},
line: {
width: 1,
opacity: 1
}
},
notes: {
visible: true,
label: {}
}
},
reflow: function (box) {
var point = this, options = point.options, chart = point.owner, value = point.value, valueAxis = chart.seriesValueAxis(options), points = [], mid, ocSlot, lhSlot;
ocSlot = valueAxis.getSlot(value.open, value.close);
lhSlot = valueAxis.getSlot(value.low, value.high);
ocSlot.x1 = lhSlot.x1 = box.x1;
ocSlot.x2 = lhSlot.x2 = box.x2;
point.realBody = ocSlot;
mid = lhSlot.center().x;
points.push([
[
mid,
lhSlot.y1
],
[
mid,
ocSlot.y1
]
]);
points.push([
[
mid,
ocSlot.y2
],
[
mid,
lhSlot.y2
]
]);
point.lines = points;
point.box = lhSlot.clone().wrap(ocSlot);
if (!point._rendered) {
point._rendered = true;
point.createNote();
}
point.reflowNote();
},
reflowNote: function () {
var point = this;
if (point.note) {
point.note.reflow(point.box);
}
},
createVisual: function () {
ChartElement.fn.createVisual.call(this);
this._mainVisual = this.mainVisual(this.options);
this.visual.append(this._mainVisual);
this.createOverlay();
},
mainVisual: function (options) {
var group = new draw.Group();
this.createBody(group, options);
this.createLines(group, options);
return group;
},
createBody: function (container, options) {
var body = draw.Path.fromRect(this.realBody.toRect(), {
fill: {
color: this.color,
opacity: options.opacity
},
stroke: null
});
if (options.border.width > 0) {
body.options.set('stroke', {
color: this.getBorderColor(),
width: options.border.width,
dashType: options.border.dashType,
opacity: valueOrDefault(options.border.opacity, options.opacity)
});
}
alignPathToPixel(body);
container.append(body);
if (hasGradientOverlay(options)) {
container.append(this.createGradientOverlay(body, { baseColor: this.color }, deepExtend({}, options.overlay)));
}
},
createLines: function (container, options) {
this.drawLines(container, options, this.lines, options.line);
},
drawLines: function (container, options, lines, lineOptions) {
if (!lines) {
return;
}
var lineStyle = {
stroke: {
color: lineOptions.color || this.color,
opacity: valueOrDefault(lineOptions.opacity, options.opacity),
width: lineOptions.width,
dashType: lineOptions.dashType,
lineCap: 'butt'
}
};
for (var i = 0; i < lines.length; i++) {
var line = draw.Path.fromPoints(lines[i], lineStyle);
alignPathToPixel(line);
container.append(line);
}
},
getBorderColor: function () {
var point = this, options = point.options, border = options.border, borderColor = border.color;
if (!defined(borderColor)) {
borderColor = new Color(point.color).brightness(border._brightness).toHex();
}
return borderColor;
},
createOverlay: function () {
var overlay = draw.Path.fromRect(this.box.toRect(), {
fill: {
color: WHITE,
opacity: 0
},
stroke: null
});
this.visual.append(overlay);
},
createHighlight: function () {
var highlight = this.options.highlight;
var normalColor = this.color;
this.color = highlight.color || this.color;
var overlay = this.mainVisual(deepExtend({}, this.options, { line: { color: this.getBorderColor() } }, highlight));
this.color = normalColor;
return overlay;
},
highlightVisual: function () {
return this._mainVisual;
},
highlightVisualArgs: function () {
return {
options: this.options,
rect: this.box.toRect(),
visual: this._mainVisual
};
},
tooltipAnchor: function () {
var point = this, box = point.box, clipBox = point.owner.pane.clipBox() || box;
return new Point2D(box.x2 + TOOLTIP_OFFSET, math.max(box.y1, clipBox.y1) + TOOLTIP_OFFSET);
},
formatValue: function (format) {
var point = this;
return point.owner.formatPointValue(point, format);
},
overlapsBox: function (box) {
return this.box.overlaps(box);
}
});
deepExtend(Candlestick.fn, PointEventsMixin);
deepExtend(Candlestick.fn, NoteMixin);
var CandlestickChart = CategoricalChart.extend({
options: {},
reflowCategories: function (categorySlots) {
var chart = this, children = chart.children, childrenLength = children.length, i;
for (i = 0; i < childrenLength; i++) {
children[i].reflow(categorySlots[i]);
}
},
addValue: function (data, fields) {
var chart = this;
var categoryIx = fields.categoryIx;
var category = fields.category;
var series = fields.series;
var seriesIx = fields.seriesIx;
var options = chart.options;
var value = data.valueFields;
var children = chart.children;
var valueParts = chart.splitValue(value);
var hasValue = areNumbers(valueParts);
var categoryPoints = chart.categoryPoints[categoryIx];
var dataItem = series.data[categoryIx];
var point, cluster;
if (!categoryPoints) {
chart.categoryPoints[categoryIx] = categoryPoints = [];
}
if (hasValue) {
point = chart.createPoint(data, fields);
}
cluster = children[categoryIx];
if (!cluster) {
cluster = new ClusterLayout({
vertical: options.invertAxes,
gap: options.gap,
spacing: options.spacing
});
chart.append(cluster);
}
if (point) {
chart.updateRange(value, fields);
cluster.append(point);
point.categoryIx = categoryIx;
point.category = category;
point.series = series;
point.seriesIx = seriesIx;
point.owner = chart;
point.dataItem = dataItem;
point.noteText = data.fields.noteText;
}
chart.points.push(point);
categoryPoints.push(point);
},
pointType: function () {
return Candlestick;
},
createPoint: function (data, fields) {
var chart = this;
var categoryIx = fields.categoryIx;
var category = fields.category;
var series = fields.series;
var seriesIx = fields.seriesIx;
var value = data.valueFields;
var pointOptions = deepExtend({}, series);
var pointType = chart.pointType();
var color = data.fields.color || series.color;
pointOptions = chart.evalPointOptions(pointOptions, value, category, categoryIx, series, seriesIx);
if (series.type == CANDLESTICK) {
if (value.open > value.close) {
color = data.fields.downColor || series.downColor || series.color;
}
}
if (kendo.isFunction(series.color)) {
color = pointOptions.color;
}
var point = new pointType(value, pointOptions);
point.color = color;
return point;
},
splitValue: function (value) {
return [
value.low,
value.open,
value.close,
value.high
];
},
updateRange: function (value, fields) {
var chart = this, axisName = fields.series.axis, axisRange = chart.valueAxisRanges[axisName], parts = chart.splitValue(value);
axisRange = chart.valueAxisRanges[axisName] = axisRange || {
min: MAX_VALUE,
max: MIN_VALUE
};
axisRange = chart.valueAxisRanges[axisName] = {
min: math.min.apply(math, parts.concat([axisRange.min])),
max: math.max.apply(math, parts.concat([axisRange.max]))
};
},
formatPointValue: function (point, format) {
var value = point.value;
return autoFormat(format, value.open, value.high, value.low, value.close, point.category);
},
animationPoints: function () {
return this.points;
}
});
deepExtend(CandlestickChart.fn, ClipAnimationMixin);
var OHLCPoint = Candlestick.extend({
reflow: function (box) {
var point = this, options = point.options, chart = point.owner, value = point.value, valueAxis = chart.seriesValueAxis(options), oPoints = [], cPoints = [], lhPoints = [], mid, oSlot, cSlot, lhSlot;
lhSlot = valueAxis.getSlot(value.low, value.high);
oSlot = valueAxis.getSlot(value.open, value.open);
cSlot = valueAxis.getSlot(value.close, value.close);
oSlot.x1 = cSlot.x1 = lhSlot.x1 = box.x1;
oSlot.x2 = cSlot.x2 = lhSlot.x2 = box.x2;
mid = lhSlot.center().x;
oPoints.push([
oSlot.x1,
oSlot.y1
]);
oPoints.push([
mid,
oSlot.y1
]);
cPoints.push([
mid,
cSlot.y1
]);
cPoints.push([
cSlot.x2,
cSlot.y1
]);
lhPoints.push([
mid,
lhSlot.y1
]);
lhPoints.push([
mid,
lhSlot.y2
]);
point.lines = [
oPoints,
cPoints,
lhPoints
];
point.box = lhSlot.clone().wrap(oSlot.clone().wrap(cSlot));
point.reflowNote();
},
createBody: $.noop
});
var OHLCChart = CandlestickChart.extend({
pointType: function () {
return OHLCPoint;
}
});
var BoxPlotChart = CandlestickChart.extend({
addValue: function (data, fields) {
var chart = this;
var categoryIx = fields.categoryIx;
var category = fields.category;
var series = fields.series;
var seriesIx = fields.seriesIx;
var options = chart.options;
var children = chart.children;
var value = data.valueFields;
var valueParts = chart.splitValue(value);
var hasValue = areNumbers(valueParts);
var categoryPoints = chart.categoryPoints[categoryIx];
var dataItem = series.data[categoryIx];
var point, cluster;
if (!categoryPoints) {
chart.categoryPoints[categoryIx] = categoryPoints = [];
}
if (hasValue) {
point = chart.createPoint(data, fields);
}
cluster = children[categoryIx];
if (!cluster) {
cluster = new ClusterLayout({
vertical: options.invertAxes,
gap: options.gap,
spacing: options.spacing
});
chart.append(cluster);
}
if (point) {
chart.updateRange(value, fields);
cluster.append(point);
point.categoryIx = categoryIx;
point.category = category;
point.series = series;
point.seriesIx = seriesIx;
point.owner = chart;
point.dataItem = dataItem;
}
chart.points.push(point);
categoryPoints.push(point);
},
pointType: function () {
return BoxPlot;
},
splitValue: function (value) {
return [
value.lower,
value.q1,
value.median,
value.q3,
value.upper
];
},
updateRange: function (value, fields) {
var chart = this, axisName = fields.series.axis, axisRange = chart.valueAxisRanges[axisName], parts = chart.splitValue(value).concat(chart.filterOutliers(value.outliers));
if (defined(value.mean)) {
parts = parts.concat(value.mean);
}
axisRange = chart.valueAxisRanges[axisName] = axisRange || {
min: MAX_VALUE,
max: MIN_VALUE
};
axisRange = chart.valueAxisRanges[axisName] = {
min: math.min.apply(math, parts.concat([axisRange.min])),
max: math.max.apply(math, parts.concat([axisRange.max]))
};
},
formatPointValue: function (point, format) {
var value = point.value;
return autoFormat(format, value.lower, value.q1, value.median, value.q3, value.upper, value.mean, point.category);
},
filterOutliers: function (items) {
var length = (items || []).length, result = [], i, item;
for (i = 0; i < length; i++) {
item = items[i];
if (defined(item)) {
appendIfNotNull(result, item);
}
}
return result;
}
});
var BoxPlot = Candlestick.extend({
init: function (value, options) {
var point = this;
ChartElement.fn.init.call(point, options);
point.value = value;
point.createNote();
},
options: {
border: { _brightness: 0.8 },
line: { width: 2 },
mean: {
width: 2,
dashType: 'dash'
},
overlay: { gradient: GLASS },
tooltip: { format: '<table style=\'text-align: left;\'>' + '<th colspan=\'2\'>{6:d}</th>' + '<tr><td>Lower:</td><td>{0:C}</td></tr>' + '<tr><td>Q1:</td><td>{1:C}</td></tr>' + '<tr><td>Median:</td><td>{2:C}</td></tr>' + '<tr><td>Mean:</td><td>{5:C}</td></tr>' + '<tr><td>Q3:</td><td>{3:C}</td></tr>' + '<tr><td>Upper:</td><td>{4:C}</td></tr>' + '</table>' },
highlight: {
opacity: 1,
border: {
width: 1,
opacity: 1
},
line: {
width: 1,
opacity: 1
}
},
notes: {
visible: true,
label: {}
},
outliers: {
visible: true,
size: LINE_MARKER_SIZE,
type: CROSS,
background: WHITE,
border: {
width: 2,
opacity: 1
},
opacity: 0
},
extremes: {
visible: true,
size: LINE_MARKER_SIZE,
type: CIRCLE,
background: WHITE,
border: {
width: 2,
opacity: 1
},
opacity: 0
}
},
reflow: function (box) {
var point = this, options = point.options, chart = point.owner, value = point.value, valueAxis = chart.seriesValueAxis(options), mid, whiskerSlot, boxSlot, medianSlot, meanSlot;
boxSlot = valueAxis.getSlot(value.q1, value.q3);
point.boxSlot = boxSlot;
whiskerSlot = valueAxis.getSlot(value.lower, value.upper);
medianSlot = valueAxis.getSlot(value.median);
boxSlot.x1 = whiskerSlot.x1 = box.x1;
boxSlot.x2 = whiskerSlot.x2 = box.x2;
point.realBody = boxSlot;
if (value.mean) {
meanSlot = valueAxis.getSlot(value.mean);
point.meanPoints = [[
[
box.x1,
meanSlot.y1
],
[
box.x2,
meanSlot.y1
]
]];
}
mid = whiskerSlot.center().x;
point.whiskerPoints = [
[
[
mid - 5,
whiskerSlot.y1
],
[
mid + 5,
whiskerSlot.y1
],
[
mid,
whiskerSlot.y1
],
[
mid,
boxSlot.y1
]
],
[
[
mid - 5,
whiskerSlot.y2
],
[
mid + 5,
whiskerSlot.y2
],
[
mid,
whiskerSlot.y2
],
[
mid,
boxSlot.y2
]
]
];
point.medianPoints = [[
[
box.x1,
medianSlot.y1
],
[
box.x2,
medianSlot.y1
]
]];
point.box = whiskerSlot.clone().wrap(boxSlot);
point.reflowNote();
},
renderOutliers: function (options) {
var point = this, markers = options.markers || {}, value = point.value, outliers = value.outliers || [], outerFence = math.abs(value.q3 - value.q1) * 3, markersBorder, shape, outlierValue, i;
var elements = [];
for (i = 0; i < outliers.length; i++) {
outlierValue = outliers[i];
if (outlierValue < value.q3 + outerFence && outlierValue > value.q1 - outerFence) {
markers = options.outliers;
} else {
markers = options.extremes;
}
markersBorder = deepExtend({}, markers.border);
if (!defined(markersBorder.color)) {
if (defined(point.color)) {
markersBorder.color = point.color;
} else {
markersBorder.color = new Color(markers.background).brightness(BAR_BORDER_BRIGHTNESS).toHex();
}
}
shape = new ShapeElement({
type: markers.type,
width: markers.size,
height: markers.size,
rotation: markers.rotation,
background: markers.background,
border: markersBorder,
opacity: markers.opacity
});
shape.value = outlierValue;
elements.push(shape);
}
this.reflowOutliers(elements);
return elements;
},
reflowOutliers: function (outliers) {
var valueAxis = this.owner.seriesValueAxis(this.options);
var centerX = this.box.center().x;
for (var i = 0; i < outliers.length; i++) {
var outlierValue = outliers[i].value;
var markerBox = valueAxis.getSlot(outlierValue).move(centerX);
this.box = this.box.wrap(markerBox);
outliers[i].reflow(markerBox);
}
},
mainVisual: function (options) {
var group = Candlestick.fn.mainVisual.call(this, options);
var outliers = this.renderOutliers(options);
for (var i = 0; i < outliers.length; i++) {
var element = outliers[i].getElement();
if (element) {
group.append(element);
}
}
return group;
},
createLines: function (container, options) {
this.drawLines(container, options, this.whiskerPoints, options.line);
this.drawLines(container, options, this.medianPoints, options.median);
this.drawLines(container, options, this.meanPoints, options.mean);
},
getBorderColor: function () {
if (this.color) {
return this.color;
}
return Candlestick.getBorderColor.call(this);
}
});
deepExtend(BoxPlot.fn, PointEventsMixin);
var PieSegment = ChartElement.extend({
init: function (value, sector, options) {
var segment = this;
segment.value = value;
segment.sector = sector;
ChartElement.fn.init.call(segment, options);
},
options: {
color: WHITE,
overlay: { gradient: ROUNDED_BEVEL },
border: { width: 0.5 },
labels: {
visible: false,
distance: 35,
font: DEFAULT_FONT,
margin: getSpacing(0.5),
align: CIRCLE,
zIndex: 1,
position: OUTSIDE_END
},
animation: { type: PIE },
highlight: {
visible: true,
border: { width: 1 }
},
visible: true
},
render: function () {
var segment = this, options = segment.options, labels = options.labels, labelText = segment.value, labelTemplate;
if (segment._rendered || segment.visible === false) {
return;
} else {
segment._rendered = true;
}
if (labels.template) {
labelTemplate = template(labels.template);
labelText = labelTemplate({
dataItem: segment.dataItem,
category: segment.category,
value: segment.value,
series: segment.series,
percentage: segment.percentage
});
} else if (labels.format) {
labelText = autoFormat(labels.format, labelText);
}
if (labels.visible && labelText) {
segment.label = new TextBox(labelText, deepExtend({}, labels, {
align: CENTER,
vAlign: '',
animation: {
type: FADEIN,
delay: segment.animationDelay
}
}));
segment.append(segment.label);
}
},
reflow: function (targetBox) {
var segment = this;
segment.render();
segment.box = targetBox;
segment.reflowLabel();
},
reflowLabel: function () {
var segment = this, sector = segment.sector.clone(), options = segment.options, label = segment.label, labelsOptions = options.labels, labelsDistance = labelsOptions.distance, angle = sector.middle(), lp, x1, labelWidth, labelHeight;
if (label) {
labelHeight = label.box.height();
labelWidth = label.box.width();
if (labelsOptions.position == CENTER) {
sector.r = math.abs((sector.r - labelHeight) / 2) + labelHeight;
lp = sector.point(angle);
label.reflow(Box2D(lp.x, lp.y - labelHeight / 2, lp.x, lp.y));
} else if (labelsOptions.position == INSIDE_END) {
sector.r = sector.r - labelHeight / 2;
lp = sector.point(angle);
label.reflow(Box2D(lp.x, lp.y - labelHeight / 2, lp.x, lp.y));
} else {
lp = sector.clone().expand(labelsDistance).point(angle);
if (lp.x >= sector.c.x) {
x1 = lp.x + labelWidth;
label.orientation = RIGHT;
} else {
x1 = lp.x - labelWidth;
label.orientation = LEFT;
}
label.reflow(Box2D(x1, lp.y - labelHeight, lp.x, lp.y));
}
}
},
createVisual: function () {
var segment = this, sector = segment.sector, options = segment.options;
ChartElement.fn.createVisual.call(this);
if (segment.value) {
if (options.visual) {
var startAngle = (sector.startAngle + 180) % 360;
var visual = options.visual({
category: segment.category,
dataItem: segment.dataItem,
value: segment.value,
series: segment.series,
percentage: segment.percentage,
center: new geom.Point(sector.c.x, sector.c.y),
radius: sector.r,
innerRadius: sector.ir,
startAngle: startAngle,
endAngle: startAngle + sector.angle,
options: options,
createVisual: function () {
var group = new draw.Group();
segment.createSegmentVisual(group);
return group;
}
});
if (visual) {
segment.visual.append(visual);
}
} else {
segment.createSegmentVisual(segment.visual);
}
}
},
createSegmentVisual: function (group) {
var segment = this, sector = segment.sector, options = segment.options, borderOptions = options.border || {}, border = borderOptions.width > 0 ? {
stroke: {
color: borderOptions.color,
width: borderOptions.width,
opacity: borderOptions.opacity,
dashType: borderOptions.dashType
}
} : {}, color = options.color, fill = {
color: color,
opacity: options.opacity
}, visual;
visual = segment.createSegment(sector, deepExtend({
fill: fill,
stroke: { opacity: options.opacity },
zIndex: options.zIndex
}, border));
group.append(visual);
if (hasGradientOverlay(options)) {
group.append(this.createGradientOverlay(visual, {
baseColor: color,
fallbackFill: fill
}, deepExtend({
center: [
sector.c.x,
sector.c.y
],
innerRadius: sector.ir,
radius: sector.r,
userSpace: true
}, options.overlay)));
}
},
createSegment: function (sector, options) {
if (options.singleSegment) {
return new draw.Circle(new geom.Circle(new geom.Point(sector.c.x, sector.c.y), sector.r), options);
} else {
return ShapeBuilder.current.createRing(sector, options);
}
},
createAnimation: function () {
var options = this.options;
var center = this.sector.c;
deepExtend(options, {
animation: {
center: [
center.x,
center.y
],
delay: this.animationDelay
}
});
ChartElement.fn.createAnimation.call(this);
},
createHighlight: function (options) {
var segment = this, highlight = segment.options.highlight || {}, border = highlight.border || {};
return segment.createSegment(segment.sector, deepExtend({}, options, {
fill: {
color: highlight.color,
opacity: highlight.opacity
},
stroke: {
opacity: border.opacity,
width: border.width,
color: border.color
}
}));
},
highlightVisual: function () {
return this.visual.children[0];
},
highlightVisualArgs: function () {
var sector = this.sector;
return {
options: this.options,
radius: sector.r,
innerRadius: sector.ir,
center: new geom.Point(sector.c.x, sector.c.y),
startAngle: sector.startAngle,
endAngle: sector.angle + sector.startAngle,
visual: this.visual
};
},
tooltipAnchor: function (width, height) {
var point = this, box = point.sector.adjacentBox(TOOLTIP_OFFSET, width, height);
return new Point2D(box.x1, box.y1);
},
formatValue: function (format) {
var point = this;
return point.owner.formatPointValue(point, format);
}
});
deepExtend(PieSegment.fn, PointEventsMixin);
var PieChartMixin = {
createLegendItem: function (value, point, options) {
var chart = this, legendOptions = chart.options.legend || {}, labelsOptions = legendOptions.labels || {}, inactiveItems = legendOptions.inactiveItems || {}, inactiveItemsLabels = inactiveItems.labels || {}, text, labelTemplate, markerColor, itemLabelOptions, pointVisible;
if (options && options.visibleInLegend !== false) {
pointVisible = options.visible !== false;
text = options.category || '';
labelTemplate = pointVisible ? labelsOptions.template : inactiveItemsLabels.template || labelsOptions.template;
if (labelTemplate) {
text = template(labelTemplate)({
text: text,
series: options.series,
dataItem: options.dataItem,
percentage: options.percentage,
value: value
});
}
if (pointVisible) {
itemLabelOptions = {};
markerColor = point.color;
} else {
itemLabelOptions = {
color: inactiveItemsLabels.color,
font: inactiveItemsLabels.font
};
markerColor = (inactiveItems.markers || {}).color;
}
if (text) {
chart.legendItems.push({
pointIndex: options.index,
text: text,
series: options.series,
markerColor: markerColor,
labels: itemLabelOptions
});
}
}
}
};
var PieChart = ChartElement.extend({
init: function (plotArea, options) {
var chart = this;
ChartElement.fn.init.call(chart, options);
chart.plotArea = plotArea;
chart.points = [];
chart.legendItems = [];
chart.render();
},
options: {
startAngle: 90,
connectors: {
width: 1,
color: '#939393',
padding: 4
},
inactiveItems: {
markers: {},
labels: {}
}
},
render: function () {
var chart = this;
chart.traverseDataPoints(proxy(chart.addValue, chart));
},
traverseDataPoints: function (callback) {
var chart = this, options = chart.options, colors = chart.plotArea.options.seriesColors || [], colorsCount = colors.length, series = options.series, seriesCount = series.length, currentSeries, pointData, fields, seriesIx, angle, data, anglePerValue, value, plotValue, explode, total, currentAngle, i, pointIx = 0;
for (seriesIx = 0; seriesIx < seriesCount; seriesIx++) {
currentSeries = series[seriesIx];
data = currentSeries.data;
total = seriesTotal(currentSeries);
anglePerValue = 360 / total;
if (defined(currentSeries.startAngle)) {
currentAngle = currentSeries.startAngle;
} else {
currentAngle = options.startAngle;
}
if (seriesIx != seriesCount - 1) {
if (currentSeries.labels.position == OUTSIDE_END) {
currentSeries.labels.position = CENTER;
}
}
for (i = 0; i < data.length; i++) {
pointData = SeriesBinder.current.bindPoint(currentSeries, i);
value = pointData.valueFields.value;
plotValue = math.abs(value);
fields = pointData.fields;
angle = plotValue * anglePerValue;
explode = data.length != 1 && !!fields.explode;
if (!isFn(currentSeries.color)) {
currentSeries.color = fields.color || colors[i % colorsCount];
}
callback(value, new Ring(null, 0, 0, currentAngle, angle), {
owner: chart,
category: fields.category || '',
index: pointIx,
series: currentSeries,
seriesIx: seriesIx,
dataItem: data[i],
percentage: total !== 0 ? plotValue / total : 0,
explode: explode,
visibleInLegend: fields.visibleInLegend,
visible: fields.visible,
zIndex: seriesCount - seriesIx,
animationDelay: chart.animationDelay(i, seriesIx, seriesCount)
});
if (pointData.fields.visible !== false) {
currentAngle += angle;
}
pointIx++;
}
pointIx = 0;
}
},
evalSegmentOptions: function (options, value, fields) {
var series = fields.series;
evalOptions(options, {
value: value,
series: series,
dataItem: fields.dataItem,
category: fields.category,
percentage: fields.percentage
}, {
defaults: series._defaults,
excluded: [
'data',
'template',
'visual',
'toggle'
]
});
},
addValue: function (value, sector, fields) {
var chart = this, segment;
var segmentOptions = deepExtend({}, fields.series, { index: fields.index });
chart.evalSegmentOptions(segmentOptions, value, fields);
chart.createLegendItem(value, segmentOptions, fields);
if (fields.visible === false) {
return;
}
segment = new PieSegment(value, sector, segmentOptions);
extend(segment, fields);
chart.append(segment);
chart.points.push(segment);
},
reflow: function (targetBox) {
var chart = this, options = chart.options, box = targetBox.clone(), space = 5, minWidth = math.min(box.width(), box.height()), halfMinWidth = minWidth / 2, defaultPadding = minWidth - minWidth * 0.85, padding = valueOrDefault(options.padding, defaultPadding), newBox = Box2D(box.x1, box.y1, box.x1 + minWidth, box.y1 + minWidth), newBoxCenter = newBox.center(), seriesConfigs = chart.seriesConfigs || [], boxCenter = box.center(), points = chart.points, count = points.length, seriesCount = options.series.length, leftSideLabels = [], rightSideLabels = [], seriesConfig, seriesIndex, label, segment, sector, r, i, c;
padding = padding > halfMinWidth - space ? halfMinWidth - space : padding;
newBox.translate(boxCenter.x - newBoxCenter.x, boxCenter.y - newBoxCenter.y);
r = halfMinWidth - padding;
c = Point2D(r + newBox.x1 + padding, r + newBox.y1 + padding);
for (i = 0; i < count; i++) {
segment = points[i];
sector = segment.sector;
sector.r = r;
sector.c = c;
seriesIndex = segment.seriesIx;
if (seriesConfigs.length) {
seriesConfig = seriesConfigs[seriesIndex];
sector.ir = seriesConfig.ir;
sector.r = seriesConfig.r;
}
if (seriesIndex == seriesCount - 1 && segment.explode) {
sector.c = sector.clone().radius(sector.r * 0.15).point(sector.middle());
}
segment.reflow(newBox);
label = segment.label;
if (label) {
if (label.options.position === OUTSIDE_END) {
if (seriesIndex == seriesCount - 1) {
if (label.orientation === RIGHT) {
rightSideLabels.push(label);
} else {
leftSideLabels.push(label);
}
}
}
}
}
if (leftSideLabels.length > 0) {
leftSideLabels.sort(chart.labelComparator(true));
chart.leftLabelsReflow(leftSideLabels);
}
if (rightSideLabels.length > 0) {
rightSideLabels.sort(chart.labelComparator(false));
chart.rightLabelsReflow(rightSideLabels);
}
chart.box = newBox;
},
leftLabelsReflow: function (labels) {
var chart = this, distances = chart.distanceBetweenLabels(labels);
chart.distributeLabels(distances, labels);
},
rightLabelsReflow: function (labels) {
var chart = this, distances = chart.distanceBetweenLabels(labels);
chart.distributeLabels(distances, labels);
},
distanceBetweenLabels: function (labels) {
var chart = this, points = chart.points, segment = points[points.length - 1], sector = segment.sector, firstBox = labels[0].box, count = labels.length - 1, lr = sector.r + segment.options.labels.distance, distances = [], secondBox, distance, i;
distance = round(firstBox.y1 - (sector.c.y - lr - firstBox.height() - firstBox.height() / 2));
distances.push(distance);
for (i = 0; i < count; i++) {
firstBox = labels[i].box;
secondBox = labels[i + 1].box;
distance = round(secondBox.y1 - firstBox.y2);
distances.push(distance);
}
distance = round(sector.c.y + lr - labels[count].box.y2 - labels[count].box.height() / 2);
distances.push(distance);
return distances;
},
distributeLabels: function (distances, labels) {
var chart = this, count = distances.length, remaining, left, right, i;
for (i = 0; i < count; i++) {
left = right = i;
remaining = -distances[i];
while (remaining > 0 && (left >= 0 || right < count)) {
remaining = chart._takeDistance(distances, i, --left, remaining);
remaining = chart._takeDistance(distances, i, ++right, remaining);
}
}
chart.reflowLabels(distances, labels);
},
_takeDistance: function (distances, anchor, position, amount) {
if (distances[position] > 0) {
var available = math.min(distances[position], amount);
amount -= available;
distances[position] -= available;
distances[anchor] += available;
}
return amount;
},
reflowLabels: function (distances, labels) {
var chart = this, points = chart.points, segment = points[points.length - 1], sector = segment.sector, labelsCount = labels.length, labelOptions = segment.options.labels, labelDistance = labelOptions.distance, boxY = sector.c.y - (sector.r + labelDistance) - labels[0].box.height(), label, boxX, box, i;
distances[0] += 2;
for (i = 0; i < labelsCount; i++) {
label = labels[i];
boxY += distances[i];
box = label.box;
boxX = chart.hAlignLabel(box.x2, sector.clone().expand(labelDistance), boxY, boxY + box.height(), label.orientation == RIGHT);
if (label.orientation == RIGHT) {
if (labelOptions.align !== CIRCLE) {
boxX = sector.r + sector.c.x + labelDistance;
}
label.reflow(Box2D(boxX + box.width(), boxY, boxX, boxY));
} else {
if (labelOptions.align !== CIRCLE) {
boxX = sector.c.x - sector.r - labelDistance;
}
label.reflow(Box2D(boxX - box.width(), boxY, boxX, boxY));
}
boxY += box.height();
}
},
createVisual: function () {
var chart = this, options = chart.options, connectors = options.connectors, points = chart.points, connectorLine, count = points.length, space = 4, sector, angle, segment, seriesIx, label, i;
ChartElement.fn.createVisual.call(this);
this._connectorLines = [];
for (i = 0; i < count; i++) {
segment = points[i];
sector = segment.sector;
angle = sector.middle();
label = segment.label;
seriesIx = { seriesId: segment.seriesIx };
if (label) {
connectorLine = new draw.Path({
stroke: {
color: connectors.color,
width: connectors.width
},
animation: {
type: FADEIN,
delay: segment.animationDelay
}
});
if (label.options.position === OUTSIDE_END && segment.value !== 0) {
var box = label.box, centerPoint = sector.c, start = sector.point(angle), middle = Point2D(box.x1, box.center().y), sr, end, crossing;
start = sector.clone().expand(connectors.padding).point(angle);
connectorLine.moveTo(start.x, start.y);
if (label.orientation == RIGHT) {
end = Point2D(box.x1 - connectors.padding, box.center().y);
crossing = intersection(centerPoint, start, middle, end);
middle = Point2D(end.x - space, end.y);
crossing = crossing || middle;
crossing.x = math.min(crossing.x, middle.x);
if (chart.pointInCircle(crossing, sector.c, sector.r + space) || crossing.x < sector.c.x) {
sr = sector.c.x + sector.r + space;
if (segment.options.labels.align !== COLUMN) {
if (sr < middle.x) {
connectorLine.lineTo(sr, start.y);
} else {
connectorLine.lineTo(start.x + space * 2, start.y);
}
} else {
connectorLine.lineTo(sr, start.y);
}
connectorLine.lineTo(middle.x, end.y);
} else {
crossing.y = end.y;
connectorLine.lineTo(crossing.x, crossing.y);
}
} else {
end = Point2D(box.x2 + connectors.padding, box.center().y);
crossing = intersection(centerPoint, start, middle, end);
middle = Point2D(end.x + space, end.y);
crossing = crossing || middle;
crossing.x = math.max(crossing.x, middle.x);
if (chart.pointInCircle(crossing, sector.c, sector.r + space) || crossing.x > sector.c.x) {
sr = sector.c.x - sector.r - space;
if (segment.options.labels.align !== COLUMN) {
if (sr > middle.x) {
connectorLine.lineTo(sr, start.y);
} else {
connectorLine.lineTo(start.x - space * 2, start.y);
}
} else {
connectorLine.lineTo(sr, start.y);
}
connectorLine.lineTo(middle.x, end.y);
} else {
crossing.y = end.y;
connectorLine.lineTo(crossing.x, crossing.y);
}
}
connectorLine.lineTo(end.x, end.y);
this._connectorLines.push(connectorLine);
this.visual.append(connectorLine);
}
}
}
},
labelComparator: function (reverse) {
reverse = reverse ? -1 : 1;
return function (a, b) {
a = (a.parent.sector.middle() + 270) % 360;
b = (b.parent.sector.middle() + 270) % 360;
return (a - b) * reverse;
};
},
hAlignLabel: function (originalX, sector, y1, y2, direction) {
var cx = sector.c.x, cy = sector.c.y, r = sector.r, t = math.min(math.abs(cy - y1), math.abs(cy - y2));
if (t > r) {
return originalX;
} else {
return cx + math.sqrt(r * r - t * t) * (direction ? 1 : -1);
}
},
pointInCircle: function (point, c, r) {
return sqr(c.x - point.x) + sqr(c.y - point.y) < sqr(r);
},
formatPointValue: function (point, format) {
return autoFormat(format, point.value);
},
animationDelay: function (categoryIndex) {
return categoryIndex * PIE_SECTOR_ANIM_DELAY;
}
});
deepExtend(PieChart.fn, PieChartMixin);
var DonutSegment = PieSegment.extend({
options: {
overlay: { gradient: ROUNDED_GLASS },
labels: { position: CENTER },
animation: { type: PIE }
},
reflowLabel: function () {
var segment = this, sector = segment.sector.clone(), options = segment.options, label = segment.label, labelsOptions = options.labels, lp, angle = sector.middle(), labelHeight;
if (label) {
labelHeight = label.box.height();
if (labelsOptions.position == CENTER) {
sector.r -= (sector.r - sector.ir) / 2;
lp = sector.point(angle);
label.reflow(new Box2D(lp.x, lp.y - labelHeight / 2, lp.x, lp.y));
} else {
PieSegment.fn.reflowLabel.call(segment);
}
}
},
createSegment: function (sector, options) {
return ShapeBuilder.current.createRing(sector, options);
}
});
deepExtend(DonutSegment.fn, PointEventsMixin);
var DonutChart = PieChart.extend({
options: {
startAngle: 90,
connectors: {
width: 1,
color: '#939393',
padding: 4
}
},
addValue: function (value, sector, fields) {
var chart = this, segment;
var segmentOptions = deepExtend({}, fields.series, { index: fields.index });
chart.evalSegmentOptions(segmentOptions, value, fields);
chart.createLegendItem(value, segmentOptions, fields);
if (!value || fields.visible === false) {
return;
}
segment = new DonutSegment(value, sector, segmentOptions);
extend(segment, fields);
chart.append(segment);
chart.points.push(segment);
},
reflow: function (targetBox) {
var chart = this, options = chart.options, box = targetBox.clone(), space = 5, minWidth = math.min(box.width(), box.height()), halfMinWidth = minWidth / 2, defaultPadding = minWidth - minWidth * 0.85, padding = valueOrDefault(options.padding, defaultPadding), series = options.series, currentSeries, seriesCount = series.length, seriesWithoutSize = 0, holeSize, totalSize, size, margin = 0, i, r, ir = 0, currentSize = 0;
chart.seriesConfigs = [];
padding = padding > halfMinWidth - space ? halfMinWidth - space : padding;
totalSize = halfMinWidth - padding;
for (i = 0; i < seriesCount; i++) {
currentSeries = series[i];
if (i === 0) {
if (defined(currentSeries.holeSize)) {
holeSize = currentSeries.holeSize;
totalSize -= currentSeries.holeSize;
}
}
if (defined(currentSeries.size)) {
totalSize -= currentSeries.size;
} else {
seriesWithoutSize++;
}
if (defined(currentSeries.margin) && i != seriesCount - 1) {
totalSize -= currentSeries.margin;
}
}
if (!defined(holeSize)) {
currentSize = (halfMinWidth - padding) / (seriesCount + 0.75);
holeSize = currentSize * 0.75;
totalSize -= holeSize;
}
ir = holeSize;
for (i = 0; i < seriesCount; i++) {
currentSeries = series[i];
size = valueOrDefault(currentSeries.size, totalSize / seriesWithoutSize);
ir += margin;
r = ir + size;
chart.seriesConfigs.push({
ir: ir,
r: r
});
margin = currentSeries.margin || 0;
ir = r;
}
PieChart.fn.reflow.call(chart, targetBox);
},
animationDelay: function (categoryIndex, seriesIndex, seriesCount) {
return categoryIndex * DONUT_SECTOR_ANIM_DELAY + INITIAL_ANIMATION_DURATION * (seriesIndex + 1) / (seriesCount + 1);
}
});
var WaterfallChart = BarChart.extend({
render: function () {
BarChart.fn.render.call(this);
this.createSegments();
},
traverseDataPoints: function (callback) {
var series = this.options.series;
var categories = this.categoryAxis.options.categories || [];
var totalCategories = categoriesCount(series);
var isVertical = !this.options.invertAxes;
for (var seriesIx = 0; seriesIx < series.length; seriesIx++) {
var currentSeries = series[seriesIx];
var total = 0;
var runningTotal = 0;
for (var categoryIx = 0; categoryIx < totalCategories; categoryIx++) {
var data = SeriesBinder.current.bindPoint(currentSeries, categoryIx);
var value = data.valueFields.value;
var summary = data.fields.summary;
var from = total;
var to;
if (summary) {
if (summary.toLowerCase() === 'total') {
data.valueFields.value = total;
from = 0;
to = total;
} else {
data.valueFields.value = runningTotal;
to = from - runningTotal;
runningTotal = 0;
}
} else if (isNumber(value)) {
runningTotal += value;
total += value;
to = total;
}
callback(data, {
category: categories[categoryIx],
categoryIx: categoryIx,
series: currentSeries,
seriesIx: seriesIx,
total: total,
runningTotal: runningTotal,
from: from,
to: to,
isVertical: isVertical
});
}
}
},
updateRange: function (value, fields) {
BarChart.fn.updateRange.call(this, { value: fields.to }, fields);
},
aboveAxis: function (point) {
return point.value >= 0;
},
plotRange: function (point) {
return [
point.from,
point.to
];
},
createSegments: function () {
var series = this.options.series;
var seriesPoints = this.seriesPoints;
var segments = this.segments = [];
for (var seriesIx = 0; seriesIx < series.length; seriesIx++) {
var currentSeries = series[seriesIx];
var points = seriesPoints[seriesIx];
if (points) {
var prevPoint;
for (var pointIx = 0; pointIx < points.length; pointIx++) {
var point = points[pointIx];
if (point && prevPoint) {
var segment = new WaterfallSegment(prevPoint, point, currentSeries);
segments.push(segment);
this.append(segment);
}
prevPoint = point;
}
}
}
}
});
var WaterfallSegment = ChartElement.extend({
init: function (from, to, series) {
var segment = this;
ChartElement.fn.init.call(segment);
segment.from = from;
segment.to = to;
segment.series = series;
},
options: {
animation: {
type: FADEIN,
delay: INITIAL_ANIMATION_DURATION
}
},
linePoints: function () {
var points = [];
var from = this.from;
var fromBox = from.box;
var toBox = this.to.box;
if (from.isVertical) {
var y = from.aboveAxis ? fromBox.y1 : fromBox.y2;
points.push([
fromBox.x1,
y
], [
toBox.x2,
y
]);
} else {
var x = from.aboveAxis ? fromBox.x2 : fromBox.x1;
points.push([
x,
fromBox.y1
], [
x,
toBox.y2
]);
}
return points;
},
createVisual: function () {
ChartElement.fn.createVisual.call(this);
var line = this.series.line || {};
var path = draw.Path.fromPoints(this.linePoints(), {
stroke: {
color: line.color,
width: line.width,
opacity: line.opacity,
dashType: line.dashType
}
});
alignPathToPixel(path);
this.visual.append(path);
}
});
function returnSelf() {
return this;
}
var Pane = BoxElement.extend({
init: function (options) {
var pane = this;
BoxElement.fn.init.call(pane, options);
options = pane.options;
pane.id = kendo.guid();
pane.createTitle();
pane.content = new ChartElement();
pane.chartContainer = new ChartContainer({}, pane);
pane.append(pane.content);
pane.axes = [];
pane.charts = [];
},
options: {
zIndex: -1,
shrinkToFit: true,
title: { align: LEFT },
visible: true
},
createTitle: function () {
var pane = this;
var titleOptions = pane.options.title;
if (typeof titleOptions === OBJECT) {
titleOptions = deepExtend({}, titleOptions, {
align: titleOptions.position,
position: TOP
});
}
pane.title = Title.buildTitle(titleOptions, pane, Pane.fn.options.title);
},
appendAxis: function (axis) {
var pane = this;
pane.content.append(axis);
pane.axes.push(axis);
axis.pane = pane;
},
appendChart: function (chart) {
var pane = this;
if (pane.chartContainer.parent !== pane.content) {
pane.content.append(pane.chartContainer);
}
pane.charts.push(chart);
pane.chartContainer.append(chart);
chart.pane = pane;
},
empty: function () {
var pane = this, plotArea = pane.parent, i;
if (plotArea) {
for (i = 0; i < pane.axes.length; i++) {
plotArea.removeAxis(pane.axes[i]);
}
for (i = 0; i < pane.charts.length; i++) {
plotArea.removeChart(pane.charts[i]);
}
}
pane.axes = [];
pane.charts = [];
pane.content.destroy();
pane.content.children = [];
pane.chartContainer.children = [];
},
reflow: function (targetBox) {
var pane = this;
var content;
if (last(pane.children) === pane.content) {
content = pane.children.pop();
}
BoxElement.fn.reflow.call(pane, targetBox);
if (content) {
pane.children.push(content);
}
if (pane.title) {
pane.contentBox.y1 += pane.title.box.height();
}
},
visualStyle: function () {
var style = BoxElement.fn.visualStyle.call(this);
style.zIndex = -10;
return style;
},
renderComplete: function () {
if (this.options.visible) {
this.createGridLines();
}
},
stackRoot: returnSelf,
clipRoot: returnSelf,
createGridLines: function () {
var pane = this, axes = pane.axes, allAxes = axes.concat(pane.parent.axes), vGridLines = [], hGridLines = [], gridLines, i, j, axis, vertical, altAxis;
for (i = 0; i < axes.length; i++) {
axis = axes[i];
vertical = axis.options.vertical;
gridLines = vertical ? vGridLines : hGridLines;
for (j = 0; j < allAxes.length; j++) {
if (gridLines.length === 0) {
altAxis = allAxes[j];
if (vertical !== altAxis.options.vertical) {
append(gridLines, axis.createGridLines(altAxis));
}
}
}
}
},
refresh: function () {
this.visual.clear();
this.content.parent = null;
this.content.createGradient = $.proxy(this.createGradient, this);
this.content.renderVisual();
this.content.parent = this;
if (this.title) {
this.visual.append(this.title.visual);
}
this.visual.append(this.content.visual);
this.renderComplete();
},
clipBox: function () {
return this.chartContainer.clipBox;
}
});
var ChartContainer = ChartElement.extend({
init: function (options, pane) {
var container = this;
ChartElement.fn.init.call(container, options);
container.pane = pane;
},
shouldClip: function () {
var container = this, children = container.children, length = children.length, i;
for (i = 0; i < length; i++) {
if (children[i].options.clip === true) {
return true;
}
}
return false;
},
_clipBox: function () {
var container = this, pane = container.pane, axes = pane.axes, length = axes.length, clipBox = pane.box.clone(), axisValueField, idx, lineBox, axis;
for (idx = 0; idx < length; idx++) {
axis = axes[idx];
axisValueField = axis.options.vertical ? Y : X;
lineBox = axis.lineBox();
clipBox[axisValueField + 1] = lineBox[axisValueField + 1];
clipBox[axisValueField + 2] = lineBox[axisValueField + 2];
}
return clipBox;
},
createVisual: function () {
this.visual = new draw.Group({ zIndex: 0 });
if (this.shouldClip()) {
var clipBox = this.clipBox = this._clipBox();
var clipRect = clipBox.toRect();
var clipPath = draw.Path.fromRect(clipRect);
this.visual.clip(clipPath);
this.unclipLabels();
}
},
stackRoot: returnSelf,
unclipLabels: function () {
var container = this, charts = container.children, clipBox = container.clipBox, points, point, i, j, length;
for (i = 0; i < charts.length; i++) {
points = charts[i].points || {};
length = points.length;
for (j = 0; j < length; j++) {
point = points[j];
if (point && point.label && point.label.options.visible) {
if (point.overlapsBox(clipBox)) {
if (point.label.alignToClipBox) {
point.label.alignToClipBox(clipBox);
}
point.label.options.noclip = true;
}
}
}
}
},
destroy: function () {
ChartElement.fn.destroy.call(this);
delete this.parent;
}
});
var PlotAreaBase = ChartElement.extend({
init: function (series, options) {
var plotArea = this;
ChartElement.fn.init.call(plotArea, options);
plotArea.series = series;
plotArea.initSeries();
plotArea.charts = [];
plotArea.options.legend.items = [];
plotArea.axes = [];
plotArea.crosshairs = [];
plotArea.createPanes();
plotArea.render();
plotArea.createCrosshairs();
},
options: {
series: [],
plotArea: { margin: {} },
background: '',
border: {
color: BLACK,
width: 0
},
legend: {
inactiveItems: {
labels: { color: '#919191' },
markers: { color: '#919191' }
}
}
},
initSeries: function () {
var series = this.series, i, currentSeries;
for (i = 0; i < series.length; i++) {
currentSeries = series[i];
currentSeries.index = i;
}
},
createPanes: function () {
var plotArea = this, panes = [], paneOptions = plotArea.options.panes || [], i, panesLength = math.max(paneOptions.length, 1), currentPane;
for (i = 0; i < panesLength; i++) {
currentPane = new Pane(paneOptions[i]);
currentPane.paneIndex = i;
panes.push(currentPane);
plotArea.append(currentPane);
}
plotArea.panes = panes;
},
createCrosshairs: function (panes) {
var plotArea = this, i, j, pane, axis, currentCrosshair;
panes = panes || plotArea.panes;
for (i = 0; i < panes.length; i++) {
pane = panes[i];
for (j = 0; j < pane.axes.length; j++) {
axis = pane.axes[j];
if (axis.options.crosshair && axis.options.crosshair.visible) {
currentCrosshair = new Crosshair(axis, axis.options.crosshair);
plotArea.crosshairs.push(currentCrosshair);
pane.content.append(currentCrosshair);
}
}
}
},
removeCrosshairs: function (pane) {
var plotArea = this, crosshairs = plotArea.crosshairs, axes = pane.axes, i, j;
for (i = crosshairs.length - 1; i >= 0; i--) {
for (j = 0; j < axes.length; j++) {
if (crosshairs[i].axis === axes[j]) {
crosshairs.splice(i, 1);
break;
}
}
}
},
hideCrosshairs: function () {
var crosshairs = this.crosshairs;
for (var idx = 0; idx < crosshairs.length; idx++) {
crosshairs[idx].hide();
}
},
findPane: function (name) {
var plotArea = this, panes = plotArea.panes, i, matchingPane;
for (i = 0; i < panes.length; i++) {
if (panes[i].options.name === name) {
matchingPane = panes[i];
break;
}
}
return matchingPane || panes[0];
},
findPointPane: function (point) {
var plotArea = this, panes = plotArea.panes, i, matchingPane;
for (i = 0; i < panes.length; i++) {
if (panes[i].box.containsPoint(point)) {
matchingPane = panes[i];
break;
}
}
return matchingPane;
},
appendAxis: function (axis) {
var plotArea = this, pane = plotArea.findPane(axis.options.pane);
pane.appendAxis(axis);
plotArea.axes.push(axis);
axis.plotArea = plotArea;
},
removeAxis: function (axisToRemove) {
var plotArea = this, i, axis, filteredAxes = [];
for (i = 0; i < plotArea.axes.length; i++) {
axis = plotArea.axes[i];
if (axisToRemove !== axis) {
filteredAxes.push(axis);
} else {
axis.destroy();
}
}
plotArea.axes = filteredAxes;
},
appendChart: function (chart, pane) {
var plotArea = this;
plotArea.charts.push(chart);
if (pane) {
pane.appendChart(chart);
} else {
plotArea.append(chart);
}
},
removeChart: function (chartToRemove) {
var plotArea = this, i, chart, filteredCharts = [];
for (i = 0; i < plotArea.charts.length; i++) {
chart = plotArea.charts[i];
if (chart !== chartToRemove) {
filteredCharts.push(chart);
} else {
chart.destroy();
}
}
plotArea.charts = filteredCharts;
},
addToLegend: function (series) {
var count = series.length, data = [], i, currentSeries, text, legend = this.options.legend, labels = legend.labels || {}, inactiveItems = legend.inactiveItems || {}, inactiveItemsLabels = inactiveItems.labels || {}, color, itemLabelOptions, markerColor, defaults, seriesVisible, labelTemplate;
for (i = 0; i < count; i++) {
currentSeries = series[i];
seriesVisible = currentSeries.visible !== false;
if (currentSeries.visibleInLegend === false) {
continue;
}
text = currentSeries.name || '';
labelTemplate = seriesVisible ? labels.template : inactiveItemsLabels.template || labels.template;
if (labelTemplate) {
text = template(labelTemplate)({
text: text,
series: currentSeries
});
}
color = currentSeries.color;
defaults = currentSeries._defaults;
if (isFn(color) && defaults) {
color = defaults.color;
}
if (seriesVisible) {
itemLabelOptions = {};
markerColor = color;
} else {
itemLabelOptions = {
color: inactiveItemsLabels.color,
font: inactiveItemsLabels.font
};
markerColor = inactiveItems.markers.color;
}
if (text) {
data.push({
text: text,
labels: itemLabelOptions,
markerColor: markerColor,
series: currentSeries,
active: seriesVisible
});
}
}
append(legend.items, data);
},
groupAxes: function (panes) {
var xAxes = [], yAxes = [], paneAxes, axis, paneIx, axisIx;
for (paneIx = 0; paneIx < panes.length; paneIx++) {
paneAxes = panes[paneIx].axes;
for (axisIx = 0; axisIx < paneAxes.length; axisIx++) {
axis = paneAxes[axisIx];
if (axis.options.vertical) {
yAxes.push(axis);
} else {
xAxes.push(axis);
}
}
}
return {
x: xAxes,
y: yAxes,
any: xAxes.concat(yAxes)
};
},
groupSeriesByPane: function () {
var plotArea = this, series = plotArea.series, seriesByPane = {}, i, pane, currentSeries;
for (i = 0; i < series.length; i++) {
currentSeries = series[i];
pane = plotArea.seriesPaneName(currentSeries);
if (seriesByPane[pane]) {
seriesByPane[pane].push(currentSeries);
} else {
seriesByPane[pane] = [currentSeries];
}
}
return seriesByPane;
},
filterVisibleSeries: function (series) {
var i, currentSeries, result = [];
for (i = 0; i < series.length; i++) {
currentSeries = series[i];
if (currentSeries.visible !== false) {
result.push(currentSeries);
}
}
return result;
},
reflow: function (targetBox) {
var plotArea = this, options = plotArea.options.plotArea, panes = plotArea.panes, margin = getSpacing(options.margin);
plotArea.box = targetBox.clone().unpad(margin);
plotArea.reflowPanes();
plotArea.reflowAxes(panes);
plotArea.reflowCharts(panes);
},
redraw: function (panes) {
var plotArea = this, i;
panes = [].concat(panes);
this.initSeries();
for (i = 0; i < panes.length; i++) {
plotArea.removeCrosshairs(panes[i]);
panes[i].empty();
}
plotArea.render(panes);
plotArea.reflowAxes(plotArea.panes);
plotArea.reflowCharts(panes);
plotArea.createCrosshairs(panes);
for (i = 0; i < panes.length; i++) {
panes[i].refresh();
}
},
axisCrossingValues: function (axis, crossingAxes) {
var options = axis.options, crossingValues = [].concat(options.axisCrossingValues || options.axisCrossingValue), valuesToAdd = crossingAxes.length - crossingValues.length, defaultValue = crossingValues[0] || 0, i;
for (i = 0; i < valuesToAdd; i++) {
crossingValues.push(defaultValue);
}
return crossingValues;
},
alignAxisTo: function (axis, targetAxis, crossingValue, targetCrossingValue) {
var slot = axis.getSlot(crossingValue, crossingValue, true), slotEdge = axis.options.reverse ? 2 : 1, targetSlot = targetAxis.getSlot(targetCrossingValue, targetCrossingValue, true), targetEdge = targetAxis.options.reverse ? 2 : 1, axisBox = axis.box.translate(targetSlot[X + targetEdge] - slot[X + slotEdge], targetSlot[Y + targetEdge] - slot[Y + slotEdge]);
if (axis.pane !== targetAxis.pane) {
axisBox.translate(0, axis.pane.box.y1 - targetAxis.pane.box.y1);
}
axis.reflow(axisBox);
},
alignAxes: function (xAxes, yAxes) {
var plotArea = this, xAnchor = xAxes[0], yAnchor = yAxes[0], xAnchorCrossings = plotArea.axisCrossingValues(xAnchor, yAxes), yAnchorCrossings = plotArea.axisCrossingValues(yAnchor, xAxes), leftAnchors = {}, rightAnchors = {}, topAnchors = {}, bottomAnchors = {}, pane, paneId, axis, i;
for (i = 0; i < yAxes.length; i++) {
axis = yAxes[i];
pane = axis.pane;
paneId = pane.id;
plotArea.alignAxisTo(axis, xAnchor, yAnchorCrossings[i], xAnchorCrossings[i]);
if (axis.options._overlap) {
continue;
}
if (round(axis.lineBox().x1) === round(xAnchor.lineBox().x1)) {
if (leftAnchors[paneId]) {
axis.reflow(axis.box.alignTo(leftAnchors[paneId].box, LEFT).translate(-axis.options.margin, 0));
}
leftAnchors[paneId] = axis;
}
if (round(axis.lineBox().x2) === round(xAnchor.lineBox().x2)) {
if (!axis._mirrored) {
axis.options.labels.mirror = !axis.options.labels.mirror;
axis._mirrored = true;
}
plotArea.alignAxisTo(axis, xAnchor, yAnchorCrossings[i], xAnchorCrossings[i]);
if (rightAnchors[paneId]) {
axis.reflow(axis.box.alignTo(rightAnchors[paneId].box, RIGHT).translate(axis.options.margin, 0));
}
rightAnchors[paneId] = axis;
}
if (i !== 0 && yAnchor.pane === axis.pane) {
axis.alignTo(yAnchor);
axis.reflow(axis.box);
}
}
for (i = 0; i < xAxes.length; i++) {
axis = xAxes[i];
pane = axis.pane;
paneId = pane.id;
plotArea.alignAxisTo(axis, yAnchor, xAnchorCrossings[i], yAnchorCrossings[i]);
if (axis.options._overlap) {
continue;
}
if (round(axis.lineBox().y1) === round(yAnchor.lineBox().y1)) {
if (!axis._mirrored) {
axis.options.labels.mirror = !axis.options.labels.mirror;
axis._mirrored = true;
}
plotArea.alignAxisTo(axis, yAnchor, xAnchorCrossings[i], yAnchorCrossings[i]);
if (topAnchors[paneId]) {
axis.reflow(axis.box.alignTo(topAnchors[paneId].box, TOP).translate(0, -axis.options.margin));
}
topAnchors[paneId] = axis;
}
if (round(axis.lineBox().y2, COORD_PRECISION) === round(yAnchor.lineBox().y2, COORD_PRECISION)) {
if (bottomAnchors[paneId]) {
axis.reflow(axis.box.alignTo(bottomAnchors[paneId].box, BOTTOM).translate(0, axis.options.margin));
}
bottomAnchors[paneId] = axis;
}
if (i !== 0) {
axis.alignTo(xAnchor);
axis.reflow(axis.box);
}
}
},
shrinkAxisWidth: function (panes) {
var plotArea = this, axes = plotArea.groupAxes(panes).any, axisBox = axisGroupBox(axes), overflowX = 0, i, currentPane, currentAxis;
for (i = 0; i < panes.length; i++) {
currentPane = panes[i];
if (currentPane.axes.length > 0) {
overflowX = math.max(overflowX, axisBox.width() - currentPane.contentBox.width());
}
}
if (overflowX !== 0) {
for (i = 0; i < axes.length; i++) {
currentAxis = axes[i];
if (!currentAxis.options.vertical) {
currentAxis.reflow(currentAxis.box.shrink(overflowX, 0));
}
}
}
},
shrinkAxisHeight: function (panes) {
var i, currentPane, axes, overflowY, j, currentAxis, shrinked;
for (i = 0; i < panes.length; i++) {
currentPane = panes[i];
axes = currentPane.axes;
overflowY = math.max(0, axisGroupBox(axes).height() - currentPane.contentBox.height());
if (overflowY !== 0) {
for (j = 0; j < axes.length; j++) {
currentAxis = axes[j];
if (currentAxis.options.vertical) {
currentAxis.reflow(currentAxis.box.shrink(0, overflowY));
}
}
shrinked = true;
}
}
return shrinked;
},
fitAxes: function (panes) {
var plotArea = this, axes = plotArea.groupAxes(panes).any, offsetX = 0, paneAxes, paneBox, axisBox, offsetY, currentPane, currentAxis, i, j;
for (i = 0; i < panes.length; i++) {
currentPane = panes[i];
paneAxes = currentPane.axes;
paneBox = currentPane.contentBox;
if (paneAxes.length > 0) {
axisBox = axisGroupBox(paneAxes);
offsetX = math.max(offsetX, paneBox.x1 - axisBox.x1);
offsetY = math.max(paneBox.y1 - axisBox.y1, paneBox.y2 - axisBox.y2);
for (j = 0; j < paneAxes.length; j++) {
currentAxis = paneAxes[j];
currentAxis.reflow(currentAxis.box.translate(0, offsetY));
}
}
}
for (i = 0; i < axes.length; i++) {
currentAxis = axes[i];
currentAxis.reflow(currentAxis.box.translate(offsetX, 0));
}
},
reflowAxes: function (panes) {
var plotArea = this, i, axes = plotArea.groupAxes(panes);
for (i = 0; i < panes.length; i++) {
plotArea.reflowPaneAxes(panes[i]);
}
if (axes.x.length > 0 && axes.y.length > 0) {
plotArea.alignAxes(axes.x, axes.y);
plotArea.shrinkAxisWidth(panes);
plotArea.autoRotateAxisLabels(axes);
plotArea.alignAxes(axes.x, axes.y);
if (plotArea.shrinkAxisWidth(panes)) {
plotArea.alignAxes(axes.x, axes.y);
}
plotArea.shrinkAxisHeight(panes);
plotArea.alignAxes(axes.x, axes.y);
if (plotArea.shrinkAxisHeight(panes)) {
plotArea.alignAxes(axes.x, axes.y);
}
plotArea.fitAxes(panes);
}
},
autoRotateAxisLabels: function (groupedAxes) {
var axes = this.axes;
var panes = this.panes;
var axis, idx, rotated;
for (idx = 0; idx < axes.length; idx++) {
axis = axes[idx];
if (axis.autoRotateLabels()) {
rotated = true;
}
}
if (rotated) {
for (idx = 0; idx < panes.length; idx++) {
this.reflowPaneAxes(panes[idx]);
}
if (groupedAxes.x.length > 0 && groupedAxes.y.length > 0) {
this.alignAxes(groupedAxes.x, groupedAxes.y);
this.shrinkAxisWidth(panes);
}
}
},
reflowPaneAxes: function (pane) {
var axes = pane.axes, i, length = axes.length;
if (length > 0) {
for (i = 0; i < length; i++) {
axes[i].reflow(pane.contentBox);
}
}
},
reflowCharts: function (panes) {
var plotArea = this, charts = plotArea.charts, count = charts.length, box = plotArea.box, chartPane, i;
for (i = 0; i < count; i++) {
chartPane = charts[i].pane;
if (!chartPane || inArray(chartPane, panes)) {
charts[i].reflow(box);
}
}
},
reflowPanes: function () {
var plotArea = this, box = plotArea.box, panes = plotArea.panes, panesLength = panes.length, i, currentPane, paneBox, remainingHeight = box.height(), remainingPanes = panesLength, autoHeightPanes = 0, top = box.y1, height, percents;
for (i = 0; i < panesLength; i++) {
currentPane = panes[i];
height = currentPane.options.height;
currentPane.options.width = box.width();
if (!currentPane.options.height) {
autoHeightPanes++;
} else {
if (height.indexOf && height.indexOf('%')) {
percents = parseInt(height, 10) / 100;
currentPane.options.height = percents * box.height();
}
currentPane.reflow(box.clone());
remainingHeight -= currentPane.options.height;
}
}
for (i = 0; i < panesLength; i++) {
currentPane = panes[i];
if (!currentPane.options.height) {
currentPane.options.height = remainingHeight / autoHeightPanes;
}
}
for (i = 0; i < panesLength; i++) {
currentPane = panes[i];
paneBox = box.clone().move(box.x1, top);
currentPane.reflow(paneBox);
remainingPanes--;
top += currentPane.options.height;
}
},
backgroundBox: function () {
var plotArea = this, axes = plotArea.axes, axesCount = axes.length, lineBox, box, i, j, axisA, axisB;
for (i = 0; i < axesCount; i++) {
axisA = axes[i];
for (j = 0; j < axesCount; j++) {
axisB = axes[j];
if (axisA.options.vertical !== axisB.options.vertical) {
lineBox = axisA.lineBox().clone().wrap(axisB.lineBox());
if (!box) {
box = lineBox;
} else {
box = box.wrap(lineBox);
}
}
}
}
return box || plotArea.box;
},
createVisual: function () {
ChartElement.fn.createVisual.call(this);
var bgBox = this.backgroundBox();
var options = this.options.plotArea;
var border = options.border || {};
var background = options.background;
var opacity = options.opacity;
if (util.isTransparent(background)) {
background = WHITE;
opacity = 0;
}
var bg = this._bgVisual = draw.Path.fromRect(bgBox.toRect(), {
fill: {
color: background,
opacity: opacity
},
stroke: {
color: border.width ? border.color : '',
width: border.width,
dashType: border.dashType
},
zIndex: -1
});
this.appendVisual(bg);
},
pointsByCategoryIndex: function (categoryIndex) {
var charts = this.charts, result = [], i, j, points, point, chart;
if (categoryIndex !== null) {
for (i = 0; i < charts.length; i++) {
chart = charts[i];
if (chart.pane.options.name === '_navigator') {
continue;
}
points = charts[i].categoryPoints[categoryIndex];
if (points && points.length) {
for (j = 0; j < points.length; j++) {
point = points[j];
if (point && defined(point.value) && point.value !== null) {
result.push(point);
}
}
}
}
}
return result;
},
pointsBySeriesIndex: function (seriesIndex) {
var charts = this.charts, result = [], points, point, i, j, chart;
for (i = 0; i < charts.length; i++) {
chart = charts[i];
points = chart.points;
for (j = 0; j < points.length; j++) {
point = points[j];
if (point && point.options.index === seriesIndex) {
result.push(point);
}
}
}
return result;
},
pointsBySeriesName: function (name) {
var charts = this.charts, result = [], points, point, i, j, chart;
for (i = 0; i < charts.length; i++) {
chart = charts[i];
points = chart.points;
for (j = 0; j < points.length; j++) {
point = points[j];
if (point && point.series.name === name) {
result.push(point);
}
}
}
return result;
},
paneByPoint: function (point) {
var plotArea = this, panes = plotArea.panes, pane, i;
for (i = 0; i < panes.length; i++) {
pane = panes[i];
if (pane.box.containsPoint(point)) {
return pane;
}
}
}
});
var CategoricalPlotArea = PlotAreaBase.extend({
init: function (series, options) {
var plotArea = this;
plotArea.namedCategoryAxes = {};
plotArea.namedValueAxes = {};
plotArea.valueAxisRangeTracker = new AxisGroupRangeTracker();
if (series.length > 0) {
plotArea.invertAxes = inArray(series[0].type, [
BAR,
BULLET,
VERTICAL_LINE,
VERTICAL_AREA,
RANGE_BAR,
HORIZONTAL_WATERFALL
]);
for (var i = 0; i < series.length; i++) {
var stack = series[i].stack;
if (stack && stack.type === '100%') {
plotArea.stack100 = true;
break;
}
}
}
PlotAreaBase.fn.init.call(plotArea, series, options);
},
options: {
categoryAxis: { categories: [] },
valueAxis: {}
},
render: function (panes) {
var plotArea = this;
panes = panes || plotArea.panes;
plotArea.createCategoryAxes(panes);
plotArea.aggregateCategories(panes);
plotArea.createCategoryAxesLabels(panes);
plotArea.createCharts(panes);
plotArea.createValueAxes(panes);
},
removeAxis: function (axis) {
var plotArea = this, axisName = axis.options.name;
PlotAreaBase.fn.removeAxis.call(plotArea, axis);
if (axis instanceof CategoryAxis) {
delete plotArea.namedCategoryAxes[axisName];
} else {
plotArea.valueAxisRangeTracker.reset(axisName);
delete plotArea.namedValueAxes[axisName];
}
if (axis === plotArea.categoryAxis) {
delete plotArea.categoryAxis;
}
if (axis === plotArea.valueAxis) {
delete plotArea.valueAxis;
}
},
createCharts: function (panes) {
var seriesByPane = this.groupSeriesByPane();
for (var i = 0; i < panes.length; i++) {
var pane = panes[i];
var paneSeries = seriesByPane[pane.options.name || 'default'] || [];
this.addToLegend(paneSeries);
var visibleSeries = this.filterVisibleSeries(paneSeries);
if (!visibleSeries) {
continue;
}
var groups = this.groupSeriesByCategoryAxis(visibleSeries);
for (var groupIx = 0; groupIx < groups.length; groupIx++) {
this.createChartGroup(groups[groupIx], pane);
}
}
},
createChartGroup: function (series, pane) {
this.createAreaChart(filterSeriesByType(series, [
AREA,
VERTICAL_AREA
]), pane);
this.createBarChart(filterSeriesByType(series, [
COLUMN,
BAR
]), pane);
this.createRangeBarChart(filterSeriesByType(series, [
RANGE_COLUMN,
RANGE_BAR
]), pane);
this.createBulletChart(filterSeriesByType(series, [
BULLET,
VERTICAL_BULLET
]), pane);
this.createCandlestickChart(filterSeriesByType(series, CANDLESTICK), pane);
this.createBoxPlotChart(filterSeriesByType(series, BOX_PLOT), pane);
this.createOHLCChart(filterSeriesByType(series, OHLC), pane);
this.createWaterfallChart(filterSeriesByType(series, [
WATERFALL,
HORIZONTAL_WATERFALL
]), pane);
this.createLineChart(filterSeriesByType(series, [
LINE,
VERTICAL_LINE
]), pane);
},
aggregateCategories: function (panes) {
var plotArea = this, series = plotArea.srcSeries || plotArea.series, processedSeries = [], i, currentSeries, categoryAxis, axisPane, dateAxis;
for (i = 0; i < series.length; i++) {
currentSeries = series[i];
categoryAxis = plotArea.seriesCategoryAxis(currentSeries);
axisPane = plotArea.findPane(categoryAxis.options.pane);
dateAxis = equalsIgnoreCase(categoryAxis.options.type, DATE);
if ((dateAxis || currentSeries.categoryField) && inArray(axisPane, panes)) {
currentSeries = plotArea.aggregateSeries(currentSeries, categoryAxis);
} else if (isNumber(categoryAxis.options.min) || isNumber(categoryAxis.options.max)) {
currentSeries = plotArea.filterSeries(currentSeries, categoryAxis);
}
processedSeries.push(currentSeries);
}
plotArea.srcSeries = series;
plotArea.series = processedSeries;
},
filterSeries: function (currentSeries, categoryAxis) {
var range = categoryAxis.totalRangeIndices();
var justified = categoryAxis.options.justified;
var outOfRangePoints = inArray(currentSeries.type, [
LINE,
VERTICAL_LINE,
AREA,
VERTICAL_AREA
]);
var categoryIx;
range.min = isNumber(categoryAxis.options.min) ? math.floor(range.min) : 0;
range.max = isNumber(categoryAxis.options.max) ? justified ? math.floor(range.max) + 1 : math.ceil(range.max) : currentSeries.data.length;
currentSeries = deepExtend({}, currentSeries);
if (outOfRangePoints) {
var minCategory = range.min - 1;
var srcCategories = categoryAxis.options.srcCategories || [];
if (minCategory >= 0 && minCategory < currentSeries.data.length) {
categoryIx = minCategory;
currentSeries._outOfRangeMinPoint = {
item: currentSeries.data[categoryIx],
category: srcCategories[categoryIx],
categoryIx: -1
};
}
if (range.max < currentSeries.data.length) {
categoryIx = range.max;
currentSeries._outOfRangeMaxPoint = {
item: currentSeries.data[categoryIx],
category: srcCategories[categoryIx],
categoryIx: range.max - range.min
};
}
}
categoryAxis._seriesMax = math.max(categoryAxis._seriesMax || 0, currentSeries.data.length);
currentSeries.data = (currentSeries.data || []).slice(range.min, range.max);
return currentSeries;
},
aggregateSeries: function (series, categoryAxis) {
var axisOptions = categoryAxis.options, dateAxis = equalsIgnoreCase(categoryAxis.options.type, DATE), categories = axisOptions.categories, srcCategories = axisOptions.srcCategories || categories, srcData = series.data, srcPoints = [], result = deepExtend({}, series), aggregatorSeries = deepExtend({}, series), dataItems = axisOptions.dataItems || [], i, category, categoryIx, data, aggregator, getFn = getField, outOfRangeMinIdx = util.MIN_NUM, outOfRangeMinCategory, outOfRangeMaxCategory, outOfRangeMaxIdx = util.MAX_NUM, outOfRangePoints = inArray(series.type, [
LINE,
VERTICAL_LINE,
AREA,
VERTICAL_AREA
]);
result.data = data = [];
if (dateAxis) {
getFn = getDateField;
}
for (i = 0; i < srcData.length; i++) {
if (series.categoryField) {
category = getFn(series.categoryField, srcData[i]);
} else {
category = srcCategories[i];
}
if (defined(category)) {
categoryIx = categoryAxis.categoryIndex(category);
if (0 <= categoryIx && categoryIx < categories.length) {
srcPoints[categoryIx] = srcPoints[categoryIx] || [];
srcPoints[categoryIx].push(i);
} else if (outOfRangePoints) {
if (categoryIx < 0) {
if (categoryIx == outOfRangeMinIdx) {
outOfRangeMinCategory.points.push(i);
} else if (categoryIx > outOfRangeMinIdx) {
outOfRangeMinIdx = categoryIx;
outOfRangeMinCategory = {
category: category,
points: [i]
};
}
} else if (categoryIx >= categories.length) {
if (categoryIx == outOfRangeMaxIdx) {
outOfRangeMaxCategory.points.push(i);
} else if (categoryIx < outOfRangeMaxIdx) {
outOfRangeMaxIdx = categoryIx;
outOfRangeMaxCategory = {
category: category,
points: [i]
};
}
}
}
}
}
aggregator = new SeriesAggregator(aggregatorSeries, SeriesBinder.current, DefaultAggregates.current);
for (i = 0; i < categories.length; i++) {
data[i] = aggregator.aggregatePoints(srcPoints[i], categories[i]);
if (srcPoints[i]) {
dataItems[i] = data[i];
}
}
if (outOfRangeMinCategory && data.length) {
result._outOfRangeMinPoint = {
item: aggregator.aggregatePoints(outOfRangeMinCategory.points, outOfRangeMinCategory.category),
categoryIx: outOfRangeMinIdx,
category: outOfRangeMinCategory.category
};
}
if (outOfRangeMaxCategory && data.length) {
result._outOfRangeMaxPoint = {
item: aggregator.aggregatePoints(outOfRangeMaxCategory.points, outOfRangeMaxCategory.category),
categoryIx: outOfRangeMaxIdx,
category: outOfRangeMaxCategory.category
};
}
categoryAxis.options.dataItems = dataItems;
return result;
},
appendChart: function (chart, pane) {
var plotArea = this, series = chart.options.series, categoryAxis = plotArea.seriesCategoryAxis(series[0]), categories = categoryAxis.options.categories, categoriesToAdd = math.max(0, categoriesCount(series) - categories.length);
while (categoriesToAdd--) {
categories.push('');
}
plotArea.valueAxisRangeTracker.update(chart.valueAxisRanges);
PlotAreaBase.fn.appendChart.call(plotArea, chart, pane);
},
seriesPaneName: function (series) {
var plotArea = this, options = plotArea.options, axisName = series.axis, axisOptions = [].concat(options.valueAxis), axis = $.grep(axisOptions, function (a) {
return a.name === axisName;
})[0], panes = options.panes || [{}], defaultPaneName = (panes[0] || {}).name || 'default', paneName = (axis || {}).pane || defaultPaneName;
return paneName;
},
seriesCategoryAxis: function (series) {
var plotArea = this, axisName = series.categoryAxis, axis = axisName ? plotArea.namedCategoryAxes[axisName] : plotArea.categoryAxis;
if (!axis) {
throw new Error('Unable to locate category axis with name ' + axisName);
}
return axis;
},
stackableChartOptions: function (firstSeries, pane) {
var stack = firstSeries.stack, isStacked100 = stack && stack.type === '100%', clip;
if (defined(pane.options.clip)) {
clip = pane.options.clip;
} else if (isStacked100) {
clip = false;
}
return {
isStacked: stack,
isStacked100: isStacked100,
clip: clip
};
},
groupSeriesByCategoryAxis: function (series) {
var unique = {};
var categoryAxes = $.map(series, function (s) {
var name = s.categoryAxis || '$$default$$';
if (!unique.hasOwnProperty(name)) {
unique[name] = true;
return name;
}
});
function groupSeries(axis, axisIx) {
return $.grep(series, function (s) {
return axisIx === 0 && !s.categoryAxis || s.categoryAxis == axis;
});
}
var groups = [];
for (var axisIx = 0; axisIx < categoryAxes.length; axisIx++) {
var axis = categoryAxes[axisIx];
var axisSeries = groupSeries(axis, axisIx);
if (axisSeries.length === 0) {
continue;
}
groups.push(axisSeries);
}
return groups;
},
createBarChart: function (series, pane) {
if (series.length === 0) {
return;
}
var plotArea = this, firstSeries = series[0], barChart = new BarChart(plotArea, extend({
series: series,
invertAxes: plotArea.invertAxes,
gap: firstSeries.gap,
spacing: firstSeries.spacing
}, plotArea.stackableChartOptions(firstSeries, pane)));
plotArea.appendChart(barChart, pane);
},
createRangeBarChart: function (series, pane) {
if (series.length === 0) {
return;
}
var plotArea = this, firstSeries = series[0], rangeColumnChart = new RangeBarChart(plotArea, {
series: series,
invertAxes: plotArea.invertAxes,
gap: firstSeries.gap,
spacing: firstSeries.spacing
});
plotArea.appendChart(rangeColumnChart, pane);
},
createBulletChart: function (series, pane) {
if (series.length === 0) {
return;
}
var plotArea = this, firstSeries = series[0], bulletChart = new BulletChart(plotArea, {
series: series,
invertAxes: plotArea.invertAxes,
gap: firstSeries.gap,
spacing: firstSeries.spacing,
clip: pane.options.clip
});
plotArea.appendChart(bulletChart, pane);
},
createLineChart: function (series, pane) {
if (series.length === 0) {
return;
}
var plotArea = this, firstSeries = series[0], lineChart = new LineChart(plotArea, extend({
invertAxes: plotArea.invertAxes,
series: series
}, plotArea.stackableChartOptions(firstSeries, pane)));
plotArea.appendChart(lineChart, pane);
},
createAreaChart: function (series, pane) {
if (series.length === 0) {
return;
}
var plotArea = this, firstSeries = series[0], areaChart = new AreaChart(plotArea, extend({
invertAxes: plotArea.invertAxes,
series: series
}, plotArea.stackableChartOptions(firstSeries, pane)));
plotArea.appendChart(areaChart, pane);
},
createOHLCChart: function (series, pane) {
if (series.length === 0) {
return;
}
var plotArea = this, firstSeries = series[0], chart = new OHLCChart(plotArea, {
invertAxes: plotArea.invertAxes,
gap: firstSeries.gap,
series: series,
spacing: firstSeries.spacing,
clip: pane.options.clip
});
plotArea.appendChart(chart, pane);
},
createCandlestickChart: function (series, pane) {
if (series.length === 0) {
return;
}
var plotArea = this, firstSeries = series[0], chart = new CandlestickChart(plotArea, {
invertAxes: plotArea.invertAxes,
gap: firstSeries.gap,
series: series,
spacing: firstSeries.spacing,
clip: pane.options.clip
});
plotArea.appendChart(chart, pane);
},
createBoxPlotChart: function (series, pane) {
if (series.length === 0) {
return;
}
var plotArea = this, firstSeries = series[0], chart = new BoxPlotChart(plotArea, {
invertAxes: plotArea.invertAxes,
gap: firstSeries.gap,
series: series,
spacing: firstSeries.spacing,
clip: pane.options.clip
});
plotArea.appendChart(chart, pane);
},
createWaterfallChart: function (series, pane) {
if (series.length === 0) {
return;
}
var plotArea = this, firstSeries = series[0], waterfallChart = new WaterfallChart(plotArea, {
series: series,
invertAxes: plotArea.invertAxes,
gap: firstSeries.gap,
spacing: firstSeries.spacing
});
plotArea.appendChart(waterfallChart, pane);
},
axisRequiresRounding: function (categoryAxisName, categoryAxisIndex) {
var plotArea = this, centeredSeries = filterSeriesByType(plotArea.series, EQUALLY_SPACED_SERIES), seriesIx, seriesAxis;
for (seriesIx = 0; seriesIx < plotArea.series.length; seriesIx++) {
var currentSeries = plotArea.series[seriesIx];
if (currentSeries.type === LINE || currentSeries.type === AREA) {
var line = currentSeries.line;
if (line && line.style === STEP) {
centeredSeries.push(currentSeries);
}
}
}
for (seriesIx = 0; seriesIx < centeredSeries.length; seriesIx++) {
seriesAxis = centeredSeries[seriesIx].categoryAxis || '';
if (seriesAxis === categoryAxisName || !seriesAxis && categoryAxisIndex === 0) {
return true;
}
}
},
aggregatedAxis: function (categoryAxisName, categoryAxisIndex) {
var plotArea = this, series = plotArea.series, seriesIx, seriesAxis;
for (seriesIx = 0; seriesIx < series.length; seriesIx++) {
seriesAxis = series[seriesIx].categoryAxis || '';
if ((seriesAxis === categoryAxisName || !seriesAxis && categoryAxisIndex === 0) && series[seriesIx].categoryField) {
return true;
}
}
},
createCategoryAxesLabels: function () {
var axes = this.axes;
for (var i = 0; i < axes.length; i++) {
if (axes[i] instanceof CategoryAxis) {
axes[i].createLabels();
}
}
},
createCategoryAxes: function (panes) {
var plotArea = this, invertAxes = plotArea.invertAxes, definitions = [].concat(plotArea.options.categoryAxis), i, axisOptions, axisPane, categories, type, name, categoryAxis, axes = [], primaryAxis;
for (i = 0; i < definitions.length; i++) {
axisOptions = definitions[i];
axisPane = plotArea.findPane(axisOptions.pane);
if (inArray(axisPane, panes)) {
name = axisOptions.name;
categories = axisOptions.categories || [];
type = axisOptions.type || '';
axisOptions = deepExtend({
vertical: invertAxes,
axisCrossingValue: invertAxes ? MAX_VALUE : 0,
_deferLabels: true
}, axisOptions);
if (!defined(axisOptions.justified)) {
axisOptions.justified = plotArea.isJustified();
}
if (plotArea.axisRequiresRounding(name, i)) {
axisOptions.justified = false;
}
if (isDateAxis(axisOptions, categories[0])) {
categoryAxis = new DateCategoryAxis(axisOptions);
} else {
categoryAxis = new CategoryAxis(axisOptions);
}
if (name) {
if (plotArea.namedCategoryAxes[name]) {
throw new Error('Category axis with name ' + name + ' is already defined');
}
plotArea.namedCategoryAxes[name] = categoryAxis;
}
categoryAxis.axisIndex = i;
axes.push(categoryAxis);
plotArea.appendAxis(categoryAxis);
}
}
primaryAxis = plotArea.categoryAxis || axes[0];
plotArea.categoryAxis = primaryAxis;
if (invertAxes) {
plotArea.axisY = primaryAxis;
} else {
plotArea.axisX = primaryAxis;
}
},
isJustified: function () {
var plotArea = this, series = plotArea.series, i, currentSeries;
for (i = 0; i < series.length; i++) {
currentSeries = series[i];
if (!inArray(currentSeries.type, [
AREA,
VERTICAL_AREA
])) {
return false;
}
}
return true;
},
createValueAxes: function (panes) {
var plotArea = this, tracker = plotArea.valueAxisRangeTracker, defaultRange = tracker.query(), definitions = [].concat(plotArea.options.valueAxis), invertAxes = plotArea.invertAxes, baseOptions = { vertical: !invertAxes }, axisOptions, axisPane, valueAxis, primaryAxis, axes = [], range, axisType, defaultAxisRange, name, i;
if (plotArea.stack100) {
baseOptions.roundToMajorUnit = false;
baseOptions.labels = { format: 'P0' };
}
for (i = 0; i < definitions.length; i++) {
axisOptions = definitions[i];
axisPane = plotArea.findPane(axisOptions.pane);
if (inArray(axisPane, panes)) {
name = axisOptions.name;
defaultAxisRange = equalsIgnoreCase(axisOptions.type, LOGARITHMIC) ? {
min: 0.1,
max: 1
} : {
min: 0,
max: 1
};
range = tracker.query(name) || defaultRange || defaultAxisRange;
if (i === 0 && range && defaultRange) {
range.min = math.min(range.min, defaultRange.min);
range.max = math.max(range.max, defaultRange.max);
}
if (equalsIgnoreCase(axisOptions.type, LOGARITHMIC)) {
axisType = LogarithmicAxis;
} else {
axisType = NumericAxis;
}
valueAxis = new axisType(range.min, range.max, deepExtend({}, baseOptions, axisOptions));
if (name) {
if (plotArea.namedValueAxes[name]) {
throw new Error('Value axis with name ' + name + ' is already defined');
}
plotArea.namedValueAxes[name] = valueAxis;
}
valueAxis.axisIndex = i;
axes.push(valueAxis);
plotArea.appendAxis(valueAxis);
}
}
primaryAxis = plotArea.valueAxis || axes[0];
plotArea.valueAxis = primaryAxis;
if (invertAxes) {
plotArea.axisX = primaryAxis;
} else {
plotArea.axisY = primaryAxis;
}
},
click: function (chart, e) {
var plotArea = this, coords = chart._eventCoordinates(e), point = new Point2D(coords.x, coords.y), pane = plotArea.pointPane(point), allAxes, i, axis, categories = [], values = [];
if (!pane) {
return;
}
allAxes = pane.axes;
for (i = 0; i < allAxes.length; i++) {
axis = allAxes[i];
if (axis.getValue) {
appendIfNotNull(values, axis.getValue(point));
} else {
appendIfNotNull(categories, axis.getCategory(point));
}
}
if (categories.length === 0) {
appendIfNotNull(categories, plotArea.categoryAxis.getCategory(point));
}
if (categories.length > 0 && values.length > 0) {
chart.trigger(PLOT_AREA_CLICK, {
element: $(e.target),
originalEvent: e,
category: singleItemOrArray(categories),
value: singleItemOrArray(values)
});
}
},
pointPane: function (point) {
var plotArea = this, panes = plotArea.panes, currentPane, i;
for (i = 0; i < panes.length; i++) {
currentPane = panes[i];
if (currentPane.contentBox.containsPoint(point)) {
return currentPane;
}
}
},
updateAxisOptions: function (axis, options) {
var axesOptions = axis instanceof CategoryAxis ? [].concat(this.options.categoryAxis) : [].concat(this.options.valueAxis);
deepExtend(axesOptions[axis.axisIndex], options);
}
});
var AxisGroupRangeTracker = Class.extend({
init: function () {
var tracker = this;
tracker.axisRanges = {};
},
update: function (chartAxisRanges) {
var tracker = this, axisRanges = tracker.axisRanges, range, chartRange, axisName;
for (axisName in chartAxisRanges) {
range = axisRanges[axisName];
chartRange = chartAxisRanges[axisName];
axisRanges[axisName] = range = range || {
min: MAX_VALUE,
max: MIN_VALUE
};
range.min = math.min(range.min, chartRange.min);
range.max = math.max(range.max, chartRange.max);
}
},
reset: function (axisName) {
this.axisRanges[axisName] = undefined;
},
query: function (axisName) {
return this.axisRanges[axisName];
}
});
var XYPlotArea = PlotAreaBase.extend({
init: function (series, options) {
var plotArea = this;
plotArea.namedXAxes = {};
plotArea.namedYAxes = {};
plotArea.xAxisRangeTracker = new AxisGroupRangeTracker();
plotArea.yAxisRangeTracker = new AxisGroupRangeTracker();
PlotAreaBase.fn.init.call(plotArea, series, options);
},
options: {
xAxis: {},
yAxis: {}
},
render: function (panes) {
var plotArea = this, seriesByPane = plotArea.groupSeriesByPane(), i, pane, paneSeries, filteredSeries;
panes = panes || plotArea.panes;
for (i = 0; i < panes.length; i++) {
pane = panes[i];
paneSeries = seriesByPane[pane.options.name || 'default'] || [];
plotArea.addToLegend(paneSeries);
filteredSeries = plotArea.filterVisibleSeries(paneSeries);
if (!filteredSeries) {
continue;
}
plotArea.createScatterChart(filterSeriesByType(filteredSeries, SCATTER), pane);
plotArea.createScatterLineChart(filterSeriesByType(filteredSeries, SCATTER_LINE), pane);
plotArea.createBubbleChart(filterSeriesByType(filteredSeries, BUBBLE), pane);
}
plotArea.createAxes(panes);
},
appendChart: function (chart, pane) {
var plotArea = this;
plotArea.xAxisRangeTracker.update(chart.xAxisRanges);
plotArea.yAxisRangeTracker.update(chart.yAxisRanges);
PlotAreaBase.fn.appendChart.call(plotArea, chart, pane);
},
removeAxis: function (axis) {
var plotArea = this, axisName = axis.options.name;
PlotAreaBase.fn.removeAxis.call(plotArea, axis);
if (axis.options.vertical) {
plotArea.yAxisRangeTracker.reset(axisName);
delete plotArea.namedYAxes[axisName];
} else {
plotArea.xAxisRangeTracker.reset(axisName);
delete plotArea.namedXAxes[axisName];
}
if (axis === plotArea.axisX) {
delete plotArea.axisX;
}
if (axis === plotArea.axisY) {
delete plotArea.axisY;
}
},
seriesPaneName: function (series) {
var plotArea = this, options = plotArea.options, xAxisName = series.xAxis, xAxisOptions = [].concat(options.xAxis), xAxis = $.grep(xAxisOptions, function (a) {
return a.name === xAxisName;
})[0], yAxisName = series.yAxis, yAxisOptions = [].concat(options.yAxis), yAxis = $.grep(yAxisOptions, function (a) {
return a.name === yAxisName;
})[0], panes = options.panes || [{}], defaultPaneName = panes[0].name || 'default', paneName = (xAxis || {}).pane || (yAxis || {}).pane || defaultPaneName;
return paneName;
},
createScatterChart: function (series, pane) {
var plotArea = this;
if (series.length > 0) {
plotArea.appendChart(new ScatterChart(plotArea, {
series: series,
clip: pane.options.clip
}), pane);
}
},
createScatterLineChart: function (series, pane) {
var plotArea = this;
if (series.length > 0) {
plotArea.appendChart(new ScatterLineChart(plotArea, {
series: series,
clip: pane.options.clip
}), pane);
}
},
createBubbleChart: function (series, pane) {
var plotArea = this;
if (series.length > 0) {
plotArea.appendChart(new BubbleChart(plotArea, {
series: series,
clip: pane.options.clip
}), pane);
}
},
createXYAxis: function (options, vertical, axisIndex) {
var plotArea = this, axisName = options.name, namedAxes = vertical ? plotArea.namedYAxes : plotArea.namedXAxes, tracker = vertical ? plotArea.yAxisRangeTracker : plotArea.xAxisRangeTracker, axisOptions = deepExtend({}, options, { vertical: vertical }), isLog = equalsIgnoreCase(axisOptions.type, LOGARITHMIC), defaultRange = tracker.query(), defaultAxisRange = isLog ? {
min: 0.1,
max: 1
} : {
min: 0,
max: 1
}, range = tracker.query(axisName) || defaultRange || defaultAxisRange, axis, axisType, seriesIx, series = plotArea.series, currentSeries, seriesAxisName, firstPointValue, typeSamples = [
axisOptions.min,
axisOptions.max
], inferredDate, i;
for (seriesIx = 0; seriesIx < series.length; seriesIx++) {
currentSeries = series[seriesIx];
seriesAxisName = currentSeries[vertical ? 'yAxis' : 'xAxis'];
if (seriesAxisName == axisOptions.name || axisIndex === 0 && !seriesAxisName) {
firstPointValue = SeriesBinder.current.bindPoint(currentSeries, 0).valueFields;
typeSamples.push(firstPointValue[vertical ? 'y' : 'x']);
break;
}
}
if (axisIndex === 0 && defaultRange) {
range.min = math.min(range.min, defaultRange.min);
range.max = math.max(range.max, defaultRange.max);
}
for (i = 0; i < typeSamples.length; i++) {
if (typeSamples[i] instanceof Date) {
inferredDate = true;
break;
}
}
if (equalsIgnoreCase(axisOptions.type, DATE) || !axisOptions.type && inferredDate) {
axisType = DateValueAxis;
} else if (isLog) {
axisType = LogarithmicAxis;
} else {
axisType = NumericAxis;
}
axis = new axisType(range.min, range.max, axisOptions);
if (axisName) {
if (namedAxes[axisName]) {
throw new Error((vertical ? 'Y' : 'X') + ' axis with name ' + axisName + ' is already defined');
}
namedAxes[axisName] = axis;
}
plotArea.appendAxis(axis);
return axis;
},
createAxes: function (panes) {
var plotArea = this, options = plotArea.options, axisPane, xAxesOptions = [].concat(options.xAxis), xAxes = [], yAxesOptions = [].concat(options.yAxis), yAxes = [];
each(xAxesOptions, function (i) {
axisPane = plotArea.findPane(this.pane);
if (inArray(axisPane, panes)) {
xAxes.push(plotArea.createXYAxis(this, false, i));
}
});
each(yAxesOptions, function (i) {
axisPane = plotArea.findPane(this.pane);
if (inArray(axisPane, panes)) {
yAxes.push(plotArea.createXYAxis(this, true, i));
}
});
plotArea.axisX = plotArea.axisX || xAxes[0];
plotArea.axisY = plotArea.axisY || yAxes[0];
},
click: function (chart, e) {
var plotArea = this, coords = chart._eventCoordinates(e), point = new Point2D(coords.x, coords.y), allAxes = plotArea.axes, i, length = allAxes.length, axis, xValues = [], yValues = [], currentValue, values;
for (i = 0; i < length; i++) {
axis = allAxes[i];
values = axis.options.vertical ? yValues : xValues;
currentValue = axis.getValue(point);
if (currentValue !== null) {
values.push(currentValue);
}
}
if (xValues.length > 0 && yValues.length > 0) {
chart.trigger(PLOT_AREA_CLICK, {
element: $(e.target),
originalEvent: e,
x: singleItemOrArray(xValues),
y: singleItemOrArray(yValues)
});
}
},
updateAxisOptions: function (axis, options) {
var vertical = axis.options.vertical;
var axes = this.groupAxes(this.panes);
var index = indexOf(axis, vertical ? axes.y : axes.x);
var axisOptions = [].concat(vertical ? this.options.yAxis : this.options.xAxis)[index];
deepExtend(axisOptions, options);
}
});
var PiePlotArea = PlotAreaBase.extend({
render: function () {
var plotArea = this, series = plotArea.series;
plotArea.createPieChart(series);
},
createPieChart: function (series) {
var plotArea = this, firstSeries = series[0], pieChart = new PieChart(plotArea, {
series: series,
padding: firstSeries.padding,
startAngle: firstSeries.startAngle,
connectors: firstSeries.connectors,
legend: plotArea.options.legend
});
plotArea.appendChart(pieChart);
},
appendChart: function (chart, pane) {
PlotAreaBase.fn.appendChart.call(this, chart, pane);
append(this.options.legend.items, chart.legendItems);
}
});
var DonutPlotArea = PiePlotArea.extend({
render: function () {
var plotArea = this, series = plotArea.series;
plotArea.createDonutChart(series);
},
createDonutChart: function (series) {
var plotArea = this, firstSeries = series[0], donutChart = new DonutChart(plotArea, {
series: series,
padding: firstSeries.padding,
connectors: firstSeries.connectors,
legend: plotArea.options.legend
});
plotArea.appendChart(donutChart);
}
});
var PieAnimation = draw.Animation.extend({
options: {
easing: 'easeOutElastic',
duration: INITIAL_ANIMATION_DURATION
},
setup: function () {
this.element.transform(geom.transform().scale(START_SCALE, START_SCALE, this.options.center));
},
step: function (pos) {
this.element.transform(geom.transform().scale(pos, pos, this.options.center));
}
});
draw.AnimationFactory.current.register(PIE, PieAnimation);
var BubbleAnimation = draw.Animation.extend({
options: { easing: 'easeOutElastic' },
setup: function () {
var center = this.center = this.element.bbox().center();
this.element.transform(geom.transform().scale(START_SCALE, START_SCALE, center));
},
step: function (pos) {
this.element.transform(geom.transform().scale(pos, pos, this.center));
}
});
draw.AnimationFactory.current.register(BUBBLE, BubbleAnimation);
var Highlight = Class.extend({
init: function () {
this._points = [];
},
destroy: function () {
this._points = [];
},
show: function (points) {
points = [].concat(points);
this.hide();
for (var i = 0; i < points.length; i++) {
var point = points[i];
if (point && point.toggleHighlight && point.hasHighlight()) {
this.togglePointHighlight(point, true);
this._points.push(point);
}
}
},
togglePointHighlight: function (point, show) {
var toggleHandler = (point.options.highlight || {}).toggle;
if (toggleHandler) {
var eventArgs = {
category: point.category,
series: point.series,
dataItem: point.dataItem,
value: point.value,
preventDefault: preventDefault,
visual: point.highlightVisual(),
show: show
};
toggleHandler(eventArgs);
if (!eventArgs._defaultPrevented) {
point.toggleHighlight(show);
}
} else {
point.toggleHighlight(show);
}
},
hide: function () {
var points = this._points;
while (points.length) {
this.togglePointHighlight(points.pop(), false);
}
},
isHighlighted: function (element) {
var points = this._points;
for (var i = 0; i < points.length; i++) {
var point = points[i];
if (element == point) {
return true;
}
}
return false;
}
});
var BaseTooltip = Observable.extend({
init: function (chartElement, options) {
var tooltip = this;
Observable.fn.init.call(tooltip);
tooltip.options = deepExtend({}, tooltip.options, options);
tooltip.chartElement = chartElement;
tooltip.template = BaseTooltip.template;
if (!tooltip.template) {
tooltip.template = BaseTooltip.template = renderTemplate('<div class=\'' + CSS_PREFIX + 'tooltip ' + CSS_PREFIX + 'chart-tooltip\' ' + 'style=\'display:none; position: absolute; font: #= d.font #;' + 'border: #= d.border.width #px solid;' + 'opacity: #= d.opacity #; filter: alpha(opacity=#= d.opacity * 100 #);\'>' + '</div>');
}
var padding = getSpacing(tooltip.options.padding || {}, 'auto');
tooltip.element = $(tooltip.template(tooltip.options)).css({
'padding-top': padding.top,
'padding-right': padding.right,
'padding-bottom': padding.bottom,
'padding-left': padding.left
});
tooltip.move = proxy(tooltip.move, tooltip);
tooltip._mouseleave = proxy(tooltip._mouseleave, tooltip);
var mobileScrollerSelector = kendo.format('[{0}=\'content\'],[{0}=\'scroller\']', kendo.attr('role'));
tooltip._mobileScroller = chartElement.closest(mobileScrollerSelector).data('kendoMobileScroller');
},
destroy: function () {
this._clearShowTimeout();
if (this.element) {
this.element.off(MOUSELEAVE_NS).remove();
this.element = null;
}
},
options: {
border: { width: 1 },
opacity: 1,
animation: { duration: TOOLTIP_ANIMATION_DURATION }
},
move: function () {
var tooltip = this, options = tooltip.options, element = tooltip.element, offset;
if (!tooltip.anchor || !tooltip.element) {
return;
}
offset = tooltip._offset();
if (!tooltip.visible) {
element.css({
top: offset.top,
left: offset.left
});
}
tooltip.visible = true;
tooltip._ensureElement(document.body);
element.stop(true, true).show().animate({
left: offset.left,
top: offset.top
}, options.animation.duration);
},
_clearShowTimeout: function () {
if (this.showTimeout) {
clearTimeout(this.showTimeout);
this.showTimeout = null;
}
},
_padding: function () {
if (!this._chartPadding) {
var chartElement = this.chartElement;
this._chartPadding = {
top: parseInt(chartElement.css('paddingTop'), 10),
left: parseInt(chartElement.css('paddingLeft'), 10)
};
}
return this._chartPadding;
},
_offset: function () {
var tooltip = this, size = tooltip._measure(), anchor = tooltip.anchor, chartPadding = tooltip._padding(), chartOffset = tooltip.chartElement.offset(), top = round(anchor.y + chartPadding.top + chartOffset.top), left = round(anchor.x + chartPadding.left + chartOffset.left), zoomLevel = kendo.support.zoomLevel(), viewport = $(window), scrollTop = window.pageYOffset || document.documentElement.scrollTop || 0, scrollLeft = window.pageXOffset || document.documentElement.scrollLeft || 0, movable = (this._mobileScroller || {}).movable;
if (!movable || movable.scale === 1) {
top += tooltip._fit(top - scrollTop, size.height, viewport.outerHeight() / zoomLevel);
left += tooltip._fit(left - scrollLeft, size.width, viewport.outerWidth() / zoomLevel);
} else {
var transform = geom.transform().scale(movable.scale, movable.scale, [
movable.x,
movable.y
]);
var point = new geom.Point(left, top).transform(transform);
left = point.x;
top = point.y;
}
return {
top: top,
left: left
};
},
setStyle: function (options, point) {
var background = options.background;
var border = options.border.color;
if (point) {
var pointColor = point.color || point.options.color;
background = valueOrDefault(background, pointColor);
border = valueOrDefault(border, pointColor);
}
if (!defined(options.color)) {
var brightness = new Color(background).percBrightness();
this.element.toggleClass(CSS_PREFIX + TOOLTIP_INVERSE, brightness > 180);
}
this.element.css({
backgroundColor: background,
borderColor: border,
font: options.font,
color: options.color,
opacity: options.opacity,
borderWidth: options.border.width
});
},
show: function () {
this._clearShowTimeout();
this.showTimeout = setTimeout(this.move, TOOLTIP_SHOW_DELAY);
},
hide: function () {
var tooltip = this;
clearTimeout(tooltip.showTimeout);
tooltip._hideElement();
if (tooltip.visible) {
tooltip.point = null;
tooltip.visible = false;
tooltip.index = null;
}
},
_measure: function () {
this._ensureElement();
var size = {
width: this.element.outerWidth(),
height: this.element.outerHeight()
};
return size;
},
_ensureElement: function () {
if (this.element) {
this.element.appendTo(document.body).on(MOUSELEAVE_NS, this._mouseleave);
}
},
_mouseleave: function (e) {
var target = e.relatedTarget;
var chart = this.chartElement[0];
if (target && target !== chart && !$.contains(chart, target)) {
this.trigger(LEAVE);
this.hide();
}
},
_hideElement: function () {
var tooltip = this;
var element = this.element;
if (element) {
element.fadeOut({
always: function () {
if (!tooltip.visible) {
element.off(MOUSELEAVE_NS).remove();
}
}
});
}
},
_pointContent: function (point) {
var tooltip = this, options = deepExtend({}, tooltip.options, point.options.tooltip), content, tooltipTemplate;
if (defined(point.value)) {
content = point.value.toString();
}
if (options.template) {
tooltipTemplate = template(options.template);
content = tooltipTemplate({
value: point.value,
category: point.category,
series: point.series,
dataItem: point.dataItem,
percentage: point.percentage,
runningTotal: point.runningTotal,
total: point.total,
low: point.low,
high: point.high,
xLow: point.xLow,
xHigh: point.xHigh,
yLow: point.yLow,
yHigh: point.yHigh
});
} else if (options.format) {
content = point.formatValue(options.format);
}
return content;
},
_pointAnchor: function (point) {
var size = this._measure();
return point.tooltipAnchor(size.width, size.height);
},
_fit: function (offset, size, viewPortSize) {
var output = 0;
if (offset + size > viewPortSize) {
output = viewPortSize - (offset + size);
}
if (offset < 0) {
output = -offset;
}
return output;
}
});
var Tooltip = BaseTooltip.extend({
show: function (point) {
var tooltip = this, options = deepExtend({}, tooltip.options, point.options.tooltip);
if (!point || !point.tooltipAnchor || !tooltip.element) {
return;
}
tooltip.element.html(tooltip._pointContent(point));
tooltip.anchor = tooltip._pointAnchor(point);
if (tooltip.anchor) {
tooltip.setStyle(options, point);
BaseTooltip.fn.show.call(tooltip, point);
} else {
tooltip.hide();
}
}
});
var SharedTooltip = BaseTooltip.extend({
init: function (element, plotArea, options) {
var tooltip = this;
BaseTooltip.fn.init.call(tooltip, element, options);
tooltip.plotArea = plotArea;
},
options: {
sharedTemplate: '<table>' + '<th colspan=\'2\'>#= categoryText #</th>' + '# for(var i = 0; i < points.length; i++) { #' + '# var point = points[i]; #' + '<tr>' + '# if(point.series.name) { # ' + '<td> #= point.series.name #:</td>' + '# } #' + '<td>#= content(point) #</td>' + '</tr>' + '# } #' + '</table>',
categoryFormat: '{0:d}'
},
showAt: function (points, coords) {
var tooltip = this, options = tooltip.options, plotArea = tooltip.plotArea, axis = plotArea.categoryAxis, index = axis.pointCategoryIndex(coords), category = axis.getCategory(coords), slot = axis.getSlot(index), content;
points = $.grep(points, function (p) {
var tooltip = p.series.tooltip, excluded = tooltip && tooltip.visible === false;
return !excluded;
});
if (points.length > 0) {
content = tooltip._content(points, category);
tooltip.element.html(content);
tooltip.anchor = tooltip._slotAnchor(coords, slot);
tooltip.setStyle(options, points[0]);
BaseTooltip.fn.show.call(tooltip);
}
},
_slotAnchor: function (point, slot) {
var tooltip = this, plotArea = tooltip.plotArea, axis = plotArea.categoryAxis, anchor, size = this._measure(), hCenter = point.y - size.height / 2;
if (axis.options.vertical) {
anchor = Point2D(point.x, hCenter);
} else {
anchor = Point2D(slot.center().x, hCenter);
}
return anchor;
},
_content: function (points, category) {
var tooltip = this, template, content;
template = kendo.template(tooltip.options.sharedTemplate);
content = template({
points: points,
category: category,
categoryText: autoFormat(tooltip.options.categoryFormat, category),
content: tooltip._pointContent
});
return content;
}
});
var Crosshair = ChartElement.extend({
init: function (axis, options) {
ChartElement.fn.init.call(this, options);
this.axis = axis;
this.stickyMode = axis instanceof CategoryAxis;
},
options: {
color: BLACK,
width: 1,
zIndex: -1,
tooltip: { visible: false }
},
showAt: function (point) {
this.point = point;
this.moveLine();
this.line.visible(true);
var tooltipOptions = this.options.tooltip;
if (tooltipOptions.visible) {
if (!this.tooltip) {
this.tooltip = new CrosshairTooltip(this, deepExtend({}, tooltipOptions, { stickyMode: this.stickyMode }));
}
this.tooltip.showAt(point);
}
},
hide: function () {
this.line.visible(false);
if (this.tooltip) {
this.tooltip.hide();
}
},
moveLine: function () {
var crosshair = this, axis = crosshair.axis, vertical = axis.options.vertical, box = crosshair.getBox(), point = crosshair.point, dim = vertical ? Y : X, slot, lineStart, lineEnd;
lineStart = new geom.Point(box.x1, box.y1);
if (vertical) {
lineEnd = new geom.Point(box.x2, box.y1);
} else {
lineEnd = new geom.Point(box.x1, box.y2);
}
if (point) {
if (crosshair.stickyMode) {
slot = axis.getSlot(axis.pointCategoryIndex(point));
lineStart[dim] = lineEnd[dim] = slot.center()[dim];
} else {
lineStart[dim] = lineEnd[dim] = point[dim];
}
}
crosshair.box = box;
this.line.moveTo(lineStart).lineTo(lineEnd);
},
getBox: function () {
var crosshair = this, axis = crosshair.axis, axes = axis.pane.axes, length = axes.length, vertical = axis.options.vertical, box = axis.lineBox().clone(), dim = vertical ? X : Y, axisLineBox, currentAxis, i;
for (i = 0; i < length; i++) {
currentAxis = axes[i];
if (currentAxis.options.vertical != vertical) {
if (!axisLineBox) {
axisLineBox = currentAxis.lineBox().clone();
} else {
axisLineBox.wrap(currentAxis.lineBox());
}
}
}
box[dim + 1] = axisLineBox[dim + 1];
box[dim + 2] = axisLineBox[dim + 2];
return box;
},
createVisual: function () {
ChartElement.fn.createVisual.call(this);
var options = this.options;
this.line = new draw.Path({
stroke: {
color: options.color,
width: options.width,
opacity: options.opacity,
dashType: options.dashType
},
visible: false
});
this.moveLine();
this.visual.append(this.line);
},
destroy: function () {
var crosshair = this;
if (crosshair.tooltip) {
crosshair.tooltip.destroy();
}
ChartElement.fn.destroy.call(crosshair);
}
});
var CrosshairTooltip = BaseTooltip.extend({
init: function (crosshair, options) {
var tooltip = this, chartElement = crosshair.axis.getRoot().chart.element;
tooltip.crosshair = crosshair;
BaseTooltip.fn.init.call(tooltip, chartElement, deepExtend({}, tooltip.options, { background: crosshair.axis.plotArea.options.seriesColors[0] }, options));
tooltip.setStyle(tooltip.options);
},
options: { padding: 10 },
showAt: function (point) {
var tooltip = this, element = tooltip.element;
if (element) {
tooltip.point = point;
tooltip.element.html(tooltip.content(point));
tooltip.anchor = tooltip.getAnchor();
tooltip.move();
}
},
move: function () {
var tooltip = this, element = tooltip.element, offset = tooltip._offset();
tooltip._ensureElement();
element.css({
top: offset.top,
left: offset.left
}).show();
},
content: function (point) {
var tooltip = this, options = tooltip.options, axis = tooltip.crosshair.axis, axisOptions = axis.options, content, value, tooltipTemplate;
value = content = axis[options.stickyMode ? 'getCategory' : 'getValue'](point);
if (options.template) {
tooltipTemplate = template(options.template);
content = tooltipTemplate({ value: value });
} else if (options.format) {
content = autoFormat(options.format, value);
} else {
if (axisOptions.type === DATE) {
content = autoFormat(axisOptions.labels.dateFormats[axisOptions.baseUnit], value);
}
}
return content;
},
getAnchor: function () {
var tooltip = this, options = tooltip.options, position = options.position, crosshair = this.crosshair, vertical = !crosshair.axis.options.vertical, lineBox = crosshair.line.bbox(), size = this._measure(), halfWidth = size.width / 2, halfHeight = size.height / 2, padding = options.padding, anchor;
if (vertical) {
if (position === BOTTOM) {
anchor = lineBox.bottomLeft().translate(-halfWidth, padding);
} else {
anchor = lineBox.topLeft().translate(-halfWidth, -size.height - padding);
}
} else {
if (position === LEFT) {
anchor = lineBox.topLeft().translate(-size.width - padding, -halfHeight);
} else {
anchor = lineBox.topRight().translate(padding, -halfHeight);
}
}
return anchor;
},
hide: function () {
this.element.hide();
this.point = null;
},
destroy: function () {
BaseTooltip.fn.destroy.call(this);
this.point = null;
}
});
var Aggregates = {
min: function (values) {
var min = MAX_VALUE, length = values.length, i, n;
for (i = 0; i < length; i++) {
n = values[i];
if (isNumber(n)) {
min = math.min(min, n);
}
}
return min === MAX_VALUE ? values[0] : min;
},
max: function (values) {
var max = MIN_VALUE, length = values.length, i, n;
for (i = 0; i < length; i++) {
n = values[i];
if (isNumber(n)) {
max = math.max(max, n);
}
}
return max === MIN_VALUE ? values[0] : max;
},
sum: function (values) {
var length = values.length, sum = 0, i, n;
for (i = 0; i < length; i++) {
n = values[i];
if (isNumber(n)) {
sum += n;
}
}
return sum;
},
sumOrNull: function (values) {
var result = null;
if (countNumbers(values)) {
result = Aggregates.sum(values);
}
return result;
},
count: function (values) {
var length = values.length, count = 0, i, val;
for (i = 0; i < length; i++) {
val = values[i];
if (val !== null && defined(val)) {
count++;
}
}
return count;
},
avg: function (values) {
var result = values[0], count = countNumbers(values);
if (count > 0) {
result = Aggregates.sum(values) / count;
}
return result;
},
first: function (values) {
var length = values.length, i, val;
for (i = 0; i < length; i++) {
val = values[i];
if (val !== null && defined(val)) {
return val;
}
}
return values[0];
}
};
function DefaultAggregates() {
this._defaults = {};
}
DefaultAggregates.prototype = {
register: function (seriesTypes, aggregates) {
for (var i = 0; i < seriesTypes.length; i++) {
this._defaults[seriesTypes[i]] = aggregates;
}
},
query: function (seriesType) {
return this._defaults[seriesType];
}
};
DefaultAggregates.current = new DefaultAggregates();
var Selection = Observable.extend({
init: function (chart, categoryAxis, options) {
var that = this, chartElement = chart.element, categoryAxisLineBox = categoryAxis.lineBox(), valueAxis = that.getValueAxis(categoryAxis), valueAxisLineBox = valueAxis.lineBox(), selectorPrefix = '.' + CSS_PREFIX, wrapper, padding;
Observable.fn.init.call(that);
that.options = deepExtend({}, that.options, options);
options = that.options;
that.chart = chart;
that.chartElement = chartElement;
that.categoryAxis = categoryAxis;
that._dateAxis = that.categoryAxis instanceof DateCategoryAxis;
that.valueAxis = valueAxis;
if (that._dateAxis) {
deepExtend(options, {
min: toDate(options.min),
max: toDate(options.max),
from: toDate(options.from),
to: toDate(options.to)
});
}
that.template = Selection.template;
if (!that.template) {
that.template = Selection.template = renderTemplate('<div class=\'' + CSS_PREFIX + 'selector\' ' + 'style=\'width: #= d.width #px; height: #= d.height #px;' + ' top: #= d.offset.top #px; left: #= d.offset.left #px;\'>' + '<div class=\'' + CSS_PREFIX + 'mask\'></div>' + '<div class=\'' + CSS_PREFIX + 'mask\'></div>' + '<div class=\'' + CSS_PREFIX + 'selection\'>' + '<div class=\'' + CSS_PREFIX + 'selection-bg\'></div>' + '<div class=\'' + CSS_PREFIX + 'handle ' + CSS_PREFIX + 'leftHandle\'><div></div></div>' + '<div class=\'' + CSS_PREFIX + 'handle ' + CSS_PREFIX + 'rightHandle\'><div></div></div>' + '</div></div>');
}
padding = {
left: parseInt(chartElement.css('paddingLeft'), 10),
right: parseInt(chartElement.css('paddingTop'), 10)
};
that.options = deepExtend({}, {
width: categoryAxisLineBox.width(),
height: valueAxisLineBox.height(),
padding: padding,
offset: {
left: valueAxisLineBox.x2 + padding.left,
top: valueAxisLineBox.y1 + padding.right
},
from: options.min,
to: options.max
}, options);
if (that.options.visible) {
that.wrapper = wrapper = $(that.template(that.options)).appendTo(chartElement);
that.selection = wrapper.find(selectorPrefix + 'selection');
that.leftMask = wrapper.find(selectorPrefix + 'mask').first();
that.rightMask = wrapper.find(selectorPrefix + 'mask').last();
that.leftHandle = wrapper.find(selectorPrefix + 'leftHandle');
that.rightHandle = wrapper.find(selectorPrefix + 'rightHandle');
that.options.selection = {
border: {
left: parseFloat(that.selection.css('border-left-width'), 10),
right: parseFloat(that.selection.css('border-right-width'), 10)
}
};
that.leftHandle.css('top', (that.selection.height() - that.leftHandle.height()) / 2);
that.rightHandle.css('top', (that.selection.height() - that.rightHandle.height()) / 2);
that.set(that._index(options.from), that._index(options.to));
that.bind(that.events, that.options);
that.wrapper[0].style.cssText = that.wrapper[0].style.cssText;
that.wrapper.on(MOUSEWHEEL_NS, proxy(that._mousewheel, that));
if (kendo.UserEvents) {
that.userEvents = new kendo.UserEvents(that.wrapper, {
global: true,
stopPropagation: true,
multiTouch: true,
fastTap: true,
start: proxy(that._start, that),
move: proxy(that._move, that),
end: proxy(that._end, that),
tap: proxy(that._tap, that),
gesturestart: proxy(that._gesturechange, that),
gesturechange: proxy(that._gesturechange, that)
});
} else {
that.leftHandle.add(that.rightHandle).removeClass(CSS_PREFIX + 'handle');
}
}
},
events: [
SELECT_START,
SELECT,
SELECT_END
],
options: {
visible: true,
mousewheel: { zoom: BOTH },
min: MIN_VALUE,
max: MAX_VALUE
},
destroy: function () {
var that = this, userEvents = that.userEvents;
if (userEvents) {
userEvents.destroy();
}
clearTimeout(that._mwTimeout);
that._state = null;
that.wrapper.remove();
},
_rangeEventArgs: function (range) {
var that = this;
return {
axis: that.categoryAxis.options,
from: that._value(range.from),
to: that._value(range.to)
};
},
_start: function (e) {
var that = this, options = that.options, target = $(e.event.target), args;
if (that._state || !target) {
return;
}
that.chart._unsetActivePoint();
that._state = {
moveTarget: target.parents('.k-handle').add(target).first(),
startLocation: e.x ? e.x.location : 0,
range: {
from: that._index(options.from),
to: that._index(options.to)
}
};
args = that._rangeEventArgs({
from: that._index(options.from),
to: that._index(options.to)
});
if (that.trigger(SELECT_START, args)) {
that.userEvents.cancel();
that._state = null;
}
},
_move: function (e) {
if (!this._state) {
return;
}
var that = this, state = that._state, options = that.options, categories = that.categoryAxis.options.categories, from = that._index(options.from), to = that._index(options.to), min = that._index(options.min), max = that._index(options.max), delta = state.startLocation - e.x.location, range = state.range, oldRange = {
from: range.from,
to: range.to
}, span = range.to - range.from, target = state.moveTarget, scale = that.wrapper.width() / (categories.length - 1), offset = math.round(delta / scale);
if (!target) {
return;
}
e.preventDefault();
if (target.is('.k-selection, .k-selection-bg')) {
range.from = math.min(math.max(min, from - offset), max - span);
range.to = math.min(range.from + span, max);
} else if (target.is('.k-leftHandle')) {
range.from = math.min(math.max(min, from - offset), max - 1);
range.to = math.max(range.from + 1, range.to);
} else if (target.is('.k-rightHandle')) {
range.to = math.min(math.max(min + 1, to - offset), max);
range.from = math.min(range.to - 1, range.from);
}
if (range.from !== oldRange.from || range.to !== oldRange.to) {
that.move(range.from, range.to);
that.trigger(SELECT, that._rangeEventArgs(range));
}
},
_end: function () {
var that = this, range = that._state.range;
delete that._state;
that.set(range.from, range.to);
that.trigger(SELECT_END, that._rangeEventArgs(range));
},
_gesturechange: function (e) {
if (!this._state) {
return;
}
var that = this, chart = that.chart, state = that._state, options = that.options, categoryAxis = that.categoryAxis, range = state.range, p0 = chart._toModelCoordinates(e.touches[0].x.location).x, p1 = chart._toModelCoordinates(e.touches[1].x.location).x, left = math.min(p0, p1), right = math.max(p0, p1);
e.preventDefault();
state.moveTarget = null;
range.from = categoryAxis.pointCategoryIndex(new dataviz.Point2D(left)) || options.min;
range.to = categoryAxis.pointCategoryIndex(new dataviz.Point2D(right)) || options.max;
that.move(range.from, range.to);
},
_tap: function (e) {
var that = this, options = that.options, coords = that.chart._eventCoordinates(e), categoryAxis = that.categoryAxis, categoryIx = categoryAxis.pointCategoryIndex(new dataviz.Point2D(coords.x, categoryAxis.box.y1)), from = that._index(options.from), to = that._index(options.to), min = that._index(options.min), max = that._index(options.max), span = to - from, mid = from + span / 2, offset = math.round(mid - categoryIx), range = {}, rightClick = e.event.which === 3;
if (that._state || rightClick) {
return;
}
e.preventDefault();
that.chart._unsetActivePoint();
if (!categoryAxis.options.justified) {
offset--;
}
range.from = math.min(math.max(min, from - offset), max - span);
range.to = math.min(range.from + span, max);
that._start(e);
if (that._state) {
that._state.range = range;
that.trigger(SELECT, that._rangeEventArgs(range));
that._end();
}
},
_mousewheel: function (e) {
var that = this, options = that.options, delta = mwDelta(e);
that._start({ event: { target: that.selection } });
if (that._state) {
var range = that._state.range;
e.preventDefault();
e.stopPropagation();
if (math.abs(delta) > 1) {
delta *= ZOOM_ACCELERATION;
}
if (options.mousewheel.reverse) {
delta *= -1;
}
if (that.expand(delta)) {
that.trigger(SELECT, {
axis: that.categoryAxis.options,
delta: delta,
originalEvent: e,
from: that._value(range.from),
to: that._value(range.to)
});
}
if (that._mwTimeout) {
clearTimeout(that._mwTimeout);
}
that._mwTimeout = setTimeout(function () {
that._end();
}, MOUSEWHEEL_DELAY);
}
},
_index: function (value) {
var that = this, categoryAxis = that.categoryAxis, categories = categoryAxis.options.categories, index = value;
if (value instanceof Date) {
index = lteDateIndex(value, categories);
if (!categoryAxis.options.justified && value > last(categories)) {
index += 1;
}
}
return index;
},
_value: function (index) {
var that = this, categoryAxis = this.categoryAxis, categories = categoryAxis.options.categories, value = index;
if (that._dateAxis) {
if (index > categories.length - 1) {
value = that.options.max;
} else {
value = categories[index];
}
}
return value;
},
_slot: function (value) {
var categoryAxis = this.categoryAxis;
var index = this._index(value);
return categoryAxis.getSlot(index, index, true);
},
move: function (from, to) {
var that = this, options = that.options, offset = options.offset, padding = options.padding, border = options.selection.border, leftMaskWidth, rightMaskWidth, box, distance;
box = that._slot(from);
leftMaskWidth = round(box.x1 - offset.left + padding.left);
that.leftMask.width(leftMaskWidth);
that.selection.css('left', leftMaskWidth);
box = that._slot(to);
rightMaskWidth = round(options.width - (box.x1 - offset.left + padding.left));
that.rightMask.width(rightMaskWidth);
distance = options.width - rightMaskWidth;
if (distance != options.width) {
distance += border.right;
}
that.rightMask.css('left', distance);
that.selection.width(math.max(options.width - (leftMaskWidth + rightMaskWidth) - border.right, 0));
},
set: function (from, to) {
var that = this, options = that.options, min = that._index(options.min), max = that._index(options.max);
from = limitValue(that._index(from), min, max);
to = limitValue(that._index(to), from + 1, max);
if (options.visible) {
that.move(from, to);
}
options.from = that._value(from);
options.to = that._value(to);
},
expand: function (delta) {
var that = this, options = that.options, min = that._index(options.min), max = that._index(options.max), zDir = options.mousewheel.zoom, from = that._index(options.from), to = that._index(options.to), range = {
from: from,
to: to
}, oldRange = deepExtend({}, range);
if (that._state) {
range = that._state.range;
}
if (zDir !== RIGHT) {
range.from = limitValue(limitValue(from - delta, 0, to - 1), min, max);
}
if (zDir !== LEFT) {
range.to = limitValue(limitValue(to + delta, range.from + 1, max), min, max);
}
if (range.from !== oldRange.from || range.to !== oldRange.to) {
that.set(range.from, range.to);
return true;
}
},
getValueAxis: function (categoryAxis) {
var axes = categoryAxis.pane.axes, axesCount = axes.length, i, axis;
for (i = 0; i < axesCount; i++) {
axis = axes[i];
if (axis.options.vertical !== categoryAxis.options.vertical) {
return axis;
}
}
}
});
var Pannable = Class.extend({
init: function (plotArea, options) {
this.plotArea = plotArea;
this.options = deepExtend({}, this.options, options);
},
options: {
key: 'none',
lock: 'none'
},
start: function (e) {
this._active = acceptKey(e.event, this.options.key);
},
move: function (e) {
if (this._active) {
var axisRanges = this.axisRanges = this._panAxes(e, X).concat(this._panAxes(e, Y));
if (axisRanges.length) {
this.axisRanges = axisRanges;
return toChartAxisRanges(axisRanges);
}
}
},
end: function () {
this._active = false;
},
pan: function () {
var plotArea = this.plotArea;
var axisRanges = this.axisRanges;
var range;
if (axisRanges.length) {
for (var idx = 0; idx < axisRanges.length; idx++) {
range = axisRanges[idx];
plotArea.updateAxisOptions(range.axis, range.range);
}
plotArea.redraw(plotArea.panes);
}
},
_panAxes: function (e, position) {
var plotArea = this.plotArea;
var delta = -e[position].delta;
var lock = (this.options.lock || '').toLowerCase();
var updatedAxes = [];
if (delta !== 0 && (lock || '').toLowerCase() != position) {
var axes = plotArea.axes;
var axis;
var range;
for (var idx = 0; idx < axes.length; idx++) {
axis = axes[idx];
if (position == X && !axis.options.vertical || position == Y && axis.options.vertical) {
range = axis.pan(delta);
if (range) {
range.limitRange = true;
updatedAxes.push({
axis: axis,
range: range
});
}
}
}
}
return updatedAxes;
}
});
var ZoomSelection = Class.extend({
init: function (chart, options) {
this.chart = chart;
this.options = deepExtend({}, this.options, options);
this._marquee = $('<div class=\'k-marquee\'><div class=\'k-marquee-color\'></div></div>');
},
options: {
key: 'shift',
lock: 'none'
},
start: function (e) {
if (acceptKey(e.event, this.options.key)) {
var chart = this.chart;
var point = chart._toModelCoordinates(e.x.client, e.y.client);
var zoomPane = this._zoomPane = chart._plotArea.paneByPoint(point);
if (zoomPane) {
var clipBox = zoomPane.clipBox().clone();
var elementOffset = this._elementOffset();
clipBox.translate(elementOffset.left, elementOffset.top);
this._zoomPaneClipBox = clipBox;
this._marquee.appendTo(document.body).css({
left: e.x.client + 1,
top: e.y.client + 1,
width: 0,
height: 0
});
}
}
},
_elementOffset: function () {
var chartElement = this.chart.element;
var chartOffset = chartElement.offset();
return {
left: parseInt(chartElement.css('paddingTop'), 10) + chartOffset.left,
top: parseInt(chartElement.css('paddingLeft'), 10) + chartOffset.top
};
},
move: function (e) {
var zoomPane = this._zoomPane;
if (zoomPane) {
var selectionPosition = this._selectionPosition(e);
this._marquee.css(selectionPosition);
}
},
end: function (e) {
var zoomPane = this._zoomPane;
if (zoomPane) {
var elementOffset = this._elementOffset();
var selectionPosition = this._selectionPosition(e);
selectionPosition.left -= elementOffset.left;
selectionPosition.top -= elementOffset.top;
var start = {
x: selectionPosition.left,
y: selectionPosition.top
};
var end = {
x: selectionPosition.left + selectionPosition.width,
y: selectionPosition.top + selectionPosition.height
};
this._updateAxisRanges(start, end);
this._marquee.remove();
delete this._zoomPane;
return toChartAxisRanges(this.axisRanges);
}
},
zoom: function () {
var axisRanges = this.axisRanges;
if (axisRanges && axisRanges.length) {
var plotArea = this.chart._plotArea;
var axisRange;
for (var idx = 0; idx < axisRanges.length; idx++) {
axisRange = axisRanges[idx];
plotArea.updateAxisOptions(axisRange.axis, axisRange.range);
}
plotArea.redraw(plotArea.panes);
}
},
destroy: function () {
this._marquee.remove();
delete this._marquee;
},
_updateAxisRanges: function (start, end) {
var lock = (this.options.lock || '').toLowerCase();
var axisRanges = [];
var axes = this._zoomPane.axes;
var axis, vertical;
for (var idx = 0; idx < axes.length; idx++) {
axis = axes[idx];
vertical = axis.options.vertical;
if (!(lock == X && !vertical) && !(lock === Y && vertical)) {
var range = axis.pointsRange(start, end);
axisRanges.push({
axis: axis,
range: range
});
}
}
this.axisRanges = axisRanges;
},
_selectionPosition: function (e) {
var lock = (this.options.lock || '').toLowerCase();
var left = math.min(e.x.startLocation, e.x.location);
var top = math.min(e.y.startLocation, e.y.location);
var width = math.abs(e.x.initialDelta);
var height = math.abs(e.y.initialDelta);
var clipBox = this._zoomPaneClipBox;
if (lock == X) {
left = clipBox.x1;
width = clipBox.width();
}
if (lock == Y) {
top = clipBox.y1;
height = clipBox.height();
}
if (e.x.location > clipBox.x2) {
width = clipBox.x2 - e.x.startLocation;
}
if (e.x.location < clipBox.x1) {
width = e.x.startLocation - clipBox.x1;
}
if (e.y.location > clipBox.y2) {
height = clipBox.y2 - e.y.startLocation;
}
if (e.y.location < clipBox.y1) {
height = e.y.startLocation - clipBox.y1;
}
return {
left: math.max(left, clipBox.x1),
top: math.max(top, clipBox.y1),
width: width,
height: height
};
}
});
var MousewheelZoom = Class.extend({
init: function (chart, options) {
this.chart = chart;
this.options = deepExtend({}, this.options, options);
},
updateRanges: function (delta) {
var lock = (this.options.lock || '').toLowerCase();
var axisRanges = [];
var axes = this.chart._plotArea.axes;
var axis, vertical;
for (var idx = 0; idx < axes.length; idx++) {
axis = axes[idx];
vertical = axis.options.vertical;
if (!(lock == X && !vertical) && !(lock === Y && vertical)) {
var range = axis.zoomRange(-delta);
if (range) {
axisRanges.push({
axis: axis,
range: range
});
}
}
}
this.axisRanges = axisRanges;
return toChartAxisRanges(axisRanges);
},
zoom: function () {
var axisRanges = this.axisRanges;
if (axisRanges && axisRanges.length) {
var plotArea = this.chart._plotArea;
var axisRange;
for (var idx = 0; idx < axisRanges.length; idx++) {
axisRange = axisRanges[idx];
plotArea.updateAxisOptions(axisRange.axis, axisRange.range);
}
plotArea.redraw(plotArea.panes);
}
}
});
var SeriesAggregator = function (series, binder, defaultAggregates) {
var sa = this, canonicalFields = binder.canonicalFields(series), valueFields = binder.valueFields(series), sourceFields = binder.sourceFields(series, canonicalFields), seriesFields = sa._seriesFields = [], defaults = defaultAggregates.query(series.type), rootAggregate = series.aggregate || defaults, i;
sa._series = series;
sa._binder = binder;
for (i = 0; i < canonicalFields.length; i++) {
var field = canonicalFields[i], fieldAggregate;
if (typeof rootAggregate === OBJECT) {
fieldAggregate = rootAggregate[field];
} else if (i === 0 || inArray(field, valueFields)) {
fieldAggregate = rootAggregate;
} else {
break;
}
if (fieldAggregate) {
seriesFields.push({
canonicalName: field,
name: sourceFields[i],
transform: isFn(fieldAggregate) ? fieldAggregate : Aggregates[fieldAggregate]
});
}
}
};
SeriesAggregator.prototype = {
aggregatePoints: function (srcPoints, group) {
var sa = this, data = sa._bindPoints(srcPoints || []), series = sa._series, seriesFields = sa._seriesFields, i, field, srcValues, value, firstDataItem = data.dataItems[0], result = {};
if (firstDataItem && !isNumber(firstDataItem) && !isArray(firstDataItem)) {
var fn = function () {
};
fn.prototype = firstDataItem;
result = new fn();
}
for (i = 0; i < seriesFields.length; i++) {
field = seriesFields[i];
srcValues = sa._bindField(data.values, field.canonicalName);
value = field.transform(srcValues, series, data.dataItems, group);
if (value !== null && typeof value === OBJECT && !defined(value.length) && !(value instanceof Date)) {
result = value;
break;
} else {
if (defined(value)) {
ensureTree(field.name, result);
kendo.setter(field.name)(result, value);
}
}
}
return result;
},
_bindPoints: function (points) {
var sa = this, binder = sa._binder, series = sa._series, values = [], dataItems = [], i, pointIx;
for (i = 0; i < points.length; i++) {
pointIx = points[i];
values.push(binder.bindPoint(series, pointIx));
dataItems.push(series.data[pointIx]);
}
return {
values: values,
dataItems: dataItems
};
},
_bindField: function (data, field) {
var values = [], count = data.length, i, item, value, valueFields;
for (i = 0; i < count; i++) {
item = data[i];
valueFields = item.valueFields;
if (defined(valueFields[field])) {
value = valueFields[field];
} else {
value = item.fields[field];
}
values.push(value);
}
return values;
}
};
var ChartAxis = Class.extend({
init: function (axis) {
this._axis = axis;
},
slot: function (from, to) {
return this._axis.slot(from, to);
},
range: function () {
return this._axis.range();
}
});
function intersection(a1, a2, b1, b2) {
var result, ua_t = (b2.x - b1.x) * (a1.y - b1.y) - (b2.y - b1.y) * (a1.x - b1.x), u_b = (b2.y - b1.y) * (a2.x - a1.x) - (b2.x - b1.x) * (a2.y - a1.y), ua;
if (u_b !== 0) {
ua = ua_t / u_b;
result = new Point2D(a1.x + ua * (a2.x - a1.x), a1.y + ua * (a2.y - a1.y));
}
return result;
}
function applySeriesDefaults(options, themeOptions) {
var series = options.series, i, seriesLength = series.length, seriesType, seriesDefaults = options.seriesDefaults, commonDefaults = deepExtend({}, options.seriesDefaults), themeSeriesDefaults = themeOptions ? deepExtend({}, themeOptions.seriesDefaults) : {}, commonThemeDefaults = deepExtend({}, themeSeriesDefaults);
cleanupNestedSeriesDefaults(commonDefaults);
cleanupNestedSeriesDefaults(commonThemeDefaults);
for (i = 0; i < seriesLength; i++) {
seriesType = series[i].type || options.seriesDefaults.type;
var baseOptions = deepExtend({ data: [] }, commonThemeDefaults, themeSeriesDefaults[seriesType], { tooltip: options.tooltip }, commonDefaults, seriesDefaults[seriesType]);
series[i]._defaults = baseOptions;
series[i] = deepExtend({}, baseOptions, series[i]);
}
}
function cleanupNestedSeriesDefaults(seriesDefaults) {
delete seriesDefaults.bar;
delete seriesDefaults.column;
delete seriesDefaults.rangeColumn;
delete seriesDefaults.line;
delete seriesDefaults.verticalLine;
delete seriesDefaults.pie;
delete seriesDefaults.donut;
delete seriesDefaults.area;
delete seriesDefaults.verticalArea;
delete seriesDefaults.scatter;
delete seriesDefaults.scatterLine;
delete seriesDefaults.bubble;
delete seriesDefaults.candlestick;
delete seriesDefaults.ohlc;
delete seriesDefaults.boxPlot;
delete seriesDefaults.bullet;
delete seriesDefaults.verticalBullet;
delete seriesDefaults.polarArea;
delete seriesDefaults.polarLine;
delete seriesDefaults.radarArea;
delete seriesDefaults.radarLine;
delete seriesDefaults.waterfall;
}
function applySeriesColors(options) {
var series = options.series, colors = options.seriesColors || [], i, currentSeries, seriesColor, defaults;
for (i = 0; i < series.length; i++) {
currentSeries = series[i];
seriesColor = colors[i % colors.length];
currentSeries.color = currentSeries.color || seriesColor;
defaults = currentSeries._defaults;
if (defaults) {
defaults.color = defaults.color || seriesColor;
}
}
}
function resolveAxisAliases(options) {
var alias;
each([
CATEGORY,
VALUE,
X,
Y
], function () {
alias = this + 'Axes';
if (options[alias]) {
options[this + 'Axis'] = options[alias];
delete options[alias];
}
});
}
function applyAxisDefaults(options, themeOptions) {
var themeAxisDefaults = (themeOptions || {}).axisDefaults || {};
each([
CATEGORY,
VALUE,
X,
Y
], function () {
var axisName = this + 'Axis', axes = [].concat(options[axisName]), axisDefaults = options.axisDefaults || {};
axes = $.map(axes, function (axisOptions) {
var axisColor = (axisOptions || {}).color;
var result = deepExtend({}, themeAxisDefaults, themeAxisDefaults[axisName], axisDefaults, axisDefaults[axisName], {
line: { color: axisColor },
labels: { color: axisColor },
title: { color: axisColor }
}, axisOptions);
delete result[axisName];
return result;
});
options[axisName] = axes.length > 1 ? axes : axes[0];
});
}
function categoriesCount(series) {
var seriesCount = series.length, categories = 0, i;
for (i = 0; i < seriesCount; i++) {
categories = math.max(categories, series[i].data.length);
}
return categories;
}
function sqr(value) {
return value * value;
}
extend($.easing, {
easeOutElastic: function (n, d, first, diff) {
var s = 1.70158, p = 0, a = diff;
if (n === 0) {
return first;
}
if (n === 1) {
return first + diff;
}
if (!p) {
p = 0.5;
}
if (a < math.abs(diff)) {
a = diff;
s = p / 4;
} else {
s = p / (2 * math.PI) * math.asin(diff / a);
}
return a * math.pow(2, -10 * n) * math.sin((n * 1 - s) * (1.1 * math.PI) / p) + diff + first;
}
});
function getField(field, row) {
if (row === null) {
return row;
}
var get = getter(field, true);
return get(row);
}
function getDateField(field, row) {
if (row === null) {
return row;
}
var key = '_date_' + field, value = row[key];
if (!value) {
value = toDate(getter(field, true)(row));
row[key] = value;
}
return value;
}
function toDate(value) {
var result, i;
if (value instanceof Date) {
result = value;
} else if (typeof value === STRING) {
result = kendo.parseDate(value) || new Date(value);
} else if (value) {
if (isArray(value)) {
result = [];
for (i = 0; i < value.length; i++) {
result.push(toDate(value[i]));
}
} else {
result = new Date(value);
}
}
return result;
}
function toTime(value) {
if (isArray(value)) {
return map(value, toTime);
} else if (value) {
return toDate(value).getTime();
}
}
function addDuration(date, value, unit, weekStartDay) {
var result = date, hours;
if (date) {
date = toDate(date);
hours = date.getHours();
if (unit === YEARS) {
result = new Date(date.getFullYear() + value, 0, 1);
kendo.date.adjustDST(result, 0);
} else if (unit === MONTHS) {
result = new Date(date.getFullYear(), date.getMonth() + value, 1);
kendo.date.adjustDST(result, hours);
} else if (unit === WEEKS) {
result = addDuration(startOfWeek(date, weekStartDay), value * 7, DAYS);
kendo.date.adjustDST(result, hours);
} else if (unit === DAYS) {
result = new Date(date.getFullYear(), date.getMonth(), date.getDate() + value);
kendo.date.adjustDST(result, hours);
} else if (unit === HOURS) {
result = addTicks(new Date(date.getFullYear(), date.getMonth(), date.getDate(), date.getHours()), value * TIME_PER_HOUR);
} 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);
}
if (result.getMilliseconds() > 0) {
result.setMilliseconds(0);
}
}
return result;
}
function startOfWeek(date, weekStartDay) {
var day = date.getDay(), daysToSubtract = 0;
if (!isNaN(day)) {
weekStartDay = weekStartDay || 0;
while (day !== weekStartDay) {
if (day === 0) {
day = 6;
} else {
day--;
}
daysToSubtract++;
}
}
return addTicks(date, -daysToSubtract * TIME_PER_DAY);
}
function floorDate(date, unit, weekStartDay) {
date = toDate(date);
return addDuration(date, 0, unit, weekStartDay);
}
function ceilDate(date, unit, weekStartDay) {
date = toDate(date);
if (date && floorDate(date, unit, weekStartDay).getTime() === date.getTime()) {
return date;
}
return addDuration(date, 1, unit, weekStartDay);
}
function dateDiff(a, b) {
var diff = a.getTime() - b, offsetDiff = a.getTimezoneOffset() - b.getTimezoneOffset();
return diff - (offsetDiff * diff > 0 ? offsetDiff * TIME_PER_MINUTE : 0);
}
function absoluteDateDiff(a, b) {
var diff = a.getTime() - b, offsetDiff = a.getTimezoneOffset() - b.getTimezoneOffset();
return diff - offsetDiff * TIME_PER_MINUTE;
}
function addTicks(date, ticks) {
var tzOffsetBefore = date.getTimezoneOffset(), result = new Date(date.getTime() + ticks), tzOffsetDiff = result.getTimezoneOffset() - tzOffsetBefore;
return new Date(result.getTime() + (ticks * tzOffsetDiff > 0 ? tzOffsetDiff * TIME_PER_MINUTE : 0));
}
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 dateIndex(value, start, baseUnit, baseUnitStep) {
var index;
var date = toDate(value);
var startDate = toDate(start);
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 {
index = timeIndex(date, startDate, baseUnit);
}
return index / baseUnitStep;
}
function timeIndex(date, start, baseUnit) {
return dateDiff(date, start) / TIME_PER_UNIT[baseUnit];
}
function singleItemOrArray(array) {
return array.length === 1 ? array[0] : array;
}
function axisGroupBox(axes) {
var length = axes.length, box, i, axisBox;
if (length > 0) {
for (i = 0; i < length; i++) {
axisBox = axes[i].contentBox();
if (!box) {
box = axisBox.clone();
} else {
box.wrap(axisBox);
}
}
}
return box || Box2D();
}
function equalsIgnoreCase(a, b) {
if (a && b) {
return a.toLowerCase() === b.toLowerCase();
}
return a === b;
}
function dateEquals(a, b) {
if (a && b) {
return toTime(a) === toTime(b);
}
return a === b;
}
function appendIfNotNull(array, element) {
if (element !== null) {
array.push(element);
}
}
function lteDateIndex(date, sortedDates) {
var low = 0, high = sortedDates.length - 1, i, currentDate;
while (low <= high) {
i = math.floor((low + high) / 2);
currentDate = sortedDates[i];
if (currentDate < date) {
low = i + 1;
continue;
}
if (currentDate > date) {
high = i - 1;
continue;
}
while (dateEquals(sortedDates[i - 1], date)) {
i--;
}
return i;
}
if (sortedDates[i] <= date) {
return i;
} else {
return i - 1;
}
}
function isNumber(val) {
return typeof val === 'number' && !isNaN(val);
}
function countNumbers(values) {
var length = values.length, count = 0, i, num;
for (i = 0; i < length; i++) {
num = values[i];
if (isNumber(num)) {
count++;
}
}
return count;
}
function areNumbers(values) {
return countNumbers(values) === values.length;
}
function axisRanges(axes) {
var i, axis, axisName, ranges = {};
for (i = 0; i < axes.length; i++) {
axis = axes[i];
axisName = axis.options.name;
if (axisName) {
ranges[axisName] = axis.range();
}
}
return ranges;
}
function evalOptions(options, context, state, dryRun) {
var property, propValue, excluded, defaults, depth, needsEval = false;
state = state || {};
excluded = state.excluded = state.excluded || [];
defaults = state.defaults = state.defaults || {};
depth = state.depth = state.depth || 0;
if (depth > MAX_EXPAND_DEPTH) {
return;
}
for (property in options) {
if (!inArray(property, state.excluded) && options.hasOwnProperty(property)) {
propValue = options[property];
if (isFn(propValue)) {
needsEval = true;
if (!dryRun) {
options[property] = valueOrDefault(propValue(context), defaults[property]);
}
} else if (typeof propValue === OBJECT) {
if (!dryRun) {
state.defaults = defaults[property];
}
state.depth++;
needsEval = evalOptions(propValue, context, state, dryRun) || needsEval;
state.depth--;
}
}
}
return needsEval;
}
function groupSeries(series, data) {
var result = [], nameTemplate, legacyTemplate = series.groupNameTemplate, groupIx, dataLength = data.length, seriesClone;
if (dataLength === 0) {
seriesClone = deepExtend({}, series);
seriesClone.visibleInLegend = false;
return [seriesClone];
}
if (defined(legacyTemplate)) {
kendo.logToConsole('\'groupNameTemplate\' is obsolete and will be removed in future versions. ' + 'Specify the group name template as \'series.name\'');
if (legacyTemplate) {
nameTemplate = template(legacyTemplate);
}
} else {
nameTemplate = template(series.name || '');
if (nameTemplate._slotCount === 0) {
nameTemplate = template(defined(series.name) ? '#= group.value #: #= series.name #' : '#= group.value #');
}
}
for (groupIx = 0; groupIx < dataLength; groupIx++) {
seriesClone = deepExtend({}, series);
if (!isFn(seriesClone.color)) {
seriesClone.color = undefined;
}
seriesClone._groupIx = groupIx;
result.push(seriesClone);
if (nameTemplate) {
seriesClone.name = nameTemplate({
series: seriesClone,
group: data[groupIx]
});
}
}
return result;
}
function filterSeriesByType(series, types) {
var i, currentSeries, result = [];
types = [].concat(types);
for (i = 0; i < series.length; i++) {
currentSeries = series[i];
if (inArray(currentSeries.type, types)) {
result.push(currentSeries);
}
}
return result;
}
function indexOf(item, arr) {
if (item instanceof Date) {
for (var i = 0, length = arr.length; i < length; i++) {
if (dateEquals(arr[i], item)) {
return i;
}
}
return -1;
} else {
return $.inArray(item, arr);
}
}
function sortDates(dates, comparer) {
comparer = comparer || dateComparer;
for (var i = 1, length = dates.length; i < length; i++) {
if (comparer(dates[i], dates[i - 1]) < 0) {
dates.sort(comparer);
break;
}
}
return dates;
}
function uniqueDates(srcDates, comparer) {
var i, dates = sortDates(srcDates, comparer), length = dates.length, result = length > 0 ? [dates[0]] : [];
comparer = comparer || dateComparer;
for (i = 1; i < length; i++) {
if (comparer(dates[i], last(result)) !== 0) {
result.push(dates[i]);
}
}
return result;
}
function isDateAxis(axisOptions, sampleCategory) {
var type = axisOptions.type, dateCategory = sampleCategory instanceof Date;
return !type && dateCategory || equalsIgnoreCase(type, DATE);
}
function transpose(rows) {
var result = [], rowCount = rows.length, rowIx, row, colIx, colCount;
for (rowIx = 0; rowIx < rowCount; rowIx++) {
row = rows[rowIx];
colCount = row.length;
for (colIx = 0; colIx < colCount; colIx++) {
result[colIx] = result[colIx] || [];
result[colIx].push(row[colIx]);
}
}
return result;
}
function ensureTree(fieldName, target) {
if (fieldName.indexOf('.') > -1) {
var parts = fieldName.split('.'), path = '', val;
while (parts.length > 1) {
path += parts.shift();
val = kendo.getter(path)(target) || {};
kendo.setter(path)(target, val);
path += '.';
}
}
}
function seriesTotal(series) {
var data = series.data;
var sum = 0;
for (var i = 0; i < data.length; i++) {
var pointData = SeriesBinder.current.bindPoint(series, i);
var value = pointData.valueFields.value;
if (typeof value === STRING) {
value = parseFloat(value);
}
if (isNumber(value) && pointData.fields.visible !== false) {
sum += math.abs(value);
}
}
return sum;
}
function hasGradientOverlay(options) {
var overlay = options.overlay;
return overlay && overlay.gradient && overlay.gradient != 'none';
}
function anyHasZIndex(elements) {
for (var idx = 0; idx < elements.length; idx++) {
if (defined(elements[idx].zIndex)) {
return true;
}
}
}
function preventDefault() {
this._defaultPrevented = true;
}
function pointByCategoryName(points, name) {
if (points) {
for (var idx = 0; idx < points.length; idx++) {
if (points[idx].category === name) {
return [points[idx]];
}
}
}
}
function hasValue(value) {
return defined(value) && value !== null;
}
function toChartAxisRanges(axisRanges) {
var ranges = {};
var axisRange;
for (var idx = 0; idx < axisRanges.length; idx++) {
axisRange = axisRanges[idx];
if (axisRange.axis.options.name) {
ranges[axisRange.axis.options.name] = {
min: axisRange.range.min,
max: axisRange.range.max
};
}
}
return ranges;
}
function acceptKey(e, mouseKey) {
var key = (mouseKey || '').toLowerCase();
var accept = key == 'none' && !(e.ctrlKey || e.shiftKey || e.altKey) || e[key + 'Key'];
return accept;
}
function preloadFonts(options, callback) {
var fonts = [];
fetchFonts(options, fonts);
kendo.util.loadFonts(fonts, callback);
}
function fetchFonts(options, fonts, state) {
var MAX_DEPTH = 5;
state = state || { depth: 0 };
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++;
fetchFonts(value, fonts, state);
state.depth--;
}
});
}
dataviz.ui.plugin(Chart);
PlotAreaFactory.current.register(CategoricalPlotArea, [
BAR,
COLUMN,
LINE,
VERTICAL_LINE,
AREA,
VERTICAL_AREA,
CANDLESTICK,
OHLC,
BULLET,
VERTICAL_BULLET,
BOX_PLOT,
RANGE_COLUMN,
RANGE_BAR,
WATERFALL,
HORIZONTAL_WATERFALL
]);
PlotAreaFactory.current.register(XYPlotArea, [
SCATTER,
SCATTER_LINE,
BUBBLE
]);
PlotAreaFactory.current.register(PiePlotArea, [PIE]);
PlotAreaFactory.current.register(DonutPlotArea, [DONUT]);
SeriesBinder.current.register([
BAR,
COLUMN,
LINE,
VERTICAL_LINE,
AREA,
VERTICAL_AREA
], [VALUE], [
CATEGORY,
COLOR,
NOTE_TEXT,
ERROR_LOW_FIELD,
ERROR_HIGH_FIELD
]);
SeriesBinder.current.register([
RANGE_COLUMN,
RANGE_BAR
], [
FROM,
TO
], [
CATEGORY,
COLOR,
NOTE_TEXT
]);
SeriesBinder.current.register([
WATERFALL,
HORIZONTAL_WATERFALL
], [VALUE], [
CATEGORY,
COLOR,
NOTE_TEXT,
SUMMARY_FIELD
]);
DefaultAggregates.current.register([
BAR,
COLUMN,
LINE,
VERTICAL_LINE,
AREA,
VERTICAL_AREA,
WATERFALL,
HORIZONTAL_WATERFALL
], {
value: MAX,
color: FIRST,
noteText: FIRST,
errorLow: MIN,
errorHigh: MAX
});
DefaultAggregates.current.register([
RANGE_COLUMN,
RANGE_BAR
], {
from: MIN,
to: MAX,
color: FIRST,
noteText: FIRST
});
SeriesBinder.current.register([
SCATTER,
SCATTER_LINE,
BUBBLE
], [
X,
Y
], [
COLOR,
NOTE_TEXT,
X_ERROR_LOW_FIELD,
X_ERROR_HIGH_FIELD,
Y_ERROR_LOW_FIELD,
Y_ERROR_HIGH_FIELD
]);
SeriesBinder.current.register([BUBBLE], [
X,
Y,
'size'
], [
COLOR,
CATEGORY,
NOTE_TEXT
]);
SeriesBinder.current.register([
CANDLESTICK,
OHLC
], [
'open',
'high',
'low',
'close'
], [
CATEGORY,
COLOR,
'downColor',
NOTE_TEXT
]);
DefaultAggregates.current.register([
CANDLESTICK,
OHLC
], {
open: MAX,
high: MAX,
low: MIN,
close: MAX,
color: FIRST,
downColor: FIRST,
noteText: FIRST
});
SeriesBinder.current.register([BOX_PLOT], [
'lower',
'q1',
'median',
'q3',
'upper',
'mean',
'outliers'
], [
CATEGORY,
COLOR,
NOTE_TEXT
]);
DefaultAggregates.current.register([BOX_PLOT], {
lower: MAX,
q1: MAX,
median: MAX,
q3: MAX,
upper: MAX,
mean: MAX,
outliers: FIRST,
color: FIRST,
noteText: FIRST
});
SeriesBinder.current.register([
BULLET,
VERTICAL_BULLET
], [
'current',
'target'
], [
CATEGORY,
COLOR,
'visibleInLegend',
NOTE_TEXT
]);
DefaultAggregates.current.register([
BULLET,
VERTICAL_BULLET
], {
current: MAX,
target: MAX,
color: FIRST,
noteText: FIRST
});
SeriesBinder.current.register([
PIE,
DONUT
], [VALUE], [
CATEGORY,
COLOR,
'explode',
'visibleInLegend',
'visible'
]);
deepExtend(dataviz, {
EQUALLY_SPACED_SERIES: EQUALLY_SPACED_SERIES,
Aggregates: Aggregates,
AreaChart: AreaChart,
AreaSegment: AreaSegment,
AxisGroupRangeTracker: AxisGroupRangeTracker,
Bar: Bar,
BarChart: BarChart,
BarLabel: BarLabel,
BubbleChart: BubbleChart,
Bullet: Bullet,
BulletChart: BulletChart,
CandlestickChart: CandlestickChart,
Candlestick: Candlestick,
CategoricalChart: CategoricalChart,
CategoricalErrorBar: CategoricalErrorBar,
CategoricalPlotArea: CategoricalPlotArea,
CategoryAxis: CategoryAxis,
ChartAxis: ChartAxis,
ChartContainer: ChartContainer,
ClipAnimation: ClipAnimation,
ClusterLayout: ClusterLayout,
Crosshair: Crosshair,
CrosshairTooltip: CrosshairTooltip,
DateCategoryAxis: DateCategoryAxis,
DateValueAxis: DateValueAxis,
DefaultAggregates: DefaultAggregates,
DonutChart: DonutChart,
DonutPlotArea: DonutPlotArea,
DonutSegment: DonutSegment,
ErrorBarBase: ErrorBarBase,
ErrorRangeCalculator: ErrorRangeCalculator,
Highlight: Highlight,
SharedTooltip: SharedTooltip,
Legend: Legend,
LegendItem: LegendItem,
LegendLayout: LegendLayout,
LineChart: LineChart,
LinePoint: LinePoint,
LineSegment: LineSegment,
Pane: Pane,
PieAnimation: PieAnimation,
PieChart: PieChart,
PieChartMixin: PieChartMixin,
PiePlotArea: PiePlotArea,
PieSegment: PieSegment,
PlotAreaBase: PlotAreaBase,
PlotAreaFactory: PlotAreaFactory,
PointEventsMixin: PointEventsMixin,
RangeBar: RangeBar,
RangeBarChart: RangeBarChart,
ScatterChart: ScatterChart,
ScatterErrorBar: ScatterErrorBar,
ScatterLineChart: ScatterLineChart,
Selection: Selection,
SeriesAggregator: SeriesAggregator,
SeriesBinder: SeriesBinder,
ShapeElement: ShapeElement,
SplineSegment: SplineSegment,
SplineAreaSegment: SplineAreaSegment,
StackWrap: StackWrap,
Tooltip: Tooltip,
OHLCChart: OHLCChart,
OHLCPoint: OHLCPoint,
WaterfallChart: WaterfallChart,
WaterfallSegment: WaterfallSegment,
XYPlotArea: XYPlotArea,
MousewheelZoom: MousewheelZoom,
addDuration: addDuration,
areNumbers: areNumbers,
axisGroupBox: axisGroupBox,
categoriesCount: categoriesCount,
ceilDate: ceilDate,
countNumbers: countNumbers,
duration: duration,
ensureTree: ensureTree,
indexOf: indexOf,
isNumber: isNumber,
floorDate: floorDate,
filterSeriesByType: filterSeriesByType,
hasValue: hasValue,
lteDateIndex: lteDateIndex,
evalOptions: evalOptions,
seriesTotal: seriesTotal,
singleItemOrArray: singleItemOrArray,
sortDates: sortDates,
startOfWeek: startOfWeek,
transpose: transpose,
toDate: toDate,
toTime: toTime,
uniqueDates: uniqueDates
});
}(window.kendo.jQuery));
return window.kendo;
}, typeof define == 'function' && define.amd ? define : function (a1, a2, a3) {
(a3 || a2)();
}));